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.sequence.Sequence;
025import icy.system.thread.ThreadUtil;
026import icy.type.point.Point3D;
027import icy.type.point.Point5D;
028import icy.util.EventUtil;
029import icy.util.GraphicsUtil;
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.lang.ref.WeakReference;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.EventListener;
045import java.util.List;
046
047import org.w3c.dom.Node;
048
049import plugins.kernel.canvas.VtkCanvas;
050import vtk.vtkActor;
051import vtk.vtkInformation;
052import vtk.vtkPolyDataMapper;
053import vtk.vtkProp;
054import vtk.vtkSphereSource;
055
056/**
057 * Anchor3D class, used for 3D point control.
058 * 
059 * @author Stephane
060 */
061public class Anchor3D extends Overlay implements VtkPainter, Runnable
062{
063    /**
064     * Interface to listen Anchor3D position change
065     */
066    public static interface Anchor3DPositionListener extends EventListener
067    {
068        public void positionChanged(Anchor3D source);
069    }
070
071    public static class Anchor3DEvent implements CollapsibleEvent
072    {
073        private final Anchor3D source;
074
075        public Anchor3DEvent(Anchor3D source)
076        {
077            super();
078
079            this.source = source;
080        }
081
082        /**
083         * @return the source
084         */
085        public Anchor3D getSource()
086        {
087            return source;
088        }
089
090        @Override
091        public boolean collapse(CollapsibleEvent event)
092        {
093            if (equals(event))
094            {
095                // nothing to do here
096                return true;
097            }
098
099            return false;
100        }
101
102        @Override
103        public int hashCode()
104        {
105            return source.hashCode();
106        }
107
108        @Override
109        public boolean equals(Object obj)
110        {
111            if (obj == this)
112                return true;
113
114            if (obj instanceof Anchor3DEvent)
115            {
116                final Anchor3DEvent event = (Anchor3DEvent) obj;
117
118                return (event.getSource() == source);
119            }
120
121            return super.equals(obj);
122        }
123    }
124
125    protected static final String ID_COLOR = "color";
126    protected static final String ID_SELECTEDCOLOR = "selected_color";
127    protected static final String ID_SELECTED = "selected";
128    protected static final String ID_POS_X = "pos_x";
129    protected static final String ID_POS_Y = "pos_y";
130    protected static final String ID_POS_Z = "pos_z";
131    protected static final String ID_RAY = "ray";
132    protected static final String ID_VISIBLE = "visible";
133
134    public static final int DEFAULT_RAY = 6;
135    public static final Color DEFAULT_NORMAL_COLOR = Color.GREEN;
136    public static final Color DEFAULT_SELECTED_COLOR = Color.WHITE;
137
138    /**
139     * position (canvas)
140     */
141    protected final Point3D.Double position;
142    /**
143     * radius (integer as we express it in pixel)
144     */
145    protected int ray;
146
147    /**
148     * color
149     */
150    protected Color color;
151    /**
152     * selection color
153     */
154    protected Color selectedColor;
155    /**
156     * selection flag
157     */
158    protected boolean selected;
159    /**
160     * flag that indicate if anchor is visible
161     */
162    protected boolean visible;
163
164    // drag internals
165    protected Point3D startDragMousePosition;
166    protected Point3D startDragPainterPosition;
167
168    // 2D shape for X,Y contains test
169    protected final Ellipse2D ellipse;
170
171    // VTK 3D objects
172    vtkSphereSource vtkSource;
173    protected vtkPolyDataMapper polyMapper;
174    protected vtkActor actor;
175    protected vtkInformation vtkInfo;
176
177    // 3D internal
178    protected boolean needRebuild;
179    protected boolean needPropertiesUpdate;
180    protected double scaling[];
181    protected WeakReference<VtkCanvas> canvas3d;
182
183    // listeners
184    protected final List<Anchor3DPositionListener> anchor3DPositionlisteners;
185
186    public Anchor3D(double x, double y, double z, int ray, Color color, Color selectedColor)
187    {
188        super("Anchor", OverlayPriority.SHAPE_NORMAL);
189
190        position = new Point3D.Double(x, y, z);
191        this.ray = ray;
192        this.color = color;
193        this.selectedColor = selectedColor;
194        selected = false;
195        visible = true;
196
197        startDragMousePosition = null;
198        startDragPainterPosition = null;
199
200        ellipse = new Ellipse2D.Double();
201
202        vtkSource = null;
203        polyMapper = null;
204        actor = null;
205        vtkInfo = null;
206
207        scaling = new double[3];
208        Arrays.fill(scaling, 1d);
209
210        needRebuild = true;
211        needPropertiesUpdate = false;
212
213        canvas3d = new WeakReference<VtkCanvas>(null);
214
215        anchor3DPositionlisteners = new ArrayList<Anchor3DPositionListener>();
216    }
217
218    public Anchor3D(double x, double y, double z, Color color, Color selectedColor)
219    {
220        this(x, y, z, DEFAULT_RAY, color, selectedColor);
221    }
222
223    public Anchor3D(double x, double y, double z, int ray)
224    {
225        this(x, y, z, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
226    }
227
228    public Anchor3D(double x, double y, double z)
229    {
230        this(x, y, z, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
231    }
232
233    public Anchor3D()
234    {
235        this(0d, 0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR);
236    }
237
238    @Override
239    protected void finalize() throws Throwable
240    {
241        super.finalize();
242
243        // release allocated VTK resources
244        if (vtkSource != null)
245            vtkSource.Delete();
246        if (actor != null)
247        {
248            actor.SetPropertyKeys(null);
249            actor.Delete();
250        }
251        if (vtkInfo != null)
252        {
253            vtkInfo.Remove(VtkCanvas.visibilityKey);
254            vtkInfo.Delete();
255        }
256        if (polyMapper != null)
257            polyMapper.Delete();
258    }
259
260    /**
261     * @return X coordinate position
262     */
263    public double getX()
264    {
265        return position.x;
266    }
267
268    /**
269     * Sets the X coordinate position
270     */
271    public void setX(double value)
272    {
273        setPosition(value, position.y, position.z);
274    }
275
276    /**
277     * @return Y coordinate position
278     */
279    public double getY()
280    {
281        return position.y;
282    }
283
284    /**
285     * Sets the Y coordinate position
286     */
287    public void setY(double value)
288    {
289        setPosition(position.x, value, position.z);
290    }
291
292    /**
293     * @return Z coordinate position
294     */
295    public double getZ()
296    {
297        return position.z;
298    }
299
300    /**
301     * Sets the Z coordinate position
302     */
303    public void setZ(double value)
304    {
305        setPosition(position.x, position.y, value);
306    }
307
308    /**
309     * Get anchor position (return the internal reference)
310     */
311    public Point3D getPositionInternal()
312    {
313        return position;
314    }
315
316    /**
317     * Get anchor position
318     */
319    public Point3D getPosition()
320    {
321        return new Point3D.Double(position.x, position.y, position.z);
322    }
323
324    /**
325     * Sets anchor position
326     */
327    public void setPosition(Point3D p)
328    {
329        setPosition(p.getX(), p.getY(), p.getZ());
330    }
331
332    /**
333     * Sets anchor position
334     */
335    public void setPosition(double x, double y, double z)
336    {
337        if ((position.x != x) || (position.y != y) || (position.z != z))
338        {
339            position.x = x;
340            position.y = y;
341            position.z = z;
342
343            needRebuild = true;
344            positionChanged();
345            painterChanged();
346        }
347    }
348
349    /**
350     * Performs a translation on the anchor position
351     */
352    public void translate(double dx, double dy, double dz)
353    {
354        setPosition(position.x + dx, position.y + dy, position.z + dz);
355    }
356
357    /**
358     * @return the ray
359     */
360    public int getRay()
361    {
362        return ray;
363    }
364
365    /**
366     * Sets the ray
367     */
368    public void setRay(int value)
369    {
370        if (ray != value)
371        {
372            ray = value;
373            needRebuild = true;
374            painterChanged();
375        }
376    }
377
378    /**
379     * @return the color
380     */
381    public Color getColor()
382    {
383        return color;
384    }
385
386    /**
387     * Sets the color
388     */
389    public void setColor(Color value)
390    {
391        if (color != value)
392        {
393            color = value;
394            needPropertiesUpdate = true;
395            painterChanged();
396        }
397    }
398
399    /**
400     * @return the <code>selected</code> state color
401     */
402    public Color getSelectedColor()
403    {
404        return selectedColor;
405    }
406
407    /**
408     * Sets the <code>selected</code> state color
409     */
410    public void setSelectedColor(Color value)
411    {
412        if (selectedColor != value)
413        {
414            selectedColor = value;
415            needPropertiesUpdate = true;
416            painterChanged();
417        }
418    }
419
420    /**
421     * @return the <code>selected</code> state
422     */
423    public boolean isSelected()
424    {
425        return selected;
426    }
427
428    /**
429     * Sets the <code>selected</code> state
430     */
431    public void setSelected(boolean value)
432    {
433        if (selected != value)
434        {
435            selected = value;
436
437            // end drag
438            if (!value)
439                startDragMousePosition = null;
440
441            needPropertiesUpdate = true;
442            painterChanged();
443        }
444    }
445
446    /**
447     * @return the visible
448     */
449    public boolean isVisible()
450    {
451        return visible;
452    }
453
454    /**
455     * @param value
456     *        the visible to set
457     */
458    public void setVisible(boolean value)
459    {
460        if (visible != value)
461        {
462            visible = value;
463            needPropertiesUpdate = true;
464            painterChanged();
465        }
466    }
467
468    /**
469     * Returns <code>true</code> if specified Point3D is over the anchor in the specified canvas
470     */
471    public boolean isOver(IcyCanvas canvas, Point3D imagePoint)
472    {
473        updateEllipseForCanvas(canvas);
474
475        // specific VTK canvas processing
476        if (canvas instanceof VtkCanvas)
477        {
478            // faster to use picked object
479            return (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject());
480        }
481
482        // at this point we need image position
483        if (imagePoint == null)
484            return false;
485
486        final double x = imagePoint.getX();
487        final double y = imagePoint.getY();
488        final double z = imagePoint.getZ();
489
490        // default processing for other canvas
491        final int cnvZ = canvas.getPositionZ();
492
493        // same Z position ?
494        if ((cnvZ == -1) || (z == -1d) || (cnvZ == (int) z))
495        {
496            // fast contains test to start with
497            if (ellipse.getBounds2D().contains(x, y))
498                return ellipse.contains(x, y);
499        }
500
501        return false;
502    }
503
504    /**
505     * Returns adjusted ray for specified Canvas
506     */
507    protected double getAdjRay(IcyCanvas canvas)
508    {
509        // assume X dimension is ok here
510        return canvas.canvasToImageLogDeltaX(ray);
511    }
512
513    /**
514     * Update internal ellipse for specified Canvas
515     */
516    protected void updateEllipseForCanvas(IcyCanvas canvas)
517    {
518        // specific VTK canvas processing
519        if (canvas instanceof VtkCanvas)
520        {
521            // 3D canvas
522            final VtkCanvas cnv = (VtkCanvas) canvas;
523            // update reference if needed
524            if (canvas3d.get() != cnv)
525                canvas3d = new WeakReference<VtkCanvas>(cnv);
526
527            // FIXME : need a better implementation
528            final double[] s = cnv.getVolumeScale();
529
530            // scaling changed ?
531            if (!Arrays.equals(scaling, s))
532            {
533                // update scaling
534                scaling = s;
535                // need rebuild
536                needRebuild = true;
537            }
538
539            if (needRebuild)
540            {
541                // initialize VTK objects if not yet done
542                if (actor == null)
543                    initVtkObjects();
544
545                // request rebuild 3D objects
546                ThreadUtil.runSingle(this);
547                needRebuild = false;
548            }
549
550            if (needPropertiesUpdate)
551            {
552                updateVtkDisplayProperties();
553                needPropertiesUpdate = false;
554            }
555
556            // update sphere radius
557            updateVtkRadius();
558        }
559        else
560        {
561            final double adjRay = getAdjRay(canvas);
562
563            ellipse.setFrame(position.x - adjRay, position.y - adjRay, adjRay * 2, adjRay * 2);
564        }
565    }
566
567    protected boolean updateDrag(InputEvent e, double x, double y, double z)
568    {
569        // not dragging --> exit
570        if (startDragMousePosition == null)
571            return false;
572
573        double dx = x - startDragMousePosition.getX();
574        double dy = y - startDragMousePosition.getY();
575        double dz = z - startDragMousePosition.getZ();
576
577        // shift action --> limit to one direction
578        if (EventUtil.isShiftDown(e))
579        {
580            // X or Z drag
581            if (Math.abs(dx) > Math.abs(dy))
582            {
583                dy = 0d;
584
585                // Z drag
586                if (Math.abs(dz) > Math.abs(dx))
587                    dx = 0d;
588                else
589                    dz = 0d;
590            }
591            // Y or Z drag
592            else
593            {
594                dx = 0d;
595
596                // Z drag
597                if (Math.abs(dz) > Math.abs(dy))
598                    dy = 0d;
599                else
600                    dz = 0d;
601            }
602        }
603
604        // set new position
605        setPosition(new Point3D.Double(startDragPainterPosition.getX() + dx, startDragPainterPosition.getY() + dy,
606                startDragPainterPosition.getZ() + dz));
607
608        return true;
609    }
610
611    protected boolean updateDrag(InputEvent e, Point3D pt)
612    {
613        return updateDrag(e, pt.getX(), pt.getY(), pt.getZ());
614    }
615
616    /**
617     * called when anchor position has changed
618     */
619    protected void positionChanged()
620    {
621        updater.changed(new Anchor3DEvent(this));
622    }
623
624    @Override
625    public void onChanged(CollapsibleEvent object)
626    {
627        // we got a position change event
628        if (object instanceof Anchor3DEvent)
629        {
630            firePositionChangedEvent(((Anchor3DEvent) object).getSource());
631            return;
632        }
633
634        super.onChanged(object);
635    }
636
637    protected void firePositionChangedEvent(Anchor3D source)
638    {
639        for (Anchor3DPositionListener listener : new ArrayList<Anchor3DPositionListener>(anchor3DPositionlisteners))
640            listener.positionChanged(source);
641    }
642
643    public void addPositionListener(Anchor3DPositionListener listener)
644    {
645        anchor3DPositionlisteners.add(listener);
646    }
647
648    public void removePositionListener(Anchor3DPositionListener listener)
649    {
650        anchor3DPositionlisteners.remove(listener);
651    }
652
653    protected void initVtkObjects()
654    {
655        // init 3D painters stuff
656        vtkSource = new vtkSphereSource();
657        vtkSource.SetRadius(getRay());
658        vtkSource.SetThetaResolution(12);
659        vtkSource.SetPhiResolution(12);
660
661        polyMapper = new vtkPolyDataMapper();
662        polyMapper.SetInputConnection((vtkSource).GetOutputPort());
663
664        actor = new vtkActor();
665        actor.SetMapper(polyMapper);
666
667        // use vtkInformations to store outline visibility state (hacky)
668        vtkInfo = new vtkInformation();
669        vtkInfo.Set(VtkCanvas.visibilityKey, 0);
670        // VtkCanvas use this to restore correctly outline visibility flag
671        actor.SetPropertyKeys(vtkInfo);
672
673        // initialize color
674        final Color col = getColor();
675        actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d);
676    }
677
678    /**
679     * update 3D painter for 3D canvas (called only when VTK is loaded).
680     */
681    protected boolean rebuildVtkObjects()
682    {
683        final VtkCanvas canvas = canvas3d.get();
684        // canvas was closed
685        if (canvas == null)
686            return false;
687
688        final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
689        // canvas was closed
690        if (vtkPanel == null)
691            return false;
692
693        final Point3D pos = getPosition();
694
695        // actor can be accessed in canvas3d for rendering so we need to synchronize access
696        vtkPanel.lock();
697        try
698        {
699            // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid)
700            vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], pos.getZ() * scaling[2]);
701            vtkSource.Update();
702            polyMapper.Update();
703
704            // actor.SetScale(scaling[0], scaling[1], scaling[0]);
705        }
706        finally
707        {
708            vtkPanel.unlock();
709        }
710
711        // need to repaint
712        painterChanged();
713
714        return true;
715    }
716
717    protected void updateVtkDisplayProperties()
718    {
719        if (actor == null)
720            return;
721
722        final VtkCanvas cnv = canvas3d.get();
723        final Color col = isSelected() ? getSelectedColor() : getColor();
724        final double r = col.getRed() / 255d;
725        final double g = col.getGreen() / 255d;
726        final double b = col.getBlue() / 255d;
727        // final float opacity = getOpacity();
728
729        final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;
730
731        // we need to lock canvas as actor can be accessed during rendering
732        if (vtkPanel != null)
733            vtkPanel.lock();
734        try
735        {
736            actor.GetProperty().SetColor(r, g, b);
737            if (isVisible())
738            {
739                actor.SetVisibility(1);
740                vtkInfo.Set(VtkCanvas.visibilityKey, 1);
741            }
742            else
743            {
744                actor.SetVisibility(0);
745                vtkInfo.Set(VtkCanvas.visibilityKey, 0);
746            }
747        }
748        finally
749        {
750            if (vtkPanel != null)
751                vtkPanel.unlock();
752        }
753
754        // need to repaint
755        painterChanged();
756    }
757
758    protected void updateVtkRadius()
759    {
760        final VtkCanvas canvas = canvas3d.get();
761        // canvas was closed
762        if (canvas == null)
763            return;
764
765        final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
766        // canvas was closed
767        if (vtkPanel == null)
768            return;
769
770        if (vtkSource == null)
771            return;
772
773        // update sphere radius base on canvas scale X
774        final double radius = getAdjRay(canvas) * scaling[0];
775
776        if (vtkSource.GetRadius() != radius)
777        {
778            // actor can be accessed in canvas3d for rendering so we need to synchronize access
779            vtkPanel.lock();
780            try
781            {
782                vtkSource.SetRadius(radius);
783                vtkSource.Update();
784            }
785            finally
786            {
787                vtkPanel.unlock();
788            }
789
790            // need to repaint
791            painterChanged();
792        }
793    }
794
795    @Override
796    public vtkProp[] getProps()
797    {
798        // initialize VTK objects if not yet done
799        if (actor == null)
800            initVtkObjects();
801
802        return new vtkActor[] {actor};
803    }
804
805    @Override
806    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
807    {
808        paint(g, sequence, canvas, false);
809    }
810
811    public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified)
812    {
813        // this will update VTK objects if needed
814        updateEllipseForCanvas(canvas);
815
816        if (canvas instanceof IcyCanvas2D)
817        {
818            // nothing to do here when not visible
819            if (!isVisible())
820                return;
821
822            // get canvas Z position
823            final int cnvZ = canvas.getPositionZ();
824            // get delta Z (difference between canvas Z position and anchor Z pos)
825            final int dz = Math.abs(((int) getZ()) - cnvZ);
826            // calculate z fade range
827            final int zRange = Math.min(10, Math.max(3, sequence.getSizeZ() / 10));
828
829            // not visible on this Z position
830            if (dz > zRange)
831                return;
832
833            // trivial paint optimization
834            final boolean shapeVisible = ShapeUtil.isVisible(g, ellipse);
835
836            if (shapeVisible)
837            {
838                final Graphics2D g2 = (Graphics2D) g.create();
839                // ration for size / opacity
840                final float ratio = 1f - ((float) dz / (float) zRange);
841
842                if (ratio != 1f)
843                    GraphicsUtil.mixAlpha(g2, ratio);
844
845                // draw content
846                if (isSelected())
847                    g2.setColor(getSelectedColor());
848                else
849                    g2.setColor(getColor());
850
851                // simplified small drawing
852                if (simplified)
853                {
854                    final int ray = (int) canvas.canvasToImageDeltaX(2);
855                    g2.fillRect((int) getX() - ray, (int) getY() - ray, ray * 2, ray * 2);
856                }
857                // normal drawing
858                else
859                {
860                    // draw ellipse content
861                    g2.fill(ellipse);
862                    // draw black border
863                    g2.setStroke(new BasicStroke((float) (getAdjRay(canvas) / 8f)));
864                    g2.setColor(Color.black);
865                    g2.draw(ellipse);
866                }
867
868                g2.dispose();
869            }
870        }
871        else if (canvas instanceof VtkCanvas)
872        {
873            // nothing to do here
874        }
875    }
876
877    @Override
878    public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
879    {
880        if (!isVisible() && !getReceiveKeyEventOnHidden())
881            return;
882
883        // no image position --> exit
884        if (imagePoint == null)
885            return;
886
887        // just for the shift key state change
888        updateDrag(e, imagePoint.x, imagePoint.y, imagePoint.z);
889    }
890
891    @Override
892    public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
893    {
894        if (!isVisible() && !getReceiveKeyEventOnHidden())
895            return;
896
897        // no image position --> exit
898        if (imagePoint == null)
899            return;
900
901        // just for the shift key state change
902        updateDrag(e, imagePoint.x, imagePoint.y, imagePoint.z);
903    }
904
905    @Override
906    public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
907    {
908        if (!isVisible() && !getReceiveMouseEventOnHidden())
909            return;
910
911        if (e.isConsumed())
912            return;
913
914        // no image position --> exit
915        if (imagePoint == null)
916            return;
917
918        if (EventUtil.isLeftMouseButton(e))
919        {
920            // consume event to activate drag
921            if (isSelected())
922            {
923                startDragMousePosition = imagePoint.toPoint3D();
924                startDragPainterPosition = getPosition();
925                e.consume();
926            }
927        }
928    }
929
930    @Override
931    public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
932    {
933        startDragMousePosition = null;
934    }
935
936    @Override
937    public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
938    {
939        if (!isVisible() && !getReceiveMouseEventOnHidden())
940            return;
941
942        if (e.isConsumed())
943            return;
944
945        // no image position --> exit
946        if (imagePoint == null)
947            return;
948
949        if (EventUtil.isLeftMouseButton(e))
950        {
951            // if selected then move according to mouse position
952            if (isSelected())
953            {
954                // force start drag if not already the case
955                if (startDragMousePosition == null)
956                {
957                    startDragMousePosition = imagePoint.toPoint3D();
958                    startDragPainterPosition = getPosition();
959                }
960
961                updateDrag(e, imagePoint.x, imagePoint.y, imagePoint.z);
962
963                e.consume();
964            }
965        }
966    }
967
968    @Override
969    public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
970    {
971        if (!isVisible() && !getReceiveMouseEventOnHidden())
972            return;
973
974        // already consumed, no selection possible
975        if (e.isConsumed())
976            setSelected(false);
977        else
978        {
979            final boolean overlapped = isOver(canvas, (imagePoint != null) ? imagePoint.toPoint3D() : null);
980
981            setSelected(overlapped);
982
983            // so we can only have one selected at once
984            if (overlapped)
985                e.consume();
986        }
987    }
988
989    @Override
990    public void run()
991    {
992        rebuildVtkObjects();
993    }
994
995    public boolean loadPositionFromXML(Node node)
996    {
997        if (node == null)
998            return false;
999
1000        beginUpdate();
1001        try
1002        {
1003            setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d));
1004            setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d));
1005            setZ(XMLUtil.getElementDoubleValue(node, ID_POS_Z, 0d));
1006        }
1007        finally
1008        {
1009            endUpdate();
1010        }
1011
1012        return true;
1013    }
1014
1015    public boolean savePositionToXML(Node node)
1016    {
1017        if (node == null)
1018            return false;
1019
1020        XMLUtil.setElementDoubleValue(node, ID_POS_X, getX());
1021        XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY());
1022        XMLUtil.setElementDoubleValue(node, ID_POS_Z, getZ());
1023
1024        return true;
1025    }
1026
1027    @Override
1028    public boolean loadFromXML(Node node)
1029    {
1030        if (node == null)
1031            return false;
1032
1033        beginUpdate();
1034        try
1035        {
1036            setColor(new Color(XMLUtil.getElementIntValue(node, ID_COLOR, DEFAULT_NORMAL_COLOR.getRGB())));
1037            setSelectedColor(
1038                    new Color(XMLUtil.getElementIntValue(node, ID_SELECTEDCOLOR, DEFAULT_SELECTED_COLOR.getRGB())));
1039            setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d));
1040            setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d));
1041            setZ(XMLUtil.getElementDoubleValue(node, ID_POS_Z, 0d));
1042            setRay(XMLUtil.getElementIntValue(node, ID_RAY, DEFAULT_RAY));
1043            setVisible(XMLUtil.getElementBooleanValue(node, ID_VISIBLE, true));
1044        }
1045        finally
1046        {
1047            endUpdate();
1048        }
1049
1050        return true;
1051    }
1052
1053    @Override
1054    public boolean saveToXML(Node node)
1055    {
1056        if (node == null)
1057            return false;
1058
1059        XMLUtil.setElementIntValue(node, ID_COLOR, getColor().getRGB());
1060        XMLUtil.setElementIntValue(node, ID_SELECTEDCOLOR, getSelectedColor().getRGB());
1061        XMLUtil.setElementDoubleValue(node, ID_POS_X, getX());
1062        XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY());
1063        XMLUtil.setElementDoubleValue(node, ID_POS_Z, getY());
1064        XMLUtil.setElementIntValue(node, ID_RAY, getRay());
1065        XMLUtil.setElementBooleanValue(node, ID_VISIBLE, isVisible());
1066
1067        return true;
1068    }
1069}