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 plugins.kernel.roi.roi4d;
020
021import java.awt.Color;
022import java.awt.Graphics2D;
023import java.awt.Rectangle;
024import java.awt.event.KeyEvent;
025import java.awt.event.MouseEvent;
026import java.awt.event.MouseWheelEvent;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.TreeMap;
032import java.util.concurrent.Semaphore;
033
034import org.w3c.dom.Element;
035import org.w3c.dom.Node;
036
037import icy.canvas.IcyCanvas;
038import icy.canvas.IcyCanvas2D;
039import icy.canvas.IcyCanvas3D;
040import icy.painter.OverlayEvent;
041import icy.painter.OverlayListener;
042import icy.roi.BooleanMask2D;
043import icy.roi.ROI;
044import icy.roi.ROI3D;
045import icy.roi.ROI4D;
046import icy.roi.ROIEvent;
047import icy.roi.ROIListener;
048import icy.sequence.Sequence;
049import icy.system.IcyExceptionHandler;
050import icy.type.point.Point5D;
051import icy.type.rectangle.Rectangle3D;
052import icy.type.rectangle.Rectangle4D;
053import icy.util.XMLUtil;
054
055/**
056 * Abstract class defining a generic 4D ROI as a stack of individual 3D ROI slices.
057 * 
058 * @author Alexandre Dufour
059 * @author Stephane Dallongeville
060 * @param <R>
061 *        the type of 3D ROI for each slice of this 4D ROI
062 */
063public class ROI4DStack<R extends ROI3D> extends ROI4D implements ROIListener, OverlayListener, Iterable<R>
064{
065    public static final String PROPERTY_USECHILDCOLOR = "useChildColor";
066
067    protected final TreeMap<Integer, R> slices = new TreeMap<Integer, R>();
068
069    protected final Class<R> roiClass;
070    protected boolean useChildColor;
071    protected Semaphore modifyingSlice;
072    protected double translateT;
073
074    /**
075     * Creates a new 4D ROI based on the given 3D ROI type.
076     */
077    public ROI4DStack(Class<R> roiClass)
078    {
079        super();
080
081        this.roiClass = roiClass;
082        useChildColor = false;
083        modifyingSlice = new Semaphore(1);
084        translateT = 0d;
085    }
086
087    @Override
088    public String getDefaultName()
089    {
090        return "ROI3D stack";
091    }
092
093    @Override
094    protected ROIPainter createPainter()
095    {
096        return new ROI4DStackPainter();
097    }
098
099    /**
100     * Create a new empty 3D ROI slice.
101     */
102    protected R createSlice()
103    {
104        try
105        {
106            return roiClass.newInstance();
107        }
108        catch (Exception e)
109        {
110            IcyExceptionHandler.showErrorMessage(e, true, true);
111            return null;
112        }
113    }
114
115    /**
116     * Returns <code>true</code> if the ROI directly uses the 3D slice color draw property and <code>false</code> if it
117     * uses the global 4D ROI color draw property.
118     */
119    public boolean getUseChildColor()
120    {
121        return useChildColor;
122    }
123
124    /**
125     * Set to <code>true</code> if you want to directly use the 3D slice color draw property and <code>false</code> to
126     * keep the global 4D ROI color draw property.
127     * 
128     * @see #setColor(int, Color)
129     */
130    public void setUseChildColor(boolean value)
131    {
132        if (useChildColor != value)
133        {
134            useChildColor = value;
135            propertyChanged(PROPERTY_USECHILDCOLOR);
136            // need to redraw it
137            getOverlay().painterChanged();
138        }
139    }
140
141    /**
142     * Set the painter color for the specified ROI slice.
143     * 
144     * @see #setUseChildColor(boolean)
145     */
146    public void setColor(int t, Color value)
147    {
148        final ROI3D slice = getSlice(t);
149
150        modifyingSlice.acquireUninterruptibly();
151        try
152        {
153            if (slice != null)
154                slice.setColor(value);
155        }
156        finally
157        {
158            modifyingSlice.release();
159        }
160    }
161
162    @Override
163    public void setColor(Color value)
164    {
165        beginUpdate();
166        try
167        {
168            super.setColor(value);
169
170            if (!getUseChildColor())
171            {
172                modifyingSlice.acquireUninterruptibly();
173                try
174                {
175                    synchronized (slices)
176                    {
177                        for (R slice : slices.values())
178                            slice.setColor(value);
179                    }
180                }
181                finally
182                {
183                    modifyingSlice.release();
184                }
185            }
186        }
187        finally
188        {
189            endUpdate();
190        }
191    }
192
193    @Override
194    public void setOpacity(float value)
195    {
196        beginUpdate();
197        try
198        {
199            super.setOpacity(value);
200
201            modifyingSlice.acquireUninterruptibly();
202            try
203            {
204                synchronized (slices)
205                {
206                    for (R slice : slices.values())
207                        slice.setOpacity(value);
208                }
209            }
210            finally
211            {
212                modifyingSlice.release();
213            }
214        }
215        finally
216        {
217            endUpdate();
218        }
219    }
220
221    @Override
222    public void setStroke(double value)
223    {
224        beginUpdate();
225        try
226        {
227            super.setStroke(value);
228
229            modifyingSlice.acquireUninterruptibly();
230            try
231            {
232                synchronized (slices)
233                {
234                    for (R slice : slices.values())
235                        slice.setStroke(value);
236                }
237            }
238            finally
239            {
240                modifyingSlice.release();
241            }
242        }
243        finally
244        {
245            endUpdate();
246        }
247    }
248
249    @Override
250    public void setCreating(boolean value)
251    {
252        beginUpdate();
253        try
254        {
255            super.setCreating(value);
256
257            modifyingSlice.acquireUninterruptibly();
258            try
259            {
260                synchronized (slices)
261                {
262                    for (R slice : slices.values())
263                        slice.setCreating(value);
264                }
265            }
266            finally
267            {
268                modifyingSlice.release();
269            }
270        }
271        finally
272        {
273            endUpdate();
274        }
275    }
276
277    @Override
278    public void setReadOnly(boolean value)
279    {
280        beginUpdate();
281        try
282        {
283            super.setReadOnly(value);
284
285            modifyingSlice.acquireUninterruptibly();
286            try
287            {
288                synchronized (slices)
289                {
290                    for (R slice : slices.values())
291                        slice.setReadOnly(value);
292                }
293            }
294            finally
295            {
296                modifyingSlice.release();
297            }
298        }
299        finally
300        {
301            endUpdate();
302        }
303    }
304
305    @Override
306    public void setFocused(boolean value)
307    {
308        beginUpdate();
309        try
310        {
311            super.setFocused(value);
312
313            modifyingSlice.acquireUninterruptibly();
314            try
315            {
316                synchronized (slices)
317                {
318                    for (R slice : slices.values())
319                        slice.setFocused(value);
320                }
321            }
322            finally
323            {
324                modifyingSlice.release();
325            }
326        }
327        finally
328        {
329            endUpdate();
330        }
331    }
332
333    @Override
334    public void setSelected(boolean value)
335    {
336        beginUpdate();
337        try
338        {
339            super.setSelected(value);
340
341            modifyingSlice.acquireUninterruptibly();
342            try
343            {
344                synchronized (slices)
345                {
346                    for (R slice : slices.values())
347                        slice.setSelected(value);
348                }
349            }
350            finally
351            {
352                modifyingSlice.release();
353            }
354        }
355        finally
356        {
357            endUpdate();
358        }
359    }
360
361    @Override
362    public void setC(int value)
363    {
364        beginUpdate();
365        try
366        {
367            super.setC(value);
368
369            modifyingSlice.acquireUninterruptibly();
370            try
371            {
372                synchronized (slices)
373                {
374                    for (R slice : slices.values())
375                        slice.setC(value);
376                }
377            }
378            finally
379            {
380                modifyingSlice.release();
381            }
382        }
383        finally
384        {
385            endUpdate();
386        }
387    }
388
389    /**
390     * Returns <code>true</code> if the ROI stack is empty.
391     */
392    public boolean isEmpty()
393    {
394        return slices.isEmpty();
395    }
396
397    /**
398     * @return The size of this ROI stack along T.<br>
399     *         Note that the returned value indicates the difference between upper and lower bounds
400     *         of this ROI, but doesn't guarantee that all slices in-between exist ( {@link #getSlice(int)} may still
401     *         return <code>null</code>.<br>
402     */
403    public int getSizeT()
404    {
405        synchronized (slices)
406        {
407            if (slices.isEmpty())
408                return 0;
409
410            return (slices.lastKey().intValue() - slices.firstKey().intValue()) + 1;
411        }
412    }
413
414    /**
415     * Returns the ROI slice at given T position.
416     */
417    public R getSlice(int t)
418    {
419        return slices.get(Integer.valueOf(t));
420    }
421
422    /**
423     * Returns the ROI slice at given T position.
424     */
425    public R getSlice(int t, boolean createIfNull)
426    {
427        R result = getSlice(t);
428
429        if ((result == null) && createIfNull)
430        {
431            result = createSlice();
432            if (result != null)
433                setSlice(t, result);
434        }
435
436        return result;
437    }
438
439    /**
440     * Sets the slice for the given T position.
441     */
442    public void setSlice(int t, R roi3d)
443    {
444        if (roi3d == null)
445            throw new IllegalArgumentException("Cannot set an empty slice in a 4D ROI");
446
447        roi3d.beginUpdate();
448        try
449        {
450            // set T and C position
451            roi3d.setT(t);
452            roi3d.setC(getC());
453        }
454        finally
455        {
456            roi3d.endUpdate();
457        }
458
459        // listen events from this ROI and its overlay
460        roi3d.addListener(this);
461        roi3d.getOverlay().addOverlayListener(this);
462
463        synchronized (slices)
464        {
465            slices.put(Integer.valueOf(t), roi3d);
466        }
467
468        // notify ROI changed
469        roiChanged(true);
470    }
471
472    /**
473     * Removes slice at the given T position and returns it.
474     */
475    public R removeSlice(int t)
476    {
477        final R result;
478
479        synchronized (slices)
480        {
481            // remove the current slice (if any)
482            result = slices.remove(Integer.valueOf(t));
483        }
484
485        // remove listeners
486        if (result != null)
487        {
488            result.removeListener(this);
489            result.getOverlay().removeOverlayListener(this);
490        }
491
492        // notify ROI changed
493        roiChanged(true);
494
495        return result;
496    }
497
498    /**
499     * Removes all slices.
500     */
501    public void clear()
502    {
503        // nothing to do
504        if (isEmpty())
505            return;
506
507        synchronized (slices)
508        {
509            for (R slice : slices.values())
510            {
511                slice.removeListener(this);
512                slice.getOverlay().removeOverlayListener(this);
513            }
514
515            slices.clear();
516        }
517
518        roiChanged(true);
519    }
520
521    /**
522     * Called when a ROI slice has changed.
523     */
524    protected void sliceChanged(ROIEvent event)
525    {
526        if (modifyingSlice.availablePermits() <= 0)
527            return;
528
529        final ROI source = event.getSource();
530
531        switch (event.getType())
532        {
533            case ROI_CHANGED:
534                // position change of a slice can change global bounds --> transform to 'content changed' event type
535                roiChanged(true);
536                // roiChanged(StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL));
537                break;
538
539            case FOCUS_CHANGED:
540                setFocused(source.isFocused());
541                break;
542
543            case SELECTION_CHANGED:
544                setSelected(source.isSelected());
545                break;
546
547            case PROPERTY_CHANGED:
548                final String propertyName = event.getPropertyName();
549
550                if ((propertyName == null) || propertyName.equals(PROPERTY_READONLY))
551                    setReadOnly(source.isReadOnly());
552                if ((propertyName == null) || propertyName.equals(PROPERTY_CREATING))
553                    setCreating(source.isCreating());
554                break;
555        }
556    }
557
558    /**
559     * Called when a ROI slice overlay has changed.
560     */
561    protected void sliceOverlayChanged(OverlayEvent event)
562    {
563        switch (event.getType())
564        {
565            case PAINTER_CHANGED:
566                // forward the event to ROI stack overlay
567                getOverlay().painterChanged();
568                break;
569
570            case PROPERTY_CHANGED:
571                // forward the event to ROI stack overlay
572                getOverlay().propertyChanged(event.getPropertyName());
573                break;
574        }
575    }
576
577    @Override
578    public Rectangle4D computeBounds4D()
579    {
580        Rectangle3D xyzBounds = null;
581
582        synchronized (slices)
583        {
584            for (R slice : slices.values())
585            {
586                final Rectangle3D bnd3d = slice.getBounds3D();
587
588                // only add non empty bounds
589                if (!bnd3d.isEmpty())
590                {
591                    if (xyzBounds == null)
592                        xyzBounds = (Rectangle3D) bnd3d.clone();
593                    else
594                        xyzBounds.add(bnd3d);
595                }
596            }
597        }
598
599        // create empty 3D bounds
600        if (xyzBounds == null)
601            xyzBounds = new Rectangle3D.Double();
602
603        final int t;
604        final int sizeT;
605
606        synchronized (slices)
607        {
608            if (!slices.isEmpty())
609            {
610                t = slices.firstKey().intValue();
611                sizeT = getSizeT();
612            }
613            else
614            {
615                t = 0;
616                sizeT = 0;
617            }
618        }
619        return new Rectangle4D.Double(xyzBounds.getX(), xyzBounds.getY(), xyzBounds.getZ(), t, xyzBounds.getSizeX(),
620                xyzBounds.getSizeY(), xyzBounds.getSizeZ(), sizeT);
621    }
622
623    @Override
624    public boolean contains(double x, double y, double z, double t)
625    {
626        final R roi3d = getSlice((int) Math.floor(t));
627
628        if (roi3d != null)
629            return roi3d.contains(x, y, z);
630
631        return false;
632    }
633
634    @Override
635    public boolean contains(double x, double y, double z, double t, double sizeX, double sizeY, double sizeZ,
636            double sizeT)
637    {
638        final Rectangle4D bounds = getBounds4D();
639
640        // easy discard
641        if (!bounds.contains(x, y, z, t, sizeX, sizeY, sizeZ, sizeT))
642            return false;
643
644        final int lim = (int) Math.floor(t + sizeT);
645        for (int tc = (int) Math.floor(t); tc < lim; tc++)
646        {
647            final R roi3d = getSlice(tc);
648            if ((roi3d == null) || !roi3d.contains(x, y, z, sizeX, sizeY, sizeZ))
649                return false;
650        }
651
652        return true;
653    }
654
655    @Override
656    public boolean intersects(double x, double y, double z, double t, double sizeX, double sizeY, double sizeZ,
657            double sizeT)
658    {
659        final Rectangle4D bounds = getBounds4D();
660
661        // easy discard
662        if (!bounds.intersects(x, y, z, t, sizeX, sizeY, sizeZ, sizeT))
663            return false;
664
665        final int lim = (int) Math.floor(t + sizeT);
666        for (int tc = (int) Math.floor(t); tc < lim; tc++)
667        {
668            final R roi3d = getSlice(tc);
669            if ((roi3d != null) && roi3d.intersects(x, y, z, sizeX, sizeY, sizeZ))
670                return true;
671        }
672
673        return false;
674    }
675
676    @Override
677    public boolean hasSelectedPoint()
678    {
679        // default
680        return false;
681    }
682
683    @Override
684    public void unselectAllPoints()
685    {
686        beginUpdate();
687        try
688        {
689            modifyingSlice.acquireUninterruptibly();
690            try
691            {
692                synchronized (slices)
693                {
694                    for (R slice : slices.values())
695                        slice.unselectAllPoints();
696                }
697            }
698            finally
699            {
700                modifyingSlice.release();
701            }
702        }
703        finally
704        {
705            endUpdate();
706        }
707    }
708
709    // default approximated implementation for ROI4DStack
710    @Override
711    public double computeNumberOfContourPoints()
712    {
713        // 4D contour points = first slice points + inter slices contour points + last slice points
714        double result = 0;
715
716        synchronized (slices)
717        {
718            if (slices.size() <= 2)
719            {
720                for (R slice : slices.values())
721                    result += slice.getNumberOfPoints();
722            }
723            else
724            {
725                final Entry<Integer, R> firstEntry = slices.firstEntry();
726                final Entry<Integer, R> lastEntry = slices.lastEntry();
727                final Integer firstKey = firstEntry.getKey();
728                final Integer lastKey = lastEntry.getKey();
729
730                result = firstEntry.getValue().getNumberOfPoints();
731
732                for (R slice : slices.subMap(firstKey, false, lastKey, false).values())
733                    result += slice.getNumberOfContourPoints();
734
735                result += lastEntry.getValue().getNumberOfPoints();
736            }
737        }
738
739        return result;
740    }
741
742    @Override
743    public double computeNumberOfPoints()
744    {
745        double volume = 0;
746
747        synchronized (slices)
748        {
749            for (R slice : slices.values())
750                volume += slice.getNumberOfPoints();
751        }
752
753        return volume;
754    }
755
756    @Override
757    public boolean canTranslate()
758    {
759        synchronized (slices)
760        {
761            // only need to test the first entry
762            if (!slices.isEmpty())
763                return slices.firstEntry().getValue().canTranslate();
764        }
765
766        return false;
767    }
768
769    /**
770     * Translate the stack of specified T position.
771     */
772    public void translate(int t)
773    {
774        // easy optimizations
775        if ((t == 0) || isEmpty())
776            return;
777
778        synchronized (slices)
779        {
780            final Map<Integer, R> map = new HashMap<Integer, R>(slices);
781
782            slices.clear();
783            for (Entry<Integer, R> entry : map.entrySet())
784            {
785                final R roi = entry.getValue();
786                final int newT = roi.getT() + t;
787
788                // only positive value accepted
789                if (newT >= 0)
790                {
791                    roi.setT(newT);
792                    slices.put(Integer.valueOf(newT), roi);
793                }
794            }
795        }
796
797        // notify ROI changed
798        roiChanged(false);
799    }
800
801    @Override
802    public void translate(double dx, double dy, double dz, double dt)
803    {
804        beginUpdate();
805        try
806        {
807            translateT += dt;
808            // convert to integer
809            final int dti = (int) translateT;
810            // keep trace of not used floating part
811            translateT -= dti;
812
813            translate(dti);
814
815            modifyingSlice.acquireUninterruptibly();
816            try
817            {
818                synchronized (slices)
819                {
820                    for (R slice : slices.values())
821                        slice.translate(dx, dy, dz);
822                }
823            }
824            finally
825            {
826                modifyingSlice.release();
827            }
828
829            // notify ROI changed because we modified slice 'internally'
830            if ((dx != 0d) || (dy != 0d) || (dz != 0d))
831                roiChanged(false);
832        }
833        finally
834        {
835            endUpdate();
836        }
837    }
838
839    @Override
840    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, int t, boolean inclusive)
841    {
842        final R roi3d = getSlice(t);
843
844        if (roi3d != null)
845            return roi3d.getBooleanMask2D(x, y, width, height, z, inclusive);
846
847        return new boolean[width * height];
848    }
849
850    @Override
851    public BooleanMask2D getBooleanMask2D(int z, int t, boolean inclusive)
852    {
853        final R roi3d = getSlice(t);
854
855        if (roi3d != null)
856            return roi3d.getBooleanMask2D(z, inclusive);
857
858        return new BooleanMask2D(new Rectangle(), new boolean[0]);
859    }
860
861    // called when one of the slice ROI changed
862    @Override
863    public void roiChanged(ROIEvent event)
864    {
865        // propagate children change event
866        sliceChanged(event);
867    }
868
869    // called when one of the slice ROI overlay changed
870    @Override
871    public void overlayChanged(OverlayEvent event)
872    {
873        // propagate children overlay change event
874        sliceOverlayChanged(event);
875    }
876
877    @Override
878    public Iterator<R> iterator()
879    {
880        return slices.values().iterator();
881    }
882
883    @Override
884    public boolean loadFromXML(Node node)
885    {
886        beginUpdate();
887        try
888        {
889            if (!super.loadFromXML(node))
890                return false;
891
892            // we don't need to save the 3D ROI class as the parent class already do it
893            clear();
894
895            for (Element e : XMLUtil.getElements(node, "slice"))
896            {
897                // faster than using complete XML serialization
898                final R slice = createSlice();
899
900                // error while reloading the ROI from XML
901                if ((slice == null) || !slice.loadFromXML(e))
902                    return false;
903
904                setSlice(slice.getT(), slice);
905            }
906        }
907        finally
908        {
909            endUpdate();
910        }
911
912        return true;
913    }
914
915    @Override
916    public boolean saveToXML(Node node)
917    {
918        if (!super.saveToXML(node))
919            return false;
920
921        synchronized (slices)
922        {
923            for (R slice : slices.values())
924            {
925                Element sliceNode = XMLUtil.addElement(node, "slice");
926
927                if (!slice.saveToXML(sliceNode))
928                    return false;
929            }
930        }
931
932        return true;
933    }
934
935    public class ROI4DStackPainter extends ROIPainter
936    {
937        R getSliceForCanvas(IcyCanvas canvas)
938        {
939            final int t = canvas.getPositionT();
940
941            if (t >= 0)
942                return getSlice(t);
943
944            return null;
945        }
946
947        @Override
948        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
949        {
950            super.paint(g, sequence, canvas);
951
952            if (isActiveFor(canvas))
953            {
954                if (canvas instanceof IcyCanvas3D)
955                {
956                    // TODO
957
958                }
959                else if (canvas instanceof IcyCanvas2D)
960                {
961                    // forward event to current slice
962                    final R slice = getSliceForCanvas(canvas);
963
964                    if (slice != null)
965                        slice.getOverlay().paint(g, sequence, canvas);
966                }
967            }
968        }
969
970        @Override
971        public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
972        {
973            // send event to parent first
974            super.keyPressed(e, imagePoint, canvas);
975
976            // then send it to active slice
977            if (isActiveFor(canvas))
978            {
979                // forward event to current slice
980                final R slice = getSliceForCanvas(canvas);
981
982                if (slice != null)
983                    slice.getOverlay().keyPressed(e, imagePoint, canvas);
984            }
985        }
986
987        @Override
988        public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
989        {
990            // send event to parent first
991            super.keyReleased(e, imagePoint, canvas);
992
993            // then send it to active slice
994            if (isActiveFor(canvas))
995            {
996                // forward event to current slice
997                final R slice = getSliceForCanvas(canvas);
998
999                if (slice != null)
1000                    slice.getOverlay().keyReleased(e, imagePoint, canvas);
1001            }
1002        }
1003
1004        @Override
1005        public void mouseEntered(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1006        {
1007            // send event to parent first
1008            super.mouseEntered(e, imagePoint, canvas);
1009
1010            // then send it to active slice
1011            if (isActiveFor(canvas))
1012            {
1013                // forward event to current slice
1014                final R slice = getSliceForCanvas(canvas);
1015
1016                if (slice != null)
1017                    slice.getOverlay().mouseEntered(e, imagePoint, canvas);
1018            }
1019        }
1020
1021        @Override
1022        public void mouseExited(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1023        {
1024            // send event to parent first
1025            super.mouseExited(e, imagePoint, canvas);
1026
1027            // then send it to active slice
1028            if (isActiveFor(canvas))
1029            {
1030                // forward event to current slice
1031                final R slice = getSliceForCanvas(canvas);
1032
1033                if (slice != null)
1034                    slice.getOverlay().mouseExited(e, imagePoint, canvas);
1035            }
1036        }
1037
1038        @Override
1039        public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1040        {
1041            // send event to parent first
1042            super.mouseMove(e, imagePoint, canvas);
1043
1044            // then send it to active slice
1045            if (isActiveFor(canvas))
1046            {
1047                // forward event to current slice
1048                final R slice = getSliceForCanvas(canvas);
1049
1050                if (slice != null)
1051                    slice.getOverlay().mouseMove(e, imagePoint, canvas);
1052            }
1053        }
1054
1055        @Override
1056        public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1057        {
1058            // send event to parent first
1059            super.mouseDrag(e, imagePoint, canvas);
1060
1061            // then send it to active slice
1062            if (isActiveFor(canvas))
1063            {
1064                // forward event to current slice
1065                final R slice = getSliceForCanvas(canvas);
1066
1067                if (slice != null)
1068                    slice.getOverlay().mouseDrag(e, imagePoint, canvas);
1069            }
1070        }
1071
1072        @Override
1073        public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1074        {
1075            // send event to parent first
1076            super.mousePressed(e, imagePoint, canvas);
1077
1078            // then send it to active slice
1079            if (isActiveFor(canvas))
1080            {
1081                // forward event to current slice
1082                final R slice = getSliceForCanvas(canvas);
1083
1084                if (slice != null)
1085                    slice.getOverlay().mousePressed(e, imagePoint, canvas);
1086            }
1087        }
1088
1089        @Override
1090        public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1091        {
1092            // send event to parent first
1093            super.mouseReleased(e, imagePoint, canvas);
1094
1095            // then send it to active slice
1096            if (isActiveFor(canvas))
1097            {
1098                // forward event to current slice
1099                final R slice = getSliceForCanvas(canvas);
1100
1101                if (slice != null)
1102                    slice.getOverlay().mouseReleased(e, imagePoint, canvas);
1103            }
1104        }
1105
1106        @Override
1107        public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1108        {
1109            // send event to parent first
1110            super.mouseClick(e, imagePoint, canvas);
1111
1112            // then send it to active slice
1113            if (isActiveFor(canvas))
1114            {
1115                // forward event to current slice
1116                final R slice = getSliceForCanvas(canvas);
1117
1118                if (slice != null)
1119                    slice.getOverlay().mouseClick(e, imagePoint, canvas);
1120            }
1121        }
1122
1123        @Override
1124        public void mouseWheelMoved(MouseWheelEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1125        {
1126            // send event to parent first
1127            super.mouseWheelMoved(e, imagePoint, canvas);
1128
1129            // then send it to active slice
1130            if (isActiveFor(canvas))
1131            {
1132                // forward event to current slice
1133                final R slice = getSliceForCanvas(canvas);
1134
1135                if (slice != null)
1136                    slice.getOverlay().mouseWheelMoved(e, imagePoint, canvas);
1137            }
1138        }
1139    }
1140}