001    /**
002    * @author Václav Mikolášek
003    * nicklaus@students.zcu.cz
004    */
005    
006    package animace.label;
007    
008    import animace.*;
009    import javax.swing.*;
010    import javax.imageio.*;
011    import java.awt.*;
012    import java.awt.image.*;
013    import java.awt.event.*;
014    import java.io.*;
015    import java.util.*;
016    
017    
018    /**
019     * Třída MoveablePicture poskytuje ukázku, jak by měli vypadat 
020     * grafické komponenty pro třídu animace.label.PictureLabel.
021     * Tedy komponenty pro systém, kde se programátor stará o vykreslování sám.
022     * Podstatný je princip vykreslování. Objekt pri nějaké změně (pohyb) nastaví
023     * proměnou needUpdate na true a volá repaint(). Metoda repaint() je volána s parametry
024     * označující tzv. dirty region. Takto by měla být tato metoda volána pokaždé, jinak dochází často zbytečně
025     * k překreslováni celé obrazovky (panelu).
026     * I když není potřeba objekt překreslit, protože na něm nedošlo ke změně (needUpdate nastaveno na false),
027     * musíme ještě otestovat, zda jej jiný objekt "nepřejel", 
028     * to se zjistí z Clip oblasti nastavené v Graphics g. Viz níže metoda paint(Graphics g).
029     * Dále stojí za povšimnuti, že je implementováno přidávání posluchačů udalostí stejně, jako jsme zvyklí
030     * u klasických komponent, tedy metodou add"Něco"Listener, v tomto případě addActionListener. To je proto,
031     * že lze očekávat, že se na objekty bude klikat. V komponentě, na které tyto objekty visí, je nutné ještě
032     * zajistit, že bude volat metodu processEvent(...) nad tou komponentou, na kterou uživatel kliknul.
033     * Je toho dost co musíme napsat, abychom zajistili základní funkčnost.
034     */
035    public class MoveablePicture implements Moveable, PaintControled {
036            private Component owner;
037            private BufferedImage img;
038            private PaintControl pc = null;
039            private ActionListener actionListener = null;
040            
041            /**
042             * Indikuje, že na objetku nastaly změny, a že je nutné jej znovu překreslit.
043             */
044            public boolean needUpdate = true;
045                    
046            private int x = 0;              // location
047            private int y = 0;              // location
048            private int imgWidth = 0;
049            private int imgHeight = 0;
050                    
051            /**
052             * Konstruktor vyžaduje jako parametr grafického vlastnika (rodiče) objektu,
053             * tedy nejakou Componentu na ktere spočívá.
054             * @param owner Componenta, na které je MoveablePicture nakreslen
055             * @param img vlastní obrázek, který se bude pohybovat
056             */
057            public MoveablePicture(Component owner,BufferedImage img) {
058                    this.owner = owner;
059                    this.img = img;
060                    imgWidth = img.getWidth();
061                    imgHeight = img.getHeight();
062            }
063    
064            /**
065             * Nakreslí tento objekt do grafického kontextu předaného parametrem.
066             * Algortimus je následující. Pokud se s objektem hýbalo a je třeba jej překreslit,
067             * needUpdate == true, pak nic nezmůžeme a musíme jej kreslit. Ale pokud needUpdate == false a 
068             * tento objekt není v "dirty regionu" (g.getClipBounds()) kreslit jej nemusíme a ušetříme čas.
069             */
070            public void paint(Graphics g) {
071                    if (!needUpdate) {
072                            Rectangle toDraw = g.getClipBounds().intersection(new Rectangle(x,y,imgWidth,imgHeight));
073                            if (!toDraw.isEmpty()) {
074                                    g.drawImage(img,x,y,imgWidth,imgHeight,null);
075                            }
076                    }
077                    else {
078                            g.drawImage(img,x,y,imgWidth,imgHeight,null);
079                            needUpdate = false;
080                    }
081            }
082            
083            /**
084             * Vrací obrázek
085             */
086            public BufferedImage getImage() {
087                    return img;
088            }
089            
090            /**
091             * Vrací preferovanou velikost - rozměry obrázku.
092             */
093            public Dimension getPrefferedSize(){
094                    return new Dimension(img.getWidth(),img.getHeight());
095            }
096    
097            /**
098             * Vrací šířku obrázku
099             */
100            public int getWidth() {
101                    return img.getWidth();
102            }
103    
104            /**
105             * Vrací víšku obrázku
106             */
107            public int getHeight() 
108            {
109                    return img.getHeight();
110            }
111            
112            /**
113             * Metoda z rozhraní Moveable. Nastaví novou pozici objektu a hodnotu needUpdate na true.
114             * Poté zavolá owner.repaint(int x, int y, int width, int height), která
115             * zaregistruje požadavek na překreslení pouze té části obrazovky,
116         * kde došlo ke změnám na grafice.
117             * Při zavolani této metody, nedochází k žádným kontrolám,
118             * zda jsou údaje správné
119             */
120            public synchronized void setLocation(int x,int y) {
121                    int left = (this.x < x ? this.x : x) - 1;
122                    int up = (this.y < y ? this.y : y) - 1;
123                    int dx = Math.abs(this.x - x) + 2;
124                    int dy = Math.abs(this.y - y) + 2;
125                    this.x = x;
126                    this.y = y;
127                    needUpdate = true;
128                    owner.repaint(left,up,imgWidth+dx,imgHeight+dy);
129            }
130            
131            /**
132             * Metoda z rozhrani Moveable, Funguje standardně, vrací pozici objektu
133             */
134            public Point getLocation(){
135                    return new Point(x,y);
136            }
137                    
138            /**
139             * Metoda z rozhraní PaintControled
140             */
141            public void setPaintControl(PaintControl pc) {
142                    this.pc = pc;
143            }
144            
145            /**
146             * Metoda z rozhraní PaintControled
147             */
148            public PaintControl getPaintControl() {
149                    return pc;
150            }
151            
152            /**
153             * Metoda addActionListener, jak jsme zvyklí z java.AWT.Component
154             * Zde implementována pomocí třídy AWTEventMulticaster.
155             */
156            public void addActionListener(ActionListener newActionListener) {
157                    actionListener = AWTEventMulticaster.add(actionListener, newActionListener);
158            }
159            
160            /** 
161             * Metoda processEvent, kterou bude volat zejména předek objektu,
162             * v našem případě PictureLabel, provede zavolání metody actionPerformed(...) nad všemi
163             * zaregistrovanými posluchači.
164             */
165            public void processEvent(AWTEvent e) {
166                    if (actionListener != null) {
167                            actionListener.actionPerformed(new ActionEvent(this,0,""));
168                    }
169            }
170            
171            /**
172             * Vrací true, jestliže objekt obsahuje určitý bod, hodi se pri testu na kliknuti
173             * @param point bod, který je testován, zda leží na obrazku
174             */
175            public boolean contains(Point point) {
176                    return (new Rectangle(x,y,imgWidth,imgHeight)).contains(point);
177            }
178                    
179    }
180