001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.painter;
020
021import icy.canvas.IcyCanvas;
022import icy.common.CollapsibleEvent;
023import icy.common.UpdateEventHandler;
024import icy.common.listener.ChangeListener;
025import icy.file.xml.XMLPersistent;
026import icy.gui.viewer.Viewer;
027import icy.main.Icy;
028import icy.painter.OverlayEvent.OverlayEventType;
029import icy.sequence.Sequence;
030import icy.system.IcyExceptionHandler;
031import icy.type.point.Point5D;
032import icy.util.ClassUtil;
033import icy.util.StringUtil;
034import icy.util.XMLUtil;
035
036import java.awt.Graphics2D;
037import java.awt.event.KeyEvent;
038import java.awt.event.MouseEvent;
039import java.awt.event.MouseWheelEvent;
040import java.awt.geom.Point2D;
041import java.lang.reflect.Constructor;
042import java.util.ArrayList;
043import java.util.List;
044
045import javax.swing.JPanel;
046
047import org.w3c.dom.Node;
048
049/**
050 * Overlay class.<br>
051 * <br>
052 * This class allow interaction and rich informations display on Sequences.<br>
053 * {@link IcyCanvas} subclasses should propagate mouse and key events to overlay.
054 * 
055 * @author Stephane
056 */
057@SuppressWarnings("deprecation")
058public abstract class Overlay implements Painter, ChangeListener, Comparable<Overlay>, XMLPersistent
059{
060    /**
061     * Define the overlay priority:
062     * 
063     * <pre>
064     * Lowest   |   BACKGROUND  (below image)
065     *          |   IMAGE       (image level)
066     *          |   SHAPE       (just over the image)
067     *          |   TEXT        (over image and shape)
068     *          |   TOOLTIP     (all over the rest)
069     * Highest  |   TOPMOST     (absolute topmost)
070     * </pre>
071     * 
072     * You have 4 levels for each category (except TOPMOST) for finest adjustment:
073     * 
074     * <pre>
075     * Lowest   |   LOW
076     *          |   NORMAL
077     *          |   HIGH
078     * Highest  |   TOP
079     * </pre>
080     * 
081     * TOP level should be used to give <i>focus<i> to a specific Overlay over all other in the same
082     * category.
083     */
084    public static enum OverlayPriority
085    {
086        BACKGROUND_LOW, BACKGROUND_NORMAL, BACKGROUND_HIGH, BACKGROUND_TOP, IMAGE_LOW, IMAGE_NORMAL, IMAGE_HIGH, IMAGE_TOP, SHAPE_LOW, SHAPE_NORMAL, SHAPE_HIGH, SHAPE_TOP, TEXT_LOW, TEXT_NORMAL, TEXT_HIGH, TEXT_TOP, TOOLTIP_LOW, TOOLTIP_NORMAL, TOOLTIP_HIGH, TOOLTIP_TOP, TOPMOST
087    }
088
089    public static final String ID_OVERLAY = "overlay";
090
091    public static final String ID_CLASSNAME = "classname";
092    public static final String ID_ID = "id";
093    public static final String ID_NAME = "name";
094    public static final String ID_PRIORITY = "priority";
095    public static final String ID_READONLY = "readOnly";
096    public static final String ID_CANBEREMOVED = "canBeRemoved";
097    public static final String ID_RECEIVEKEYEVENTONHIDDEN = "receiveKeyEventOnHidden";
098    public static final String ID_RECEIVEMOUSEEVENTONHIDDEN = "receiveMouseEventOnHidden";
099
100    public static final String PROPERTY_NAME = ID_NAME;
101    public static final String PROPERTY_PRIORITY = ID_PRIORITY;
102    public static final String PROPERTY_READONLY = ID_READONLY;
103    public static final String PROPERTY_PERSISTENT = "persitent";
104    public static final String PROPERTY_CANBEREMOVED = ID_CANBEREMOVED;
105    public static final String PROPERTY_RECEIVEKEYEVENTONHIDDEN = ID_RECEIVEKEYEVENTONHIDDEN;
106    public static final String PROPERTY_RECEIVEMOUSEEVENTONHIDDEN = ID_RECEIVEMOUSEEVENTONHIDDEN;
107
108    /**
109     * We consider as tiny object anything with a size of 10 pixels or less
110     */
111    public static final int LOD_SMALL = 10;
112    public static final int LOD_TINY = 4;
113
114    protected static int id_gen = 1;
115
116    /**
117     * Create a Overlay from a XML node.
118     * 
119     * @param node
120     *        XML node defining the overlay
121     * @return the created Overlay or <code>null</code> if the Overlay class does not support XML
122     *         persistence a default
123     *         constructor
124     */
125    public static Overlay createFromXML(Node node)
126    {
127        if (node == null)
128            return null;
129
130        final String className = XMLUtil.getElementValue(node, ID_CLASSNAME, "");
131        if (StringUtil.isEmpty(className))
132            return null;
133
134        final Overlay result;
135
136        try
137        {
138            // search for the specified className
139            final Class<?> clazz = ClassUtil.findClass(className);
140
141            // class found
142            if (clazz != null)
143            {
144                final Class<? extends Overlay> overlayClazz = clazz.asSubclass(Overlay.class);
145
146                // default constructor
147                final Constructor<? extends Overlay> constructor = overlayClazz.getConstructor(new Class[] {});
148                // build Overlay
149                result = constructor.newInstance();
150
151                // load properties from XML
152                if (result != null)
153                {
154                    // error while loading infos --> return null
155                    if (!result.loadFromXML(node))
156                        return null;
157                }
158
159                return result;
160            }
161        }
162        catch (NoSuchMethodException e)
163        {
164            IcyExceptionHandler.handleException(new NoSuchMethodException("Default constructor not found in class '"
165                    + className + "', cannot create the Overlay."), true);
166        }
167        catch (ClassNotFoundException e)
168        {
169            IcyExceptionHandler.handleException(new ClassNotFoundException("Cannot find '" + className
170                    + "' class, cannot create the Overlay."), true);
171        }
172        catch (Exception e)
173        {
174            IcyExceptionHandler.handleException(e, true);
175        }
176
177        return null;
178    }
179
180    /**
181     * Return the number of Overlay defined in the specified XML node.
182     * 
183     * @param node
184     *        XML node defining the Overlay list
185     * @return the number of Overlay defined in the XML node.
186     */
187    public static int getOverlayCount(Node node)
188    {
189        if (node != null)
190        {
191            final List<Node> nodesOverlay = XMLUtil.getChildren(node, ID_OVERLAY);
192
193            if (nodesOverlay != null)
194                return nodesOverlay.size();
195        }
196
197        return 0;
198    }
199
200    /**
201     * Return a list of Overlay from a XML node.
202     * 
203     * @param node
204     *        XML node defining the Overlay list
205     * @return a list of Overlay
206     */
207    public static List<Overlay> loadOverlaysFromXML(Node node)
208    {
209        final List<Overlay> result = new ArrayList<Overlay>();
210
211        if (node != null)
212        {
213            final List<Node> nodesOverlay = XMLUtil.getChildren(node, ID_OVERLAY);
214
215            if (nodesOverlay != null)
216            {
217                for (Node n : nodesOverlay)
218                {
219                    final Overlay overlay = createFromXML(n);
220
221                    if (overlay != null)
222                    {
223                        // we assume this overlay should stay persistent then
224                        overlay.setPersistent(true);
225                        result.add(overlay);
226                    }
227                }
228            }
229        }
230
231        return result;
232    }
233
234    /**
235     * Set a list of Overlay to a XML node.
236     * 
237     * @param node
238     *        XML node which is used to store the list of Overlay
239     * @param overlays
240     *        the list of Overlay to store in the XML node
241     */
242    public static void saveOverlaysToXML(Node node, List<Overlay> overlays)
243    {
244        if (node != null)
245        {
246            for (Overlay overlay : overlays)
247            {
248                // only save persistent overlay
249                if (overlay.isPersistent())
250                {
251                    final Node nodeOverlay = XMLUtil.addElement(node, ID_OVERLAY);
252
253                    if (!overlay.saveToXML(nodeOverlay))
254                    {
255                        XMLUtil.removeNode(node, nodeOverlay);
256                        System.err.println("Error: the overlay " + overlay.getName()
257                                + " was not correctly saved to XML !");
258                    }
259                }
260            }
261        }
262    }
263
264    /**
265     * properties
266     */
267    protected int id;
268    protected String name;
269    protected OverlayPriority priority;
270    protected boolean persistent;
271    protected boolean readOnly;
272    protected boolean canBeRemoved;
273    protected boolean receiveKeyEventOnHidden;
274    protected boolean receiveMouseEventOnHidden;
275
276    /**
277     * internals
278     */
279    protected final List<OverlayListener> listeners;
280    protected final UpdateEventHandler updater;
281
282    public Overlay(String name, OverlayPriority priority)
283    {
284        super();
285
286        synchronized (Overlay.class)
287        {
288            id = id_gen++;
289        }
290
291        this.name = name;
292        this.priority = priority;
293        // by default the overlay is not persistent
294        persistent = false;
295        readOnly = false;
296        canBeRemoved = true;
297        receiveKeyEventOnHidden = false;
298        receiveMouseEventOnHidden = false;
299
300        listeners = new ArrayList<OverlayListener>();
301        updater = new UpdateEventHandler(this, false);
302    }
303
304    public Overlay(String name)
305    {
306        // create overlay with default priority
307        this(name, OverlayPriority.SHAPE_NORMAL);
308    }
309
310    /**
311     * @return the name
312     */
313    public String getName()
314    {
315        return name;
316    }
317
318    /**
319     * @param name
320     *        the name to set
321     */
322    public void setName(String name)
323    {
324        if (this.name != name)
325        {
326            this.name = name;
327            propertyChanged(PROPERTY_NAME);
328        }
329    }
330
331    /**
332     * @return the priority
333     */
334    public OverlayPriority getPriority()
335    {
336        return priority;
337    }
338
339    /**
340     * @param priority
341     *        the priority to set
342     */
343    public void setPriority(OverlayPriority priority)
344    {
345        if (this.priority != priority)
346        {
347            this.priority = priority;
348            propertyChanged(PROPERTY_PRIORITY);
349        }
350    }
351
352    /**
353     * Returns <code>true</code> if the overlay is attached to the specified {@link Sequence}.
354     */
355    public boolean isAttached(Sequence sequence)
356    {
357        if (sequence != null)
358            return sequence.contains(this);
359
360        return false;
361    }
362
363    /**
364     * @deprecated Use {@link #getCanBeRemoved()} instead.
365     * @see #setCanBeRemoved(boolean)
366     */
367    @Deprecated
368    public boolean isFixed()
369    {
370        return !getCanBeRemoved();
371    }
372
373    /**
374     * @deprecated Use {@link #setCanBeRemoved(boolean)} instead.
375     */
376    @Deprecated
377    public void setFixed(boolean value)
378    {
379        setCanBeRemoved(!value);
380    }
381
382    /**
383     * Returns <code>true</code> if the overlay can be freely removed from the Canvas where it
384     * appears and <code>false</code> otherwise.<br/>
385     * 
386     * @see #setCanBeRemoved(boolean)
387     */
388    public boolean getCanBeRemoved()
389    {
390        return canBeRemoved;
391    }
392
393    /**
394     * Set the <code>canBeRemoved</code> property.<br/>
395     * Set it to false if you want to prevent the overlay to be removed from the Canvas where it
396     * appears.
397     */
398    public void setCanBeRemoved(boolean value)
399    {
400        if (canBeRemoved != value)
401        {
402            canBeRemoved = value;
403            propertyChanged(PROPERTY_CANBEREMOVED);
404        }
405    }
406
407    /**
408     * Return persistent property.<br/>
409     * When set to <code>true</code> the Overlay will be saved in the Sequence persistent XML data.
410     */
411    public boolean isPersistent()
412    {
413        return persistent;
414    }
415
416    /**
417     * Set persistent property.<br/>
418     * When set to <code>true</code> the Overlay will be saved in the Sequence persistent XML data
419     * (default is <code>false</code>).
420     */
421    public void setPersistent(boolean value)
422    {
423        if (persistent != value)
424        {
425            persistent = value;
426            propertyChanged(PROPERTY_PERSISTENT);
427        }
428    }
429
430    /**
431     * Return read only property.<br/>
432     * When set to <code>true</code> we cannot anymore modify overlay properties from the GUI.
433     */
434    public boolean isReadOnly()
435    {
436        return readOnly;
437    }
438
439    /**
440     * Set read only property.<br/>
441     * When set to <code>true</code> we cannot anymore modify overlay properties from the GUI.
442     */
443    public void setReadOnly(boolean value)
444    {
445        if (readOnly != value)
446        {
447            readOnly = value;
448            propertyChanged(PROPERTY_READONLY);
449        }
450    }
451
452    /**
453     * @return <code>true</code> is the overlay should receive {@link KeyEvent} even when it is not
454     *         visible.
455     */
456    public boolean getReceiveKeyEventOnHidden()
457    {
458        return receiveKeyEventOnHidden;
459    }
460
461    /**
462     * Set to <code>true</code> if you want to overlay to receive {@link KeyEvent} even when it is
463     * not visible.
464     */
465    public void setReceiveKeyEventOnHidden(boolean value)
466    {
467        if (receiveKeyEventOnHidden != value)
468        {
469            receiveKeyEventOnHidden = value;
470            propertyChanged(PROPERTY_RECEIVEKEYEVENTONHIDDEN);
471        }
472    }
473
474    /**
475     * @return <code>true</code> is the overlay should receive {@link MouseEvent} even when it is
476     *         not visible.
477     */
478    public boolean getReceiveMouseEventOnHidden()
479    {
480        return receiveMouseEventOnHidden;
481    }
482
483    /**
484     * Set to <code>true</code> if you want to overlay to receive {@link KeyEvent} even when it is
485     * not visible.
486     */
487    public void setReceiveMouseEventOnHidden(boolean value)
488    {
489        if (receiveMouseEventOnHidden != value)
490        {
491            receiveMouseEventOnHidden = value;
492            propertyChanged(PROPERTY_RECEIVEMOUSEEVENTONHIDDEN);
493        }
494    }
495
496    /**
497     * Override this method to provide an extra options panel for the overlay.<br>
498     * The options panel will appears in the inspector when the layer's overlay is selected.
499     */
500    public JPanel getOptionsPanel()
501    {
502        return null;
503    }
504
505    /**
506     * @deprecated Use {@link Sequence#addOverlay(Overlay)} instead.
507     */
508    @Deprecated
509    public void attachTo(Sequence sequence)
510    {
511        if (sequence != null)
512            sequence.addOverlay(this);
513    }
514
515    /**
516     * @deprecated Use {@link Sequence#removeOverlay(Overlay)} instead.
517     */
518    @Deprecated
519    public void detachFrom(Sequence sequence)
520    {
521        if (sequence != null)
522            sequence.removeOverlay(this);
523    }
524
525    /**
526     * Remove the Overlay from all sequences and canvas where it is currently attached.
527     */
528    public void remove()
529    {
530        for (Sequence sequence : getSequences())
531            sequence.removeOverlay(this);
532        for (IcyCanvas canvas : getAttachedCanvas())
533            canvas.removeLayer(this);
534    }
535
536    /**
537     * Returns all sequences where the overlay is currently attached.
538     */
539    public List<Sequence> getSequences()
540    {
541        return Icy.getMainInterface().getSequencesContaining(this);
542    }
543
544    /**
545     * Returns all canvas where the overlay is currently present as a layer.
546     */
547    public List<IcyCanvas> getAttachedCanvas()
548    {
549        final List<IcyCanvas> result = new ArrayList<IcyCanvas>();
550
551        for (Viewer viewer : Icy.getMainInterface().getViewers())
552        {
553            final IcyCanvas canvas = viewer.getCanvas();
554
555            if ((canvas != null) && canvas.hasLayer(this))
556                result.add(canvas);
557        }
558
559        return result;
560    }
561
562    public void beginUpdate()
563    {
564        updater.beginUpdate();
565    }
566
567    public void endUpdate()
568    {
569        updater.endUpdate();
570    }
571
572    public boolean isUpdating()
573    {
574        return updater.isUpdating();
575    }
576
577    /**
578     * @deprecated Use {@link #painterChanged()} instead.
579     */
580    @Deprecated
581    public void changed()
582    {
583        painterChanged();
584    }
585
586    /**
587     * Notify the painter content has changed.<br>
588     * All sequence containing the overlay will be repainted to reflect the change.
589     */
590    public void painterChanged()
591    {
592        updater.changed(new OverlayEvent(this, OverlayEventType.PAINTER_CHANGED));
593    }
594
595    /**
596     * Notify the overlay property has changed.
597     */
598    public void propertyChanged(String propertyName)
599    {
600        updater.changed(new OverlayEvent(this, OverlayEventType.PROPERTY_CHANGED, propertyName));
601    }
602
603    @Override
604    public void onChanged(CollapsibleEvent object)
605    {
606        fireOverlayChangedEvent((OverlayEvent) object);
607    }
608
609    protected void fireOverlayChangedEvent(OverlayEvent event)
610    {
611        for (OverlayListener listener : new ArrayList<OverlayListener>(listeners))
612            listener.overlayChanged(event);
613    }
614
615    /**
616     * Add a listener.
617     */
618    public void addOverlayListener(OverlayListener listener)
619    {
620        listeners.add(listener);
621    }
622
623    /**
624     * Remove a listener.
625     */
626    public void removeOverlayListener(OverlayListener listener)
627    {
628        listeners.remove(listener);
629    }
630
631    /**
632     * Paint method called to draw the overlay.
633     */
634    @Override
635    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
636    {
637        // nothing by default
638    }
639
640    /**
641     * @deprecated Use {@link #mousePressed(MouseEvent, Point5D.Double, IcyCanvas)} instead
642     */
643    @Deprecated
644    @Override
645    public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
646    {
647        // no action by default
648    }
649
650    /**
651     * @deprecated Use {@link #mouseReleased(MouseEvent, Point5D.Double, IcyCanvas)} instead
652     */
653    @Deprecated
654    @Override
655    public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
656    {
657        // no action by default
658    }
659
660    /**
661     * @deprecated Use {@link #mouseClick(MouseEvent, Point5D.Double, IcyCanvas)} instead
662     */
663    @Deprecated
664    @Override
665    public void mouseClick(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
666    {
667        // no action by default
668    }
669
670    /**
671     * @deprecated Use {@link #mouseMove(MouseEvent, Point5D.Double, IcyCanvas)} instead
672     */
673    @Deprecated
674    @Override
675    public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
676    {
677        // no action by default
678    }
679
680    /**
681     * @deprecated Use {@link #mouseDrag(MouseEvent, Point5D.Double, IcyCanvas)} instead
682     */
683    @Deprecated
684    @Override
685    public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
686    {
687        // no action by default
688    }
689
690    /**
691     * @deprecated Use {@link #mouseEntered(MouseEvent, Point5D.Double, IcyCanvas)} instead
692     */
693    @Deprecated
694    public void mouseEntered(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
695    {
696        // no action by default
697    }
698
699    /**
700     * @deprecated Use {@link #mouseExited(MouseEvent, Point5D.Double, IcyCanvas)} instead
701     */
702    @Deprecated
703    public void mouseExited(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
704    {
705        // no action by default
706    }
707
708    /**
709     * @deprecated Use {@link #mouseWheelMoved(MouseWheelEvent, Point5D.Double, IcyCanvas)} instead
710     */
711    @Deprecated
712    public void mouseWheelMoved(MouseWheelEvent e, Point2D imagePoint, IcyCanvas canvas)
713    {
714        // no action by default
715    }
716
717    /**
718     * @deprecated Use {@link #keyPressed(KeyEvent, Point5D.Double, IcyCanvas)} instead
719     */
720    @Deprecated
721    @Override
722    public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
723    {
724        // no action by default
725    }
726
727    /**
728     * @deprecated Use {@link #keyReleased(KeyEvent, Point5D.Double, IcyCanvas)} instead
729     */
730    @Deprecated
731    @Override
732    public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
733    {
734        // no action by default
735    }
736
737    /**
738     * Mouse press event forwarded to the overlay.
739     * 
740     * @param e
741     *        mouse event
742     * @param imagePoint
743     *        mouse position (image coordinates)
744     * @param canvas
745     *        icy canvas
746     */
747    public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
748    {
749        // provide backward compatibility
750        if (imagePoint != null)
751            mousePressed(e, imagePoint.toPoint2D(), canvas);
752        else
753            mousePressed(e, (Point2D) null, canvas);
754    }
755
756    /**
757     * Mouse release event forwarded to the overlay.
758     * 
759     * @param e
760     *        mouse event
761     * @param imagePoint
762     *        mouse position (image coordinates)
763     * @param canvas
764     *        icy canvas
765     */
766    public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
767    {
768        // provide backward compatibility
769        if (imagePoint != null)
770            mouseReleased(e, imagePoint.toPoint2D(), canvas);
771        else
772            mouseReleased(e, (Point2D) null, canvas);
773    }
774
775    /**
776     * Mouse click event forwarded to the overlay.
777     * 
778     * @param e
779     *        mouse event
780     * @param imagePoint
781     *        mouse position (image coordinates)
782     * @param canvas
783     *        icy canvas
784     */
785    public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
786    {
787        // provide backward compatibility
788        if (imagePoint != null)
789            mouseClick(e, imagePoint.toPoint2D(), canvas);
790        else
791            mouseClick(e, (Point2D) null, canvas);
792    }
793
794    /**
795     * Mouse move event forwarded to the overlay.
796     * 
797     * @param e
798     *        mouse event
799     * @param imagePoint
800     *        mouse position (image coordinates)
801     * @param canvas
802     *        icy canvas
803     */
804    public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
805    {
806        // provide backward compatibility
807        if (imagePoint != null)
808            mouseMove(e, imagePoint.toPoint2D(), canvas);
809        else
810            mouseMove(e, (Point2D) null, canvas);
811    }
812
813    /**
814     * Mouse drag event forwarded to the overlay.
815     * 
816     * @param e
817     *        mouse event
818     * @param imagePoint
819     *        mouse position (image coordinates)
820     * @param canvas
821     *        icy canvas
822     */
823    public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
824    {
825        // provide backward compatibility
826        if (imagePoint != null)
827            mouseDrag(e, imagePoint.toPoint2D(), canvas);
828        else
829            mouseDrag(e, (Point2D) null, canvas);
830    }
831
832    /**
833     * Mouse enter event forwarded to the overlay.
834     * 
835     * @param e
836     *        mouse event
837     * @param imagePoint
838     *        mouse position (image coordinates)
839     * @param canvas
840     *        icy canvas
841     */
842    public void mouseEntered(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
843    {
844        // provide backward compatibility
845        if (imagePoint != null)
846            mouseEntered(e, imagePoint.toPoint2D(), canvas);
847        else
848            mouseEntered(e, (Point2D) null, canvas);
849    }
850
851    /**
852     * Mouse exit event forwarded to the overlay.
853     * 
854     * @param e
855     *        mouse event
856     * @param imagePoint
857     *        mouse position (image coordinates)
858     * @param canvas
859     *        icy canvas
860     */
861    public void mouseExited(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
862    {
863        // provide backward compatibility
864        if (imagePoint != null)
865            mouseExited(e, imagePoint.toPoint2D(), canvas);
866        else
867            mouseExited(e, (Point2D) null, canvas);
868    }
869
870    /**
871     * Mouse wheel moved event forwarded to the overlay.
872     * 
873     * @param e
874     *        mouse event
875     * @param imagePoint
876     *        mouse position (image coordinates)
877     * @param canvas
878     *        icy canvas
879     */
880    public void mouseWheelMoved(MouseWheelEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
881    {
882        // provide backward compatibility
883        if (imagePoint != null)
884            mouseWheelMoved(e, imagePoint.toPoint2D(), canvas);
885        else
886            mouseWheelMoved(e, (Point2D) null, canvas);
887    }
888
889    /**
890     * Key press event forwarded to the overlay.
891     * 
892     * @param e
893     *        key event
894     * @param imagePoint
895     *        mouse position (image coordinates)
896     * @param canvas
897     *        icy canvas
898     */
899    public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
900    {
901        // provide backward compatibility
902        if (imagePoint != null)
903            keyPressed(e, imagePoint.toPoint2D(), canvas);
904        else
905            keyPressed(e, (Point2D) null, canvas);
906    }
907
908    /**
909     * Key release event forwarded to the overlay.
910     * 
911     * @param e
912     *        key event
913     * @param imagePoint
914     *        mouse position (image coordinates)
915     * @param canvas
916     *        icy canvas
917     */
918    public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
919    {
920        // provide backward compatibility
921        if (imagePoint != null)
922            keyReleased(e, imagePoint.toPoint2D(), canvas);
923        else
924            keyReleased(e, (Point2D) null, canvas);
925    }
926
927    public boolean loadFromXML(Node node, boolean preserveId)
928    {
929        if (node == null)
930            return false;
931
932        beginUpdate();
933        try
934        {
935            // FIXME : this can make duplicate id but it is also important to preserve id
936            if (!preserveId)
937            {
938                id = XMLUtil.getElementIntValue(node, ID_ID, 0);
939                synchronized (Overlay.class)
940                {
941                    // avoid having same id
942                    if (id_gen <= id)
943                        id_gen = id + 1;
944                }
945            }
946            setName(XMLUtil.getElementValue(node, ID_NAME, ""));
947            setPriority(OverlayPriority.values()[XMLUtil.getElementIntValue(node, ID_PRIORITY,
948                    OverlayPriority.SHAPE_NORMAL.ordinal())]);
949            setReadOnly(XMLUtil.getElementBooleanValue(node, ID_READONLY, false));
950            setReceiveKeyEventOnHidden(XMLUtil.getElementBooleanValue(node, ID_RECEIVEKEYEVENTONHIDDEN, false));
951            setReceiveMouseEventOnHidden(XMLUtil.getElementBooleanValue(node, ID_RECEIVEMOUSEEVENTONHIDDEN, false));
952        }
953        finally
954        {
955            endUpdate();
956        }
957
958        return true;
959    }
960
961    @Override
962    public boolean loadFromXML(Node node)
963    {
964        return loadFromXML(node, false);
965    }
966
967    @Override
968    public boolean saveToXML(Node node)
969    {
970        if (node == null)
971            return false;
972
973        XMLUtil.setElementValue(node, ID_CLASSNAME, getClass().getName());
974        XMLUtil.setElementIntValue(node, ID_ID, id);
975        XMLUtil.setElementValue(node, ID_NAME, getName());
976        XMLUtil.setElementIntValue(node, ID_PRIORITY, getPriority().ordinal());
977        XMLUtil.setElementBooleanValue(node, ID_READONLY, isReadOnly());
978        XMLUtil.setElementBooleanValue(node, ID_RECEIVEKEYEVENTONHIDDEN, getReceiveKeyEventOnHidden());
979        XMLUtil.setElementBooleanValue(node, ID_RECEIVEMOUSEEVENTONHIDDEN, getReceiveMouseEventOnHidden());
980
981        return true;
982    }
983
984    @Override
985    public int compareTo(Overlay o)
986    {
987        // highest priority first
988        return o.priority.ordinal() - priority.ordinal();
989    }
990}