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.canvas.IcyCanvas2D;
023import icy.common.CollapsibleEvent;
024import icy.painter.OverlayEvent.OverlayEventType;
025import icy.painter.PainterEvent.PainterEventType;
026import icy.sequence.Sequence;
027import icy.system.thread.ThreadUtil;
028import icy.type.point.Point5D;
029import icy.util.EventUtil;
030import icy.util.ShapeUtil;
031import icy.util.XMLUtil;
032import icy.vtk.IcyVtkPanel;
033
034import java.awt.BasicStroke;
035import java.awt.Color;
036import java.awt.Graphics2D;
037import java.awt.event.InputEvent;
038import java.awt.event.KeyEvent;
039import java.awt.event.MouseEvent;
040import java.awt.geom.Ellipse2D;
041import java.awt.geom.Point2D;
042import java.lang.ref.WeakReference;
043import java.util.ArrayList;
044import java.util.Arrays;
045import java.util.EventListener;
046import java.util.List;
047
048import org.w3c.dom.Node;
049
050import plugins.kernel.canvas.VtkCanvas;
051import vtk.vtkActor;
052import vtk.vtkInformation;
053import vtk.vtkPolyDataMapper;
054import vtk.vtkProp;
055import vtk.vtkSphereSource;
056
057/**
058 * Anchor2D class, used for 2D point control.
059 * 
060 * @author Stephane
061 */
062public class Anchor2D extends Overlay implements VtkPainter, Runnable
063{
064    /**
065     * Interface to listen Anchor2D position change
066     */
067    public static interface Anchor2DPositionListener extends EventListener
068    {
069        public void positionChanged(Anchor2D source);
070    }
071
072    /**
073     * @deprecated Use {@link Anchor2DPositionListener} listener or {@link OverlayListener}
074     */
075    @Deprecated
076    public static interface Anchor2DListener extends PainterListener
077    {
078        public void positionChanged(Anchor2D source);
079    }
080
081    public static class Anchor2DEvent implements CollapsibleEvent
082    {
083        private final Anchor2D source;
084
085        public Anchor2DEvent(Anchor2D source)
086        {
087            super();
088
089            this.source = source;
090        }
091
092        /**
093         * @return the source
094         */
095        public Anchor2D getSource()
096        {
097            return source;
098        }
099
100        @Override
101        public boolean collapse(CollapsibleEvent event)
102        {
103            if (equals(event))
104            {
105                // nothing to do here
106                return true;
107            }
108
109            return false;
110        }
111
112        @Override
113        public int hashCode()
114        {
115            return source.hashCode();
116        }
117
118        @Override
119        public boolean equals(Object obj)
120        {
121            if (obj instanceof Anchor2DEvent)
122            {
123                final Anchor2DEvent event = (Anchor2DEvent) obj;
124
125                return (event.getSource() == source);
126            }
127
128            return super.equals(obj);
129        }
130    }
131
132    protected static final String ID_COLOR = "color";
133    protected static final String ID_SELECTEDCOLOR = "selected_color";
134    protected static final String ID_SELECTED = "selected";
135    protected static final String ID_POS_X = "pos_x";
136    protected static final String ID_POS_Y = "pos_y";
137    protected static final String ID_RAY = "ray";
138    protected static final String ID_VISIBLE = "visible";
139
140    public static final int DEFAULT_RAY = 6;
141    public static final Color DEFAULT_NORMAL_COLOR = Color.YELLOW;
142    public static final Color DEFAULT_SELECTED_COLOR = Color.WHITE;
143
144    public static final String PROPERTY_COLOR = ID_COLOR;
145    public static final String PROPERTY_SELECTEDCOLOR = ID_SELECTEDCOLOR;
146    public static final String PROPERTY_SELECTED = ID_SELECTED;
147    public static final String PROPERTY_RAY = ID_RAY;
148
149    /**
150     * position (canvas)
151     */
152    protected final Point2D.Double position;
153    /**
154     * position Z (default = -1 = ALL)
155     */
156    protected int z;
157
158    /**
159     * radius
160     */
161    protected int ray;
162
163    /**
164     * color
165     */
166    protected Color color;
167    /**
168     * selection color
169     */
170    protected Color selectedColor;
171    /**
172     * selection flag
173     */
174    protected boolean selected;
175    /**
176     * flag that indicate if anchor is visible
177     */
178    protected boolean visible;
179
180    /**
181     * internals
182     */
183    protected final Ellipse2D ellipse;
184    protected Point2D startDragMousePosition;
185    protected Point2D startDragPainterPosition;
186
187    // VTK 3D objects
188    vtkSphereSource vtkSource;
189    protected vtkPolyDataMapper polyMapper;
190    protected vtkActor actor;
191    protected vtkInformation vtkInfo;
192
193    // 3D internal
194    protected boolean needRebuild;
195    protected boolean needPropertiesUpdate;
196    protected double scaling[];
197    protected WeakReference<VtkCanvas> canvas3d;
198
199    protected final List<Anchor2DListener> anchor2Dlisteners;
200    protected final List<Anchor2DPositionListener> anchor2DPositionlisteners;
201
202    public Anchor2D(double x, double y, int ray, Color color, Color selectedColor)
203    {
204        super("Anchor", OverlayPriority.SHAPE_NORMAL);
205
206        position = new Point2D.Double(x, y);
207        z = -1;
208        this.ray = ray;
209        this.color = color;
210        this.selectedColor = selectedColor;
211        selected = false;
212        visible = true;
213
214        ellipse = new Ellipse2D.Double();
215        startDragMousePosition = null;
216        startDragPainterPosition = null;
217
218        vtkSource = null;
219        polyMapper = null;
220        actor = null;
221        vtkInfo = null;
222
223        scaling = new double[3];
224        Arrays.fill(scaling, 1d);
225
226        needRebuild = true;
227        needPropertiesUpdate = false;
228
229        canvas3d = new WeakReference<VtkCanvas>(null);
230
231        anchor2Dlisteners = new ArrayList<Anchor2DListener>();
232        anchor2DPositionlisteners = new ArrayList<Anchor2DPositionListener>();
233    }
234
235    public Anchor2D(double x, double y, int ray, Color color)
236    {
237        this(x, y, ray, color, DEFAULT_SELECTED_COLOR);
238    }
239
240    /**
241     * @param x
242     * @param y
243     * @param ray
244     */
245    public Anchor2D(double x, double y, int ray)
246    {
247        this(x, y, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
248    }
249
250    /**
251     * @param x
252     * @param y
253     * @param color
254     */
255    public Anchor2D(double x, double y, Color color, Color selectedColor)
256    {
257        this(x, y, DEFAULT_RAY, color, selectedColor);
258    }
259
260    /**
261     * @param x
262     * @param y
263     * @param color
264     */
265    public Anchor2D(double x, double y, Color color)
266    {
267        this(x, y, DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR);
268    }
269
270    /**
271     * @param x
272     * @param y
273     */
274    public Anchor2D(double x, double y)
275    {
276        this(x, y, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
277    }
278
279    /**
280     */
281    public Anchor2D()
282    {
283        this(0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
284    }
285
286    /**
287     * @deprecated Use {@link #Anchor2D(double, double, int, Color, Color)} instead.
288     */
289    @Deprecated
290    public Anchor2D(Point2D position, int ray, Color color, Color selectedColor)
291    {
292        this(position.getX(), position.getY(), ray, color, selectedColor);
293    }
294
295    /**
296     * @deprecated Use {@link #Anchor2D(double, double, int, Color)} instead.
297     */
298    @Deprecated
299    public Anchor2D(Point2D position, int ray, Color color)
300    {
301        this(position.getX(), position.getY(), ray, color, DEFAULT_SELECTED_COLOR);
302    }
303
304    /**
305     * @deprecated Use {@link #Anchor2D(double, double, int)} instead.
306     */
307    @Deprecated
308    public Anchor2D(Point2D position, int ray)
309    {
310        this(position.getX(), position.getY(), ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
311    }
312
313    /**
314     * @deprecated Use {@link #Anchor2D(double, double, Color, Color)} instead.
315     */
316    @Deprecated
317    public Anchor2D(Point2D position, Color color, Color selectedColor)
318    {
319        this(position.getX(), position.getY(), DEFAULT_RAY, color, selectedColor);
320    }
321
322    /**
323     * @deprecated Use {@link #Anchor2D(double, double, Color)} instead.
324     */
325    @Deprecated
326    public Anchor2D(Point2D position, Color color)
327    {
328        this(position.getX(), position.getY(), DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR);
329    }
330
331    /**
332     * @deprecated Use {@link #Anchor2D(double, double)} instead.
333     */
334    @Deprecated
335    public Anchor2D(Point2D position)
336    {
337        this(position.getX(), position.getY(), DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
338    }
339
340    /**
341     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int, Color, Color)} instead.
342     */
343    @Deprecated
344    public Anchor2D(Sequence sequence, double x, double y, int ray, Color color, Color selectedColor)
345    {
346        this(x, y, ray, color, selectedColor);
347        sequence.addOverlay(this);
348    }
349
350    /**
351     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int, Color, Color)} instead.
352     */
353    @Deprecated
354    public Anchor2D(Sequence sequence, Point2D position, int ray, Color color, Color selectedColor)
355    {
356        this(position.getX(), position.getY(), ray, color, selectedColor);
357        sequence.addOverlay(this);
358    }
359
360    /**
361     * @deprecated Use {@link Anchor2D#Anchor2D(Point2D, int, Color)} instead.
362     */
363    @Deprecated
364    public Anchor2D(Sequence sequence, Point2D position, int ray, Color color)
365    {
366        this(position.getX(), position.getY(), ray, color, DEFAULT_SELECTED_COLOR);
367        sequence.addOverlay(this);
368    }
369
370    /**
371     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead.
372     */
373    @Deprecated
374    public Anchor2D(Sequence sequence, double x, double y, int ray)
375    {
376        this(x, y, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
377        sequence.addOverlay(this);
378    }
379
380    /**
381     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color)} instead.
382     */
383    @Deprecated
384    public Anchor2D(Sequence sequence, Point2D position, Color color)
385    {
386        this(position.getX(), position.getY(), DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR);
387        sequence.addOverlay(this);
388    }
389
390    /**
391     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead.
392     */
393    @Deprecated
394    public Anchor2D(Sequence sequence, Point2D position, int ray)
395    {
396        this(position.getX(), position.getY(), ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
397        sequence.addOverlay(this);
398    }
399
400    /**
401     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead.
402     */
403    @Deprecated
404    public Anchor2D(Sequence sequence, double x, double y, Color color, Color selectedColor)
405    {
406        this(x, y, DEFAULT_RAY, color, selectedColor);
407        sequence.addOverlay(this);
408    }
409
410    /**
411     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color, Color)} instead.
412     */
413    @Deprecated
414    public Anchor2D(Sequence sequence, Point2D position, Color color, Color selectedColor)
415    {
416        this(position.getX(), position.getY(), DEFAULT_RAY, color, selectedColor);
417        sequence.addOverlay(this);
418    }
419
420    /**
421     * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color, Color)} instead.
422     */
423    @Deprecated
424    public Anchor2D(Sequence sequence, double x, double y, Color color)
425    {
426        this(x, y, DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR);
427        sequence.addOverlay(this);
428    }
429
430    /**
431     * @deprecated Use {@link Anchor2D#Anchor2D(double, double)} instead.
432     */
433    @Deprecated
434    public Anchor2D(Sequence sequence, double x, double y)
435    {
436        this(sequence, x, y, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
437        sequence.addOverlay(this);
438    }
439
440    /**
441     * @deprecated Use {@link Anchor2D#Anchor2D(double, double)} instead.
442     */
443    @Deprecated
444    public Anchor2D(Sequence sequence, Point2D position)
445    {
446        this(position.getX(), position.getY(), DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
447        sequence.addOverlay(this);
448    }
449
450    /**
451     * @deprecated Use {@link Anchor2D#Anchor2D()} instead.
452     */
453    @Deprecated
454    public Anchor2D(Sequence sequence)
455    {
456        this(0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
457        sequence.addOverlay(this);
458    }
459
460    @Override
461    protected void finalize() throws Throwable
462    {
463        super.finalize();
464
465        // release allocated VTK resources
466        if (vtkSource != null)
467            vtkSource.Delete();
468        if (actor != null)
469        {
470            actor.SetPropertyKeys(null);
471            actor.Delete();
472        }
473        if (vtkInfo != null)
474        {
475            vtkInfo.Remove(VtkCanvas.visibilityKey);
476            vtkInfo.Delete();
477        }
478        if (polyMapper != null)
479            polyMapper.Delete();
480    }
481
482    /**
483     * @return the x
484     */
485    public double getX()
486    {
487        return position.x;
488    }
489
490    /**
491     * @param x
492     *        the x to set
493     */
494    public void setX(double x)
495    {
496        setPosition(x, position.y);
497    }
498
499    /**
500     * @return the y
501     */
502    public double getY()
503    {
504        return position.y;
505    }
506
507    /**
508     * @param y
509     *        the y to set
510     */
511    public void setY(double y)
512    {
513        setPosition(position.x, y);
514    }
515
516    /**
517     * Get anchor position (return the internal reference)
518     */
519    public Point2D getPositionInternal()
520    {
521        return position;
522    }
523
524    public Point2D getPosition()
525    {
526        return new Point2D.Double(position.x, position.y);
527    }
528
529    public double getPositionX()
530    {
531        return position.x;
532    }
533
534    public double getPositionY()
535    {
536        return position.y;
537    }
538
539    public void moveTo(Point2D p)
540    {
541        setPosition(p.getX(), p.getY());
542    }
543
544    public void moveTo(double x, double y)
545    {
546        setPosition(x, y);
547    }
548
549    public void setPosition(Point2D p)
550    {
551        setPosition(p.getX(), p.getY());
552    }
553
554    public void setPosition(double x, double y)
555    {
556        if ((position.x != x) || (position.y != y))
557        {
558            position.x = x;
559            position.y = y;
560
561            needRebuild = true;
562            positionChanged();
563            painterChanged();
564        }
565    }
566
567    public void translate(double dx, double dy)
568    {
569        setPosition(position.x + dx, position.y + dy);
570    }
571
572    /**
573     * @return Z position (-1 = ALL)
574     */
575    public int getZ()
576    {
577        return z;
578    }
579
580    /**
581     * Set Z position (-1 = ALL)
582     */
583    public void setZ(int value)
584    {
585        final int v;
586
587        // special value for infinite dimension --> change to -1
588        if (value == Integer.MIN_VALUE)
589            v = -1;
590        else
591            v = value;
592
593        if (this.z != v)
594        {
595            this.z = v;
596
597            needRebuild = true;
598            painterChanged();
599            // FIXME: do this really need to send a position change event ?
600            positionChanged();
601        }
602    }
603
604    /**
605     * @return the ray
606     */
607    public int getRay()
608    {
609        return ray;
610    }
611
612    /**
613     * @param value
614     *        the ray to set
615     */
616    public void setRay(int value)
617    {
618        if (ray != value)
619        {
620            ray = value;
621            needRebuild = true;
622            painterChanged();
623        }
624    }
625
626    /**
627     * @return the color
628     */
629    public Color getColor()
630    {
631        return color;
632    }
633
634    /**
635     * @param value
636     *        the color to set
637     */
638    public void setColor(Color value)
639    {
640        if (color != value)
641        {
642            color = value;
643            needPropertiesUpdate = true;
644            propertyChanged(PROPERTY_COLOR);
645            painterChanged();
646        }
647    }
648
649    /**
650     * @return the selectedColor
651     */
652    public Color getSelectedColor()
653    {
654        return selectedColor;
655    }
656
657    /**
658     * @param value
659     *        the selectedColor to set
660     */
661    public void setSelectedColor(Color value)
662    {
663        if (selectedColor != value)
664        {
665            selectedColor = value;
666            needPropertiesUpdate = true;
667            propertyChanged(PROPERTY_SELECTEDCOLOR);
668            painterChanged();
669        }
670    }
671
672    /**
673     * @return the selected
674     */
675    public boolean isSelected()
676    {
677        return selected;
678    }
679
680    /**
681     * @param value
682     *        the selected to set
683     */
684    public void setSelected(boolean value)
685    {
686        if (selected != value)
687        {
688            selected = value;
689
690            // end drag
691            if (!value)
692                startDragMousePosition = null;
693
694            needPropertiesUpdate = true;
695            propertyChanged(PROPERTY_SELECTED);
696            painterChanged();
697        }
698    }
699
700    // /**
701    // * @return the canRemove
702    // */
703    // public boolean isCanRemove()
704    // {
705    // return canRemove;
706    // }
707    //
708    // /**
709    // * @param value
710    // * the canRemove to set
711    // */
712    // public void setCanRemove(boolean value)
713    // {
714    // canRemove = value;
715    // }
716
717    /**
718     * @return the visible
719     */
720    public boolean isVisible()
721    {
722        return visible;
723    }
724
725    /**
726     * @param value
727     *        the visible to set
728     */
729    public void setVisible(boolean value)
730    {
731        if (visible != value)
732        {
733            visible = value;
734            needPropertiesUpdate = true;
735            painterChanged();
736        }
737    }
738
739    protected void initVtkObjects()
740    {
741        // init 3D painters stuff
742        vtkSource = new vtkSphereSource();
743        vtkSource.SetRadius(getRay());
744        vtkSource.SetThetaResolution(12);
745        vtkSource.SetPhiResolution(12);
746
747        polyMapper = new vtkPolyDataMapper();
748        polyMapper.SetInputConnection((vtkSource).GetOutputPort());
749
750        actor = new vtkActor();
751        actor.SetMapper(polyMapper);
752
753        // use vtkInformations to store outline visibility state (hacky)
754        vtkInfo = new vtkInformation();
755        vtkInfo.Set(VtkCanvas.visibilityKey, 0);
756        // VtkCanvas use this to restore correctly outline visibility flag
757        actor.SetPropertyKeys(vtkInfo);
758
759        // initialize color
760        final Color col = getColor();
761        actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d);
762    }
763
764    /**
765     * update 3D painter for 3D canvas (called only when VTK is loaded).
766     */
767    protected boolean rebuildVtkObjects()
768    {
769        final VtkCanvas canvas = canvas3d.get();
770        // canvas was closed
771        if (canvas == null)
772            return false;
773
774        final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
775        // canvas was closed
776        if (vtkPanel == null)
777            return false;
778
779        final Sequence seq = canvas.getSequence();
780        // nothing to update
781        if (seq == null)
782            return false;
783
784        final Point2D pos = getPosition();
785        double curZ = getZ();
786
787        // all slices ?
788        if (curZ == -1d)
789            // set object at middle of the volume
790            curZ = seq.getSizeZ() / 2d;
791
792        // actor can be accessed in canvas3d for rendering so we need to synchronize access
793        vtkPanel.lock();
794        try
795        {
796            // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid)
797            vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], (curZ + 0.5d) * scaling[2]);
798            polyMapper.Update();
799
800            // actor.SetScale(scaling);
801        }
802        finally
803        {
804            vtkPanel.unlock();
805        }
806
807        // need to repaint
808        painterChanged();
809
810        return true;
811    }
812
813    protected void updateVtkDisplayProperties()
814    {
815        if (actor == null)
816            return;
817
818        final VtkCanvas cnv = canvas3d.get();
819        final Color col = isSelected() ? getSelectedColor() : getColor();
820        final double r = col.getRed() / 255d;
821        final double g = col.getGreen() / 255d;
822        final double b = col.getBlue() / 255d;
823        // final float opacity = getOpacity();
824
825        final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;
826
827        // we need to lock canvas as actor can be accessed during rendering
828        if (vtkPanel != null)
829            vtkPanel.lock();
830        try
831        {
832            actor.GetProperty().SetColor(r, g, b);
833            if (isVisible())
834            {
835                actor.SetVisibility(1);
836                vtkInfo.Set(VtkCanvas.visibilityKey, 1);
837            }
838            else
839            {
840                actor.SetVisibility(0);
841                vtkInfo.Set(VtkCanvas.visibilityKey, 0);
842            }
843        }
844        finally
845        {
846            if (vtkPanel != null)
847                vtkPanel.unlock();
848        }
849        // need to repaint
850        painterChanged();
851    }
852
853    protected void updateVtkRadius()
854    {
855        final VtkCanvas canvas = canvas3d.get();
856        // canvas was closed
857        if (canvas == null)
858            return;
859
860        final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
861        // canvas was closed
862        if (vtkPanel == null)
863            return;
864
865        if (vtkSource == null)
866            return;
867
868        // update sphere radius base on canvas scale X
869        final double radius = getAdjRay(canvas) * scaling[0];
870
871        if (vtkSource.GetRadius() != radius)
872        {
873            // actor can be accessed in canvas3d for rendering so we need to synchronize access
874            vtkPanel.lock();
875            try
876            {
877                vtkSource.SetRadius(radius);
878                vtkSource.Update();
879            }
880            finally
881            {
882                vtkPanel.unlock();
883            }
884
885            // need to repaint
886            painterChanged();
887        }
888    }
889
890    @Override
891    public vtkProp[] getProps()
892    {
893        // initialize VTK objects if not yet done
894        if (actor == null)
895            initVtkObjects();
896
897        return new vtkActor[] {actor};
898    }
899
900    /**
901     * Returns <code>true</code> if specified Point3D is over the anchor in the specified canvas
902     */
903    public boolean isOver(IcyCanvas canvas, Point2D imagePoint)
904    {
905        updateEllipseForCanvas(canvas);
906
907        // specific VTK canvas processing
908        if (canvas instanceof VtkCanvas)
909        {
910            // faster to use picked object
911            return (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject());
912        }
913
914        // at this point we need image position
915        if (imagePoint == null)
916            return false;
917
918        // fast contains test to start with
919        if (ellipse.getBounds2D().contains(imagePoint))
920            return ellipse.contains(imagePoint);
921
922        return false;
923    }
924
925    public boolean isOver(IcyCanvas canvas, double x, double y)
926    {
927        return isOver(canvas, new Point2D.Double(x, y));
928    }
929
930    protected double getAdjRay(IcyCanvas canvas)
931    {
932        // assume X dimension is ok here
933        return canvas.canvasToImageLogDeltaX(ray);
934    }
935
936    /**
937     * Update internal ellipse for specified Canvas
938     */
939    protected void updateEllipseForCanvas(IcyCanvas canvas)
940    {
941        // specific VTK canvas processing
942        if (canvas instanceof VtkCanvas)
943        {
944            // 3D canvas
945            final VtkCanvas cnv = (VtkCanvas) canvas;
946            // update reference if needed
947            if (canvas3d.get() != cnv)
948                canvas3d = new WeakReference<VtkCanvas>(cnv);
949
950            // FIXME : need a better implementation
951            final double[] s = cnv.getVolumeScale();
952
953            // scaling changed ?
954            if (!Arrays.equals(scaling, s))
955            {
956                // update scaling
957                scaling = s;
958                // need rebuild
959                needRebuild = true;
960            }
961
962            if (needRebuild)
963            {
964                // initialize VTK objects if not yet done
965                if (actor == null)
966                    initVtkObjects();
967
968                // request rebuild 3D objects
969                ThreadUtil.runSingle(this);
970                needRebuild = false;
971            }
972
973            if (needPropertiesUpdate)
974            {
975                updateVtkDisplayProperties();
976                needPropertiesUpdate = false;
977            }
978
979            // update sphere radius
980            updateVtkRadius();
981        }
982        else
983        {
984            final double adjRay = getAdjRay(canvas);
985
986            ellipse.setFrame(position.x - adjRay, position.y - adjRay, adjRay * 2, adjRay * 2);
987        }
988    }
989
990    protected boolean updateDrag(InputEvent e, double x, double y)
991    {
992        // not dragging --> exit
993        if (startDragMousePosition == null)
994            return false;
995
996        double dx = x - startDragMousePosition.getX();
997        double dy = y - startDragMousePosition.getY();
998
999        // shift action --> limit to one direction
1000        if (EventUtil.isShiftDown(e))
1001        {
1002            // X drag
1003            if (Math.abs(dx) > Math.abs(dy))
1004                dy = 0;
1005            // Y drag
1006            else
1007                dx = 0;
1008        }
1009
1010        // set new position
1011        setPosition(startDragPainterPosition.getX() + dx, startDragPainterPosition.getY() + dy);
1012
1013        return true;
1014    }
1015
1016    protected boolean updateDrag(InputEvent e, Point2D pt)
1017    {
1018        return updateDrag(e, pt.getX(), pt.getY());
1019    }
1020
1021    /**
1022     * called when anchor position has changed
1023     */
1024    protected void positionChanged()
1025    {
1026        updater.changed(new Anchor2DEvent(this));
1027    }
1028
1029    @SuppressWarnings("deprecation")
1030    @Override
1031    public void onChanged(CollapsibleEvent object)
1032    {
1033        if (object instanceof Anchor2DEvent)
1034        {
1035            firePositionChangedEvent(((Anchor2DEvent) object).getSource());
1036            return;
1037        }
1038
1039        // provide event backward compatibility
1040        if (object instanceof OverlayEvent)
1041        {
1042            final OverlayEvent event = (OverlayEvent) object;
1043
1044            if (event.getType() == OverlayEventType.PAINTER_CHANGED)
1045            {
1046                final PainterEvent pe = new PainterEvent(this, PainterEventType.PAINTER_CHANGED);
1047
1048                for (Anchor2DListener listener : new ArrayList<Anchor2DListener>(anchor2Dlisteners))
1049                    listener.painterChanged(pe);
1050            }
1051        }
1052
1053        super.onChanged(object);
1054    }
1055
1056    protected void firePositionChangedEvent(Anchor2D source)
1057    {
1058        for (Anchor2DPositionListener listener : new ArrayList<Anchor2DPositionListener>(anchor2DPositionlisteners))
1059            listener.positionChanged(source);
1060
1061        // backward compatibility
1062        for (Anchor2DListener listener : new ArrayList<Anchor2DListener>(anchor2Dlisteners))
1063            listener.positionChanged(source);
1064    }
1065
1066    public void addPositionListener(Anchor2DPositionListener listener)
1067    {
1068        anchor2DPositionlisteners.add(listener);
1069    }
1070
1071    public void removePositionListener(Anchor2DPositionListener listener)
1072    {
1073        anchor2DPositionlisteners.remove(listener);
1074    }
1075
1076    /**
1077     * @deprecated Use {@link #addPositionListener(Anchor2DPositionListener)} or
1078     *             {@link #addOverlayListener(OverlayListener)} instead.
1079     */
1080    @Deprecated
1081    public void addAnchorListener(Anchor2DListener listener)
1082    {
1083        anchor2Dlisteners.add(listener);
1084    }
1085
1086    /**
1087     * @deprecated Use {@link #removePositionListener(Anchor2DPositionListener)} or
1088     *             {@link #removeOverlayListener(OverlayListener)} instead.
1089     */
1090    @Deprecated
1091    public void removeAnchorListener(Anchor2DListener listener)
1092    {
1093        anchor2Dlisteners.remove(listener);
1094    }
1095
1096    /**
1097     * @deprecated Use {@link #addPositionListener(Anchor2DPositionListener)} or
1098     *             {@link #addOverlayListener(OverlayListener)} instead.
1099     */
1100    @Deprecated
1101    public void addListener(Anchor2DListener listener)
1102    {
1103        addAnchorListener(listener);
1104    }
1105
1106    /**
1107     * @deprecated Use {@link #removePositionListener(Anchor2DPositionListener)} or
1108     *             {@link #removeOverlayListener(OverlayListener)} instead.
1109     */
1110    @Deprecated
1111    public void removeListener(Anchor2DListener listener)
1112    {
1113        removeAnchorListener(listener);
1114    }
1115
1116    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified)
1117    {
1118        // this will update VTK objects if needed
1119        updateEllipseForCanvas(canvas);
1120
1121        if (canvas instanceof IcyCanvas2D)
1122        {
1123            // nothing to do here when not visible
1124            if (!isVisible())
1125                return;
1126
1127            // get canvas Z position
1128            final int cnvZ = canvas.getPositionZ();
1129            final int z = getZ();
1130
1131            boolean sameZPos = ((z == -1) || (cnvZ == -1) || (z == cnvZ));
1132
1133            if (sameZPos)
1134            {
1135                // trivial paint optimization
1136                if (ShapeUtil.isVisible(g, ellipse))
1137                {
1138                    final Graphics2D g2 = (Graphics2D) g.create();
1139
1140                    // draw content
1141                    if (isSelected())
1142                        g2.setColor(getSelectedColor());
1143                    else
1144                        g2.setColor(getColor());
1145
1146                    // simplified small drawing
1147                    if (simplified)
1148                    {
1149                        final int ray = (int) canvas.canvasToImageDeltaX(2);
1150                        g2.fillRect((int) getPositionX() - ray, (int) getPositionY() - ray, ray * 2, ray * 2);
1151                    }
1152                    // normal drawing
1153                    else
1154                    {
1155                        // draw ellipse content
1156                        g2.fill(ellipse);
1157                        // draw black border
1158                        g2.setStroke(new BasicStroke((float) (getAdjRay(canvas) / 8f)));
1159                        g2.setColor(Color.black);
1160                        g2.draw(ellipse);
1161                    }
1162
1163                    g2.dispose();
1164                }
1165            }
1166        }
1167        else if (canvas instanceof VtkCanvas)
1168        {
1169            // nothing to do here
1170        }
1171    }
1172
1173    @Override
1174    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
1175    {
1176        paint(g, sequence, canvas, false);
1177    }
1178
1179    /*
1180     * only for backward compatibility
1181     */
1182    @Deprecated
1183    @Override
1184    public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
1185    {
1186        if (!isVisible())
1187            return;
1188
1189        // VtkCanvas not handled here
1190        if (canvas instanceof VtkCanvas)
1191            return;
1192        // no image position --> exit
1193        if (imagePoint == null)
1194            return;
1195
1196        // just for the shift key state change
1197        updateDrag(e, imagePoint);
1198    }
1199
1200    /*
1201     * only for backward compatibility
1202     */
1203    @Deprecated
1204    @Override
1205    public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas)
1206    {
1207        if (!isVisible())
1208            return;
1209
1210        // VtkCanvas not handled here
1211        if (canvas instanceof VtkCanvas)
1212            return;
1213        // no image position --> exit
1214        if (imagePoint == null)
1215            return;
1216
1217        // just for the shift key state change
1218        updateDrag(e, imagePoint);
1219    }
1220
1221    /*
1222     * only for backward compatibility
1223     */
1224    @Deprecated
1225    @Override
1226    public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
1227    {
1228        if (!isVisible())
1229            return;
1230
1231        if (e.isConsumed())
1232            return;
1233
1234        // VtkCanvas not handled here
1235        if (canvas instanceof VtkCanvas)
1236            return;
1237        // no image position --> exit
1238        if (imagePoint == null)
1239            return;
1240
1241        if (EventUtil.isLeftMouseButton(e))
1242        {
1243            // consume event to activate drag
1244            if (isSelected())
1245            {
1246                startDragMousePosition = imagePoint;
1247                startDragPainterPosition = getPosition();
1248                e.consume();
1249            }
1250        }
1251    }
1252
1253    /*
1254     * only for backward compatibility
1255     */
1256    @Deprecated
1257    @Override
1258    public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
1259    {
1260        startDragMousePosition = null;
1261    }
1262
1263    /*
1264     * only for backward compatibility
1265     */
1266    @Deprecated
1267    @Override
1268    public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
1269    {
1270        if (!isVisible())
1271            return;
1272
1273        if (e.isConsumed())
1274            return;
1275
1276        // VtkCanvas not handled here
1277        if (canvas instanceof VtkCanvas)
1278            return;
1279        // no image position --> exit
1280        if (imagePoint == null)
1281            return;
1282
1283        if (EventUtil.isLeftMouseButton(e))
1284        {
1285            // if selected then move according to mouse position
1286            if (isSelected())
1287            {
1288                // force start drag if not already the case
1289                if (startDragMousePosition == null)
1290                {
1291                    startDragMousePosition = getPosition();
1292                    startDragPainterPosition = getPosition();
1293                }
1294
1295                updateDrag(e, imagePoint);
1296
1297                e.consume();
1298            }
1299        }
1300    }
1301
1302    /*
1303     * only for backward compatibility
1304     */
1305    @Deprecated
1306    @Override
1307    public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas)
1308    {
1309        if (!isVisible())
1310            return;
1311
1312        // VtkCanvas not handled here
1313        if (canvas instanceof VtkCanvas)
1314            return;
1315        // no image position --> exit
1316        if (imagePoint == null)
1317            return;
1318
1319        // already consumed, no selection possible
1320        if (e.isConsumed())
1321            setSelected(false);
1322        else
1323        {
1324            final boolean overlapped = isOver(canvas, imagePoint.getX(), imagePoint.getY());
1325
1326            setSelected(overlapped);
1327
1328            // so we can only have one selected at once
1329            if (overlapped)
1330                e.consume();
1331        }
1332    }
1333
1334    @Override
1335    public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1336    {
1337        if (!isVisible())
1338            return;
1339
1340        // no image position --> exit
1341        if (imagePoint == null)
1342            return;
1343
1344        // just for the shift key state change
1345        updateDrag(e, imagePoint.x, imagePoint.y);
1346    }
1347
1348    @Override
1349    public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1350    {
1351        if (!isVisible())
1352            return;
1353
1354        // no image position --> exit
1355        if (imagePoint == null)
1356            return;
1357
1358        // just for the shift key state change
1359        updateDrag(e, imagePoint.x, imagePoint.y);
1360    }
1361
1362    @Override
1363    public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1364    {
1365        if (!isVisible())
1366            return;
1367
1368        if (e.isConsumed())
1369            return;
1370
1371        // no image position --> exit
1372        if (imagePoint == null)
1373            return;
1374
1375        if (EventUtil.isLeftMouseButton(e))
1376        {
1377            // consume event to activate drag
1378            if (isSelected())
1379            {
1380                startDragMousePosition = imagePoint.toPoint2D();
1381                startDragPainterPosition = getPosition();
1382                e.consume();
1383            }
1384        }
1385    }
1386
1387    @Override
1388    public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1389    {
1390        startDragMousePosition = null;
1391    }
1392
1393    @Override
1394    public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1395    {
1396        if (!isVisible())
1397            return;
1398
1399        if (e.isConsumed())
1400            return;
1401
1402        // no image position --> exit
1403        if (imagePoint == null)
1404            return;
1405
1406        if (EventUtil.isLeftMouseButton(e))
1407        {
1408            // if selected then move according to mouse position
1409            if (isSelected())
1410            {
1411                // force start drag if not already the case
1412                if (startDragMousePosition == null)
1413                {
1414                    startDragMousePosition = getPosition();
1415                    startDragPainterPosition = getPosition();
1416                }
1417
1418                updateDrag(e, imagePoint.x, imagePoint.y);
1419
1420                e.consume();
1421            }
1422        }
1423    }
1424
1425    @Override
1426    public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1427    {
1428        if (!isVisible())
1429            return;
1430
1431        // already consumed, no selection possible
1432        if (e.isConsumed())
1433            setSelected(false);
1434        else
1435        {
1436            final boolean overlapped = isOver(canvas, (imagePoint != null) ? imagePoint.toPoint2D() : null);
1437
1438            setSelected(overlapped);
1439
1440            // so we can only have one selected at once
1441            if (overlapped)
1442                e.consume();
1443        }
1444    }
1445
1446    @Override
1447    public void run()
1448    {
1449        rebuildVtkObjects();
1450    }
1451
1452    public boolean loadPositionFromXML(Node node)
1453    {
1454        if (node == null)
1455            return false;
1456
1457        beginUpdate();
1458        try
1459        {
1460            setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d));
1461            setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d));
1462        }
1463        finally
1464        {
1465            endUpdate();
1466        }
1467
1468        return true;
1469    }
1470
1471    public boolean savePositionToXML(Node node)
1472    {
1473        if (node == null)
1474            return false;
1475
1476        XMLUtil.setElementDoubleValue(node, ID_POS_X, getX());
1477        XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY());
1478
1479        return true;
1480    }
1481
1482    @Override
1483    public boolean loadFromXML(Node node)
1484    {
1485        if (node == null)
1486            return false;
1487
1488        beginUpdate();
1489        try
1490        {
1491            setColor(new Color(XMLUtil.getElementIntValue(node, ID_COLOR, DEFAULT_NORMAL_COLOR.getRGB())));
1492            setSelectedColor(
1493                    new Color(XMLUtil.getElementIntValue(node, ID_SELECTEDCOLOR, DEFAULT_SELECTED_COLOR.getRGB())));
1494            setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d));
1495            setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d));
1496            setRay(XMLUtil.getElementIntValue(node, ID_RAY, DEFAULT_RAY));
1497            setVisible(XMLUtil.getElementBooleanValue(node, ID_VISIBLE, true));
1498        }
1499        finally
1500        {
1501            endUpdate();
1502        }
1503
1504        return true;
1505    }
1506
1507    @Override
1508    public boolean saveToXML(Node node)
1509    {
1510        if (node == null)
1511            return false;
1512
1513        XMLUtil.setElementIntValue(node, ID_COLOR, getColor().getRGB());
1514        XMLUtil.setElementIntValue(node, ID_SELECTEDCOLOR, getSelectedColor().getRGB());
1515        XMLUtil.setElementDoubleValue(node, ID_POS_X, getX());
1516        XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY());
1517        XMLUtil.setElementIntValue(node, ID_RAY, getRay());
1518        XMLUtil.setElementBooleanValue(node, ID_VISIBLE, isVisible());
1519
1520        return true;
1521    }
1522}