Секреты программирования для Internet на Java
.pdfМы снова разрешаем множественные экземпляры следующего члена иерархии ActionImageAnimation. Этот класс фактически рисует фреймы. Для осуществления двойной буферизации воспользуемся отдельным классом BufferedImageGraphics:
package ventana.awt; import java.awt.*; import java.awt.image.*; import java.util.*;
public class BufferedImageGraphics { private Graphics graphicsBuf; private Image imageBuf;
//используем для выполнения двойной буферизации private ImageObserver imageObsv;
private Vector curImages=new Vector(); private int originalWidth;
private int originalHeight;
private Rectangle cropRect=new Rectangle();
//область обрезки
private boolean waitToTouchImages=false;
//охраняет буфер во время процесса
//добавления или удаления изображений
Color backgroundColor;
public BufferedImageGraphics(ImageObserver observer, Color c) { imageObsv=observer;
backgroundColor=c;
}
public boolean needBuffer() {
return (graphicsBuf==null && imageBuf==null);} public void setBuffer(Image i) {
imageBuf=i;
originalWidth=i.getWidth(imageObsv);
originalHeight=i.getHeight(imageObsv);
graphicsBuf=imageBuf.getGraphics();
graphicsBuf.setColor(backgroundColor);
graphicsBuf.fillRect(0,0,originalWidth,originalHeight);} public synchronized void addImage(PositionedImage pI) {
while (waitToTouchImages); waitToTouchImages=true;
//Поскольку мы должны рисовать картинку и убрать обрезку, может
возникнуть
//команда сбросить буфер до окончания работы. Это приведет к хаосу. Rectangle r=pI.getRect();
//выясняем, как это влияет на область обрезки
if (cropRect==null || cropRect.isEmpty()) cropRect=r; else
cropRect=cropRect.union(r);
curImages.addElement(pI); if (graphicsBuf!=null)
graphicsBuf.drawImage
(pI.getImage(),r.x,r.y,backgroundColor,imageObsv);
waitToTouchImages=false;}
private PositionedImage getCurPosImage(int i) {
return (PositionedImage)curImages.elementAt(i);} public synchronized boolean removeImage(PositionedImage I)
{
while (waitToTouchImages); waitToTouchImages=true;
int imgIndx=curImages.indexOf(I); if (imgIndx==-1) { waitToTouchImages=false;
return false;}
Rectangle clearReg=getCurPosImage(imgIndx).getRect(); // область для очистки curImages.removeElementAt(imgIndx);
Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com
graphicsBuf.fillRect
(clearReg.x,clearReg.y,clearReg.width,clearReg.height);
//очистка области cropRect=new Rectangle();
//подготовка к сбросу области обрезки for (int i=0;i<curImages.size();i++) {
PositionedImage somePI=getCurPosImage(i); Image img=somePI.getImage();
Rectangle someRect=somePI.getRect(); cropRect.add(someRect);
int x=someRect.x; int y=someRect.y;
graphicsBuf.drawImage(img,x,y,imageObsv);
}
waitToTouchImages=false; return true;}
public Graphics graphicsContext(Rectangle r) { cropRect.add(r);
return graphicsBuf.create(r.x,r.y,r.width,r.height);} public synchronized void paintBuffer(Graphics g) {
while (waitToTouchImages); waitToTouchImages=true; Rectangle curCR=g.getClipRect(); if (curCR==null) curCR=new
Rectangle(0,0,originalWidth,originalHeight);
g.clipRect(cropRect.x,cropRect.y,cropRect.width,cropRect.height);
g.drawImage(imageBuf,0,0,backgroundColor,imageObsv);
g.clipRect(0,0,originalWidth,originalHeight); cropRect=new Rectangle();
//сброс области обрезки
waitToTouchImages=false;
}
}
Посмотрим теперь, как будут передаваться события. Мы передаем события непосредственно всем ActionImage, которые в данный момент отображены. Поскольку наш ActioImageCanvas является компонентом, содержащимся в апплете, нам не нужно вручную передавать события наружу из апплета - это делается автоматически. А поскольку ActionImage не является подклассом класса Component, передачу должен выполнять ActionImageCanvas. Мы убеждаемся в том, что событие передано нужному ActionImage и что координаты являются относительными.
Почему бы не создать подкласс в Component?
Мы могли бы осуществить передачу событий, создав подкласс ActionImageCanvas в Container и подкласс ActionImage в Component. Однако фактически задача Container - содержать элементы экрана, которые не надо перемещать. Чтобы не бороться с трудностями при создании диспетчера компоновки, который будет отвечать за перемещение изображений, напишем новый класс, который будет уметь обращаться с событиями мыши.
Создадим базовый класс ActionImageAnimation. Этот базовый класс не будет пытаться перемещать картинки, но в нем можно создать подкласс, разрешающий разнообразные перемещения. Например, мы можем создать класс EllipticalActionImageAnimation, который будет перемещать картинки по эллиптической траектории. Как уже говорилось выше, подкласс может также реагировать на конфликты с другими картинками на рабочей области. Кроме того, этот класс может создавать такие эффекты, как соединение двух картинок вместе. Сливая два изображения, метод может просто вызвать метод updateImage класса ActionImageCanvas:
package ventana.aia; import java.util.*; import java.net.*; import java.awt.*; import java.applet.*; import ventana.util.*;
public class ActionImageAnimation implements Configurable, Runnable {
www.books-shop.com
private ActionImage ActionImages[]; private Thread animator;
private ActionAnimApplet motherApplet; private ActionImageCanvas parentCanvas; private boolean checkSequence=false;
private Vector loadingActionImages=new Vector(); private int curPause=100;
private ActionImage curActionImage; public void attachObject(Object o) throws
ConfigurationAttachmentException { if (!(o instanceof ActionImage))
throw (new ConfigurationAttachmentException ("not an ActionImage"));
ActionImage cur=(ActionImage)o; loadingActionImages.addElement(cur); cur.setParent(this);}
public void setParent(Object o) {
if (o instanceof ActionImageCanvas) { parentCanvas=(ActionImageCanvas)o; motherApplet=parentCanvas.getApplet();
}
}
public ActionAnimApplet getApplet() { return motherApplet;}
public void checkSequence() { checkSequence=true;}
public void configureObject(String param, String value) throws ConfigurationInvalidParameterException, ConfigurationInvalidValueException { }
public String toString() {
String S="ActionImageAnimation\n";
for (int i=0;i<ActionImages.length;i++) S=S+ActionImages[i].toString(); return S;
}
public void completeConfiguration() throws ConfigurationFailedException {
ActionImages=new ActionImage[loadingActionImages.size()]; loadingActionImages.copyInto(ActionImages);
if (checkSequence) orderActionImages();
}
public void orderActionImages() { ActionImages[ActionImages.length-1].setNext(0); for (int i=0;i<ActionImages.length-1;i++)
ActionImages[i].setNext(i+1);
}
public void startAnimation() {
if (curActionImage==null) curActionImage=ActionImages[0]; animator=new Thread(this);
animator.start();} public void stopAnimation() {
animator.stop();} public void run() {
while(animator==Thread.currentThread()) {
MediaTracker tracker=motherApplet.mediaTrackerHandle();
if ((tracker.statusAll(true) & MediaTracker.ERRORED) !=0) { System.out.println("Одно или больше изображений не
загрузились");
return;}
//if ((tracker.statusAll(true) & MediaTracker.COMPLETE)!=0) //{
parentCanvas.removeImageFromCanvas(curActionImage);
parentCanvas.removeAreaFromCanvas(curActionImage); // первый переход совершен
www.books-shop.com
int indx=curActionImage.next(); curActionImage=ActionImages[indx]; if(!(tracker.checkAll())) {
if (!(tracker.checkID(curActionImage.priority(),true))) { try {
animator.sleep(10);}
catch (InterruptedException e) {break;} continue;}
}
else {
curActionImage.updateValues();
parentCanvas.addToCanvas(curActionImage);
parentCanvas.updateCanvas();
try {
animator.sleep(curActionImage.pause()); }catch (InterruptedException e) {
break;}
}
}
}
private protected ActionImage getNextActionImage(int i) { MediaTracker tracker=motherApplet.mediaTrackerHandle(); int nextAI=curActionImage.next(); if((tracker.statusAll(true) & MediaTracker.COMPLETE) !=0)
return ActionImages[nextAI]; else return curActionImage;}
public void setApplet(ActionAnimApplet ap) { motherApplet=ap;}
}
Нам осталось написать только два базовых класса. Первый - класс ActionImage. В самой простой форме он по требованию берет свое изображение и передает события классу ActionImageArea. Как уже говорилось выше, в ActionImage можно создать подкласс, чтобы создавать изображения на стороне клиента и, таким образом, не зависеть от скорости передачи данных по сети:
package ventana.aia; import java.util.*; import java.net.*; import java.awt.*; import java.applet.*; import ventana.util.*;
public class ActionImage implements Configurable{ private Image thisImage;
private URL imageURL; private int width=0; private int height=0; private int x=0; private int y=0; private int next=-1;
private int imagePriority=0; private int pause=100;
private ActionImageAnimation animParent; private ActionAnimApplet motherApplet; private ActionArea actionAreas[]; private Vector loadingAreas=new Vector(); public int XPos() {return x;}
public int YPos() {return y;} public Rectangle getBoundingRect() {
return new Rectangle(x,y,width,height);} public int pause() {return pause;}
public void attachObject(Object o) throws ConfigurationAttachmentException { if(!(o instanceof ActionArea))
www.books-shop.com
throw (new ConfigurationAttachmentException ("not action area"));
ActionArea curActionArea=(ActionArea)o; loadingAreas.addElement(curActionArea); curActionArea.setParent(this);
}
public void configureObject(String param, String value) throws ConfigurationInvalidParameterException, ConfigurationInvalidValueException {
boolean paramHandled=false; param=param.toLowerCase(); value=value.toLowerCase();
if (param.equals("image")) { try {
URL docBase=motherApplet.getDocumentBase(); imageURL=new URL(docBase,value); paramHandled=loadImage(value);}
catch(MalformedURLException e) {
throw (new ConfigurationInvalidValueException("not a URL: +value));}
}
try {
if (param.equals("priority")) { imagePriority=Integer.parseInt(value); paramHandled=true;}
if (param.equals("width")) { width=Integer.parseInt(value); paramHandled=true;}
if (param.equals("height")) { height=Integer.parseInt(value); paramHandled=true;}
if (param.equals("x")) { x=Integer.parseInt(value); paramHandled=true;}
if (param.equals("y")) { y=Integer.parseInt(value); paramHandled=true;}
if (param.equals("next")) { next=Integer.parseInt(value); paramHandled=true;}
if (param.equals("pause")) { pause=Integer.parseInt(value); paramHandled=true;}
} catch (NumberFormatException e) {
throw (new ConfigurationInvalidValueException (e.getMessage()));}
if (!paramHandled) { |
ConfigurationInvalidParameterException |
throw (new |
|
(param));} |
|
} |
|
public String toString() { String S="ActionImage\n"; S=S+"width="+width+"\n";
S=S+"height="+height+"\n";
S=S+"next="+next+"\n";
for (int i=0;i<actionAreas.length;i++) S=S+actionAreas[i].toString();
return S;}
public void completeConfiguration() throws ConfigurationFailedException {
MediaTracker mT=motherApplet.mediaTrackerHandle(); actionAreas=new ActionArea[loadingAreas.size()]; loadingAreas.copyInto(actionAreas);
if (imageURL==null) throw (new ConfigurationFailedException("no image URL"));
www.books-shop.com
else
thisImage=motherApplet.getImage(imageURL); if (next==-1) animParent.checkSequence();
if (width>0 && height>0) mT.addImage(thisImage,imagePriority,width,height);
else {
if (width>0 && height<=0) throw(new ConfigurationFailedException
("width specified, but not height")); if (height>0 && width<=0) throw(new
ConfigurationFailedException ("height specified, but not width"));
}
mT.addImage(thisImage,imagePriority);
mT.checkID(imagePriority,true);
// проверяет, что MediaTracker берет нужное изображение
}
На этом мы закончили работу с классом ActionArea. Класс ActionImage уже выяснил тип события и вызывает нужное событие, непосредственно работающее с методом:
package ventana.aia; import java.applet.*; import java.awt.*; import java.util.*; import ventana.util.*;
public class ActionArea implements Configurable { private Polygon thisArea;
private protected ActionAnimApplet motherApplet; private ActionImage parentActionImage;
public void setParent(Object o) { parentActionImage=(ActionImage)o;
motherApplet=parentActionImage.getApplet();} public void attachObject(Object o) throws
ConfigurationAttachmentException
{throw (new ConfigurationAttachmentException("Can't attach"));} public void configureObject(String param, String value) throws ConfigurationInvalidParameterException, ConfigurationInvalidValueException {
param=param.toLowerCase();
if(param.equals("area"))
parseArea(value); else throw (new
ConfigurationInvalidParameterException (param));
}
public String toString() { String S="ActionArea\n"; S=S+thisArea.toString(); return S;}
public void completeConfiguration() throws ConfigurationFailedException {
if (thisArea==null)
throw (new ConfigurationFailedException ("no area described",false));}
private void parseArea(String value) throws ConfigurationInvalidValueException { StringTokenizer sT=new StringTokenizer(value,";"); Vector pairs=new Vector();
while (sT.hasMoreTokens()) pairs.addElement(sT.nextToken());
if (pairs.size()==0) throw (new ConfigurationInvalidValueException
www.books-shop.com
("no pairs found")); for (int i=0;i<pairs.size();i++) {
String thisPair=(String)pairs.elementAt(i); StringTokenizer st2=new StringTokenizer(thisPair,",");
if (st2.countTokens()!=2) throw (new ConfigurationInvalidValueException ("invalid pair: "+thisPair));
String xAsString=st2.nextToken();
String yAsString=st2.nextToken(); try {
int thisX=Integer.parseInt(xAsString); int thisY=Integer.parseInt(yAsString); if (thisArea==null)
thisArea=new Polygon(); thisArea.addPoint(thisX,thisY);} catch (NumberFormatException e) { throw (new
ConfigurationInvalidValueException
("no numbers in pair: "+thisPair, false));
}
}
}
public boolean inside(int x, int y) { return
(thisArea!=null?thisArea.inside(x,y):false);} public boolean mouseMove(int x, int y,Graphics g) {
motherApplet.showStatus ("mouse moved: "+x+","+y); return false;
}
public boolean mouseDown(int x, int y,Graphics g) { motherApplet.showStatus
("mouse down: "+x+","+y); return false;}
public boolean mouseUp(int x, int y,Graphics g) { motherApplet.showStatus
("mouse up: "+x+","+y); return false;}
public boolean mouseDrag(int x, int y,Graphics g) { motherApplet.showStatus
("mouse drag: "+x+","+y); return false;}
}
Теперь мы заполнили все базовые классы нашей системы. Как уже было сказано, во всех классах можно создать подклассы для включения новых функций - мы написали классы, которые знают друг о друге.
Пора начать компоновать основную систему выполнения. Первым шагом будет создание иерархии исключений. Как вы помните из главы 10, мы просто переопределяем конструктор и передаем исключению сообщение, объясняющее, что случилось. Мы не будем здесь обсуждать текст программы для исключений - вы можете найти эту программу на диске CD-ROM или странице Online Companion. После обработки исключений следующим шагом будет создание анализатора. Затем мы реализуем подклассы ActionArea.
Создание анализатора
Прежде чем мы начнем работать с апплетом, нужно создать анализатор. Как мы говорили при написании класса Configuration, задача анализатора - проверять правильность конфигурационного файла и передавать информацию объекту Configuration. Кроме того, анализатор должен интерпретировать исключения, которые может выдавать объект Configuration, и создавать понятные сообщения.
Благодаря структуре нашего языка анализатор должен просмотреть документ только один раз.
www.books-shop.com
Главное, что здесь требуется, - это перевод тегов в переменные, которые можно передавать объекту Configuration. Кроме того, нам нужно несколько подпрограмм, которые будут выдавать более дружественные для пользователя сообщения, чем сообщения об исключениях. Лучший способ сделать сообщения понятными - это следить за номером строки. Как мы уже говорили, нам еще нужны комментарии и пустые строки:
package ventana.aia; import java.util.*; import java.net.*; import java.io.*; import ventana.util.*;
public class AIAnimParser extends ConfFileParser { private DataInputStream conf;
private int lineNumber=1;
private Stack tokenStack=new Stack();
private final String packageName="ventana.aia."; private final String beginClassToken="<"; private final String endClassToken=">";
private final String classNameDescript="type"; private final String closureToken="</"; private final String assignString="="; private final String commentChar="#";
int hierarchyDepth=0;
final String validClasses[]={"ActionAnimApplet","ActionImageCanvas", "ActionImageAnimation","ActionImage","ActionArea"};
public AIAnimParser(URL U) throws IOException{ conf=new DataInputStream(U.openStream());} public boolean start() {
try {
while(conf.available()>0) { String curLine=conf.readLine(); if (!parseLine(curLine)) {
messageError("Parsing stopped"); return false;}
lineNumber++;}
} catch(IOException e) {
messageError("Error reading configuration file",e);
}
return true;
}
public boolean parseLine(String line) { line=line.trim();
// вырезанное пустое место
if (line.startsWith(commentChar) || line.length()==0)
return true;
if (line.startsWith(closureToken)) {return closeCurrentToken(line);
}
if (line.startsWith(beginClassToken)) {return parseToken(line);
}
//передаем param/value
//текущему объекту passParam(line); return true;
//Если value не распознано, не прекращаем анализ.
//Вызов для завершения конфигурации на closureToken вызовет ошибки.
}
public void passParam(String line) {
StringTokenizer splitter=new StringTokenizer(line,assignString); String param=splitter.nextToken();
String value=splitter.nextToken();
try {
www.books-shop.com
ConfigureObject.passToCurrent(param,value);} catch (ConfigurationInvalidParameterException e) { messageError("invalid parameter",e,curClass());
}
catch (ConfigurationInvalidValueException e) {messageError("invalid value",e,curClass());}
}
public boolean parseToken(String token) { int start=beginClassToken.length();
int end=token.length()- endClassToken.length(); token=token.substring(start,end);
StringTokenizer splitter=new StringTokenizer(token,"="); String baseClassName=splitter.nextToken();
String subClassName=splitter.nextToken(); if (subClassName==null) { subClassName=baseClassName;}
if (baseClassName==null){ messageError("No class specified"); return false;}
else return configureNewClass(baseClassName,subClassName);
}
private boolean configureNewClass(String base, String sub) { if (hierarchyDepth==validClasses.length)
{messageError("Can't attach to "+base); return false;}
String nextBase=validClasses[hierarchyDepth+1]; if (!nextBase.equals(base))
{messageError("Can't deal with "+base); return false;}
try {
sub=packageName+sub;
ConfigureObject.attach(sub);
hierarchyDepth++; return true;
}catch (ConfigurationAttachmentException e){ messageError("Attachment not allowed",e,base);
}catch (IllegalAccessException e) {messageError("access to class not allowed,",e,base);}
catch (InstantiationException e) {messageError("class didn't instnatiate",e,base);}
catch (NotConfigurableException e) {messageError("invalid class for AIA",e,base);}
catch (ClassNotFoundException e) {messageError("class wasn't found",e,base);}
return false;}
public boolean closeCurrentToken(String token)
{
if (token.indexOf(curClass())==-1) { messageError(token+" doesn't match "+curClass()); return false;}
try {
ConfigureObject.configureCurrent();} catch (ConfigurationFailedException e) {
if (e.isTerminal()) {
messageError("configuration not completed",e,curClass()); return false;}
else
messageError("parameter was ignored",e,curClass());
}
hierarchyDepth--;
if (hierarchyDepth<0) { messageError("Internal parser error");
return false;} else return true;
www.books-shop.com
}
private String curClass() {
return validClasses[hierarchyDepth];} public void messageError(String mesg) {
System.out.println("Error at line "+lineNumber+": "+mesg);} public void messageError(String mesg,Exception e) {
messageError(mesg); System.out.print("details: "); System.out.println(e.getMessage());
System.out.println("\n\n*** Java's error message *****");
System.out.println("(Non programmers should ignore)"); e.printStackTrace(); System.out.println("------------\n\n");}
public void messageError(String mesg, Exception e, String className) { System.out.println(className+" reports:"); messageError(mesg,e);}
}
Создание ActionArea
Мы построили анализатор и готовы завершить систему, создав несколько рабочих областей. Все, что нам нужно для этого сделать, - это задать их конфигурацию и действия, которые они должны совершать. Последнее можно сделать, переопределив методы обработки событий мыши в классе ActionArea - mouseDown, mouseUp, mouseDrag и mouseMove. Конфигурация потребует только переопределения метода configureObject. Первый класс, который мы переопределим, просто покажет новую страницу, выдаваемую определенным URL:
package ventana.aia; import java.applet.*; import java.awt.*; import java.util.*; import java.net.*; import ventana.util.*;
public class ShowDocArea extends ActionArea implements Configurable { URL doc;
public void configureObject(String param, String value) throws ConfigurationInvalidParameterException, ConfigurationInvalidValueException {
param=param.toLowerCase();
if(param.equals("doc"))
parseDocURL(value);
else
super.configureObject(param,value);
}
public void parseDocURL(String value) throws ConfigurationInvalidValueException { try {
doc=new URL(value);
}catch (MalformedURLException e) {
throw new ConfigurationInvalidValueException(value+" not a
URL");}
}
public String toString() {
String S="SoundActionArea\n"; S=S+doc.toString()+"\n"; return S;}
public void completeConfiguration() throws ConfigurationFailedException {
if (doc==null)
throw (new ConfigurationFailedException ("no doc described"));}
public boolean mouseUp(int x, int y,Graphics g) { motherApplet.getAppletContext().showDocument(doc);
www.books-shop.com