Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
160
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

Мы снова разрешаем множественные экземпляры следующего члена иерархии 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