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.roi5d;
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.ROI4D;
045import icy.roi.ROI5D;
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.Rectangle4D;
052import icy.type.rectangle.Rectangle5D;
053import icy.util.XMLUtil;
054
055/**
056 * Abstract class defining a generic 5D ROI as a stack of individual 4D ROI slices.
057 * 
058 * @author Alexandre Dufour
059 * @author Stephane Dallongeville
060 * @param <R>
061 *        the type of 4D ROI for each slice of this 5D ROI
062 */
063public class ROI5DStack<R extends ROI4D> extends ROI5D 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 translateC;
073
074    /**
075     * Creates a new 5D ROI based on the given 4D ROI type.
076     */
077    public ROI5DStack(Class<R> roiClass)
078    {
079        super();
080
081        this.roiClass = roiClass;
082        useChildColor = false;
083        modifyingSlice = new Semaphore(1);
084        translateC = 0d;
085    }
086
087    @Override
088    public String getDefaultName()
089    {
090        return "ROI4D stack";
091    }
092
093    @Override
094    protected ROIPainter createPainter()
095    {
096        return new ROI5DStackPainter();
097    }
098
099    /**
100     * Create a new empty 4D 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 4D slice color draw property and <code>false</code> if it
117     * uses the global 5D 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 4D slice color draw property and <code>false</code> to
126     * keep the global 5D 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 c, Color value)
147    {
148        final ROI4D slice = getSlice(c);
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    /**
362     * Returns <code>true</code> if the ROI stack is empty.
363     */
364    public boolean isEmpty()
365    {
366        return slices.isEmpty();
367    }
368
369    /**
370     * @return The size of this ROI stack along C.<br>
371     *         Note that the returned value indicates the difference between upper and lower bounds
372     *         of this ROI, but doesn't guarantee that all slices in-between exist ( {@link #getSlice(int)} may still
373     *         return <code>null</code>.<br>
374     */
375    public int getSizeC()
376    {
377        synchronized (slices)
378        {
379            if (slices.isEmpty())
380                return 0;
381
382            return (slices.lastKey().intValue() - slices.firstKey().intValue()) + 1;
383        }
384    }
385
386    /**
387     * Returns the ROI slice at given C position.
388     */
389    public R getSlice(int c)
390    {
391        return slices.get(Integer.valueOf(c));
392    }
393
394    /**
395     * Returns the ROI slice at given C position.
396     */
397    public R getSlice(int c, boolean createIfNull)
398    {
399        R result = getSlice(c);
400
401        if ((result == null) && createIfNull)
402        {
403            result = createSlice();
404            if (result != null)
405                setSlice(c, result);
406        }
407
408        return result;
409    }
410
411    /**
412     * Sets the slice for the given C position.
413     */
414    public void setSlice(int c, R roi4d)
415    {
416        if (roi4d == null)
417            throw new IllegalArgumentException("Cannot set an empty slice in a 5D ROI");
418
419        // set C position
420        roi4d.setC(c);
421        // listen events from this ROI and its overlay
422        roi4d.addListener(this);
423        roi4d.getOverlay().addOverlayListener(this);
424
425        synchronized (slices)
426        {
427            slices.put(Integer.valueOf(c), roi4d);
428        }
429
430        // notify ROI changed
431        roiChanged(true);
432    }
433
434    /**
435     * Removes slice at the given C position and returns it.
436     */
437    public R removeSlice(int c)
438    {
439        final R result;
440
441        synchronized (slices)
442        {
443            // remove the current slice (if any)
444            result = slices.remove(Integer.valueOf(c));
445        }
446
447        // remove listeners
448        if (result != null)
449        {
450            result.removeListener(this);
451            result.getOverlay().removeOverlayListener(this);
452        }
453
454        // notify ROI changed
455        roiChanged(true);
456
457        return result;
458    }
459
460    /**
461     * Removes all slices.
462     */
463    public void clear()
464    {
465        // nothing to do
466        if (isEmpty())
467            return;
468
469        synchronized (slices)
470        {
471            for (R slice : slices.values())
472            {
473                slice.removeListener(this);
474                slice.getOverlay().removeOverlayListener(this);
475            }
476
477            slices.clear();
478        }
479
480        roiChanged(true);
481    }
482
483    /**
484     * Called when a ROI slice has changed.
485     */
486    protected void sliceChanged(ROIEvent event)
487    {
488        if (modifyingSlice.availablePermits() <= 0)
489            return;
490
491        final ROI source = event.getSource();
492
493        switch (event.getType())
494        {
495            case ROI_CHANGED:
496                // position change of a slice can change global bounds --> transform to 'content changed' event type
497                roiChanged(true);
498                // roiChanged(StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL));
499                break;
500
501            case FOCUS_CHANGED:
502                setFocused(source.isFocused());
503                break;
504
505            case SELECTION_CHANGED:
506                setSelected(source.isSelected());
507                break;
508
509            case PROPERTY_CHANGED:
510                final String propertyName = event.getPropertyName();
511
512                if ((propertyName == null) || propertyName.equals(PROPERTY_READONLY))
513                    setReadOnly(source.isReadOnly());
514                if ((propertyName == null) || propertyName.equals(PROPERTY_CREATING))
515                    setCreating(source.isCreating());
516                break;
517        }
518    }
519
520    /**
521     * Called when a ROI slice overlay has changed.
522     */
523    protected void sliceOverlayChanged(OverlayEvent event)
524    {
525        switch (event.getType())
526        {
527            case PAINTER_CHANGED:
528                // forward the event to ROI stack overlay
529                getOverlay().painterChanged();
530                break;
531
532            case PROPERTY_CHANGED:
533                // forward the event to ROI stack overlay
534                getOverlay().propertyChanged(event.getPropertyName());
535                break;
536        }
537    }
538
539    @Override
540    public Rectangle5D computeBounds5D()
541    {
542        Rectangle4D xyztBounds = null;
543
544        synchronized (slices)
545        {
546            for (R slice : slices.values())
547            {
548                final Rectangle4D bnd4d = slice.getBounds4D();
549
550                // only add non empty bounds
551                if (!bnd4d.isEmpty())
552                {
553                    if (xyztBounds == null)
554                        xyztBounds = (Rectangle4D) bnd4d.clone();
555                    else
556                        xyztBounds.add(bnd4d);
557                }
558            }
559        }
560
561        // create empty 4D bounds
562        if (xyztBounds == null)
563            xyztBounds = new Rectangle4D.Double();
564
565        final int c;
566        final int sizeC;
567
568        synchronized (slices)
569        {
570            if (!slices.isEmpty())
571            {
572                c = slices.firstKey().intValue();
573                sizeC = getSizeC();
574            }
575            else
576            {
577                c = 0;
578                sizeC = 0;
579            }
580        }
581
582        return new Rectangle5D.Double(xyztBounds.getX(), xyztBounds.getY(), xyztBounds.getZ(), xyztBounds.getT(), c,
583                xyztBounds.getSizeX(), xyztBounds.getSizeY(), xyztBounds.getSizeZ(), xyztBounds.getSizeT(), sizeC);
584    }
585
586    @Override
587    public boolean contains(double x, double y, double z, double t, double c)
588    {
589        final R roi4d = getSlice((int) Math.floor(c));
590
591        if (roi4d != null)
592            return roi4d.contains(x, y, z, t);
593
594        return false;
595    }
596
597    @Override
598    public boolean contains(double x, double y, double z, double t, double c, double sizeX, double sizeY, double sizeZ,
599            double sizeT, double sizeC)
600    {
601        final Rectangle5D bounds = getBounds5D();
602
603        // easy discard
604        if (!bounds.contains(x, y, z, t, c, sizeX, sizeY, sizeZ, sizeT, sizeC))
605            return false;
606
607        final int lim = (int) Math.floor(c + sizeC);
608        for (int cc = (int) Math.floor(c); cc < lim; cc++)
609        {
610            final R roi4d = getSlice(cc);
611            if ((roi4d == null) || !roi4d.contains(x, y, z, t, sizeX, sizeY, sizeZ, sizeT))
612                return false;
613        }
614
615        return true;
616    }
617
618    @Override
619    public boolean intersects(double x, double y, double z, double t, double c, double sizeX, double sizeY,
620            double sizeZ, double sizeT, double sizeC)
621    {
622        final Rectangle5D bounds = getBounds5D();
623
624        // easy discard
625        if (!bounds.intersects(x, y, z, t, c, sizeX, sizeY, sizeZ, sizeT, sizeC))
626            return false;
627
628        final int lim = (int) Math.floor(c + sizeC);
629        for (int cc = (int) Math.floor(c); cc < lim; cc++)
630        {
631            final R roi4d = getSlice(cc);
632            if ((roi4d != null) && roi4d.intersects(x, y, z, t, sizeX, sizeY, sizeZ, sizeT))
633                return true;
634        }
635
636        return false;
637    }
638
639    @Override
640    public boolean hasSelectedPoint()
641    {
642        // default
643        return false;
644    }
645
646    @Override
647    public void unselectAllPoints()
648    {
649        beginUpdate();
650        try
651        {
652            modifyingSlice.acquireUninterruptibly();
653            try
654            {
655                synchronized (slices)
656                {
657                    for (R slice : slices.values())
658                        slice.unselectAllPoints();
659                }
660            }
661            finally
662            {
663                modifyingSlice.release();
664            }
665        }
666        finally
667        {
668            endUpdate();
669        }
670    }
671
672    // default approximated implementation for ROI5DStack
673    @Override
674    public double computeNumberOfContourPoints()
675    {
676        // 5D contour points = first slice points + inter slices contour points + last slice points
677        double perimeter = 0;
678
679        synchronized (slices)
680        {
681            if (slices.size() <= 2)
682            {
683                for (R slice : slices.values())
684                    perimeter += slice.getNumberOfPoints();
685            }
686            else
687            {
688                final Entry<Integer, R> firstEntry = slices.firstEntry();
689                final Entry<Integer, R> lastEntry = slices.lastEntry();
690                final Integer firstKey = firstEntry.getKey();
691                final Integer lastKey = lastEntry.getKey();
692
693                perimeter = firstEntry.getValue().getNumberOfPoints();
694
695                for (R slice : slices.subMap(firstKey, false, lastKey, false).values())
696                    perimeter += slice.getNumberOfContourPoints();
697
698                perimeter += lastEntry.getValue().getNumberOfPoints();
699            }
700        }
701
702        return perimeter;
703    }
704
705    @Override
706    public double computeNumberOfPoints()
707    {
708        double volume = 0;
709
710        synchronized (slices)
711        {
712            for (R slice : slices.values())
713                volume += slice.getNumberOfPoints();
714        }
715
716        return volume;
717    }
718
719    @Override
720    public boolean canTranslate()
721    {
722        synchronized (slices)
723        {
724            // only need to test the first entry
725            if (!slices.isEmpty())
726                return slices.firstEntry().getValue().canTranslate();
727        }
728
729        return false;
730    }
731
732    /**
733     * Translate the stack of specified C position.
734     */
735    public void translate(int c)
736    {
737        // easy optimizations
738        if ((c == 0) || isEmpty())
739            return;
740
741        synchronized (slices)
742        {
743            final Map<Integer, R> map = new HashMap<Integer, R>(slices);
744
745            slices.clear();
746            for (Entry<Integer, R> entry : map.entrySet())
747            {
748                final R roi = entry.getValue();
749                final int newC = roi.getC() + c;
750
751                // only positive value accepted
752                if (newC >= 0)
753                {
754                    roi.setC(newC);
755                    slices.put(Integer.valueOf(newC), roi);
756                }
757            }
758        }
759
760        // notify ROI changed
761        roiChanged(false);
762    }
763
764    @Override
765    public void translate(double dx, double dy, double dz, double dt, double dc)
766    {
767        beginUpdate();
768        try
769        {
770            translateC += dc;
771            // convert to integer
772            final int dci = (int) translateC;
773            // keep trace of not used floating part
774            translateC -= dci;
775
776            translate(dci);
777
778            modifyingSlice.acquireUninterruptibly();
779            try
780            {
781                synchronized (slices)
782                {
783                    for (R slice : slices.values())
784                        slice.translate(dx, dy, dz, dt);
785                }
786            }
787            finally
788            {
789                modifyingSlice.release();
790            }
791
792            // notify ROI changed because we modified slice 'internally'
793            if ((dx != 0d) || (dy != 0d) || (dz != 0d) || (dt != 0d))
794                roiChanged(false);
795        }
796        finally
797        {
798            endUpdate();
799        }
800    }
801
802    @Override
803    public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, int t, int c, boolean inclusive)
804    {
805        final R roi4d = getSlice(c);
806
807        if (roi4d != null)
808            return roi4d.getBooleanMask2D(x, y, width, height, z, t, inclusive);
809
810        return new boolean[width * height];
811    }
812
813    @Override
814    public BooleanMask2D getBooleanMask2D(int z, int t, int c, boolean inclusive)
815    {
816        final R roi4d = getSlice(c);
817
818        if (roi4d != null)
819            return roi4d.getBooleanMask2D(z, t, inclusive);
820
821        return new BooleanMask2D(new Rectangle(), new boolean[0]);
822    }
823
824    // called when one of the slice ROI changed
825    @Override
826    public void roiChanged(ROIEvent event)
827    {
828        // propagate children change event
829        sliceChanged(event);
830    }
831
832    // called when one of the slice ROI overlay changed
833    @Override
834    public void overlayChanged(OverlayEvent event)
835    {
836        // propagate children overlay change event
837        sliceOverlayChanged(event);
838    }
839
840    @Override
841    public Iterator<R> iterator()
842    {
843        return slices.values().iterator();
844    }
845
846    @Override
847    public boolean loadFromXML(Node node)
848    {
849        beginUpdate();
850        try
851        {
852            if (!super.loadFromXML(node))
853                return false;
854
855            // we don't need to save the 4D ROI class as the parent class already do it
856            clear();
857
858            for (Element e : XMLUtil.getElements(node, "slice"))
859            {
860                // faster than using complete XML serialization
861                final R slice = createSlice();
862
863                // error while reloading the ROI from XML
864                if ((slice == null) || !slice.loadFromXML(e))
865                    return false;
866
867                setSlice(slice.getC(), slice);
868            }
869        }
870        finally
871        {
872            endUpdate();
873        }
874
875        return true;
876    }
877
878    @Override
879    public boolean saveToXML(Node node)
880    {
881        if (!super.saveToXML(node))
882            return false;
883
884        synchronized (slices)
885        {
886            for (R slice : slices.values())
887            {
888                Element sliceNode = XMLUtil.addElement(node, "slice");
889
890                if (!slice.saveToXML(sliceNode))
891                    return false;
892            }
893        }
894
895        return true;
896    }
897
898    public class ROI5DStackPainter extends ROIPainter
899    {
900        R getSliceForCanvas(IcyCanvas canvas)
901        {
902            final int c = canvas.getPositionC();
903
904            if (c >= 0)
905                return getSlice(c);
906
907            return null;
908        }
909
910        @Override
911        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
912        {
913            super.paint(g, sequence, canvas);
914
915            if (isActiveFor(canvas))
916            {
917                if (canvas instanceof IcyCanvas3D)
918                {
919                    // TODO
920
921                }
922                else if (canvas instanceof IcyCanvas2D)
923                {
924                    // forward event to current slice
925                    final R slice = getSliceForCanvas(canvas);
926
927                    if (slice != null)
928                        slice.getOverlay().paint(g, sequence, canvas);
929                }
930            }
931        }
932
933        @Override
934        public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
935        {
936            // send event to parent first
937            super.keyPressed(e, imagePoint, canvas);
938
939            // then send it to active slice
940            if (isActiveFor(canvas))
941            {
942                // forward event to current slice
943                final R slice = getSliceForCanvas(canvas);
944
945                if (slice != null)
946                    slice.getOverlay().keyPressed(e, imagePoint, canvas);
947            }
948        }
949
950        @Override
951        public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
952        {
953            // send event to parent first
954            super.keyReleased(e, imagePoint, canvas);
955
956            // then send it to active slice
957            if (isActiveFor(canvas))
958            {
959                // forward event to current slice
960                final R slice = getSliceForCanvas(canvas);
961
962                if (slice != null)
963                    slice.getOverlay().keyReleased(e, imagePoint, canvas);
964            }
965        }
966
967        @Override
968        public void mouseEntered(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
969        {
970            // send event to parent first
971            super.mouseEntered(e, imagePoint, canvas);
972
973            // then send it to active slice
974            if (isActiveFor(canvas))
975            {
976                // forward event to current slice
977                final R slice = getSliceForCanvas(canvas);
978
979                if (slice != null)
980                    slice.getOverlay().mouseEntered(e, imagePoint, canvas);
981            }
982        }
983
984        @Override
985        public void mouseExited(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
986        {
987            // send event to parent first
988            super.mouseExited(e, imagePoint, canvas);
989
990            // then send it to active slice
991            if (isActiveFor(canvas))
992            {
993                // forward event to current slice
994                final R slice = getSliceForCanvas(canvas);
995
996                if (slice != null)
997                    slice.getOverlay().mouseExited(e, imagePoint, canvas);
998            }
999        }
1000
1001        @Override
1002        public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1003        {
1004            // send event to parent first
1005            super.mouseMove(e, imagePoint, canvas);
1006
1007            // then send it to active slice
1008            if (isActiveFor(canvas))
1009            {
1010                // forward event to current slice
1011                final R slice = getSliceForCanvas(canvas);
1012
1013                if (slice != null)
1014                    slice.getOverlay().mouseMove(e, imagePoint, canvas);
1015            }
1016        }
1017
1018        @Override
1019        public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1020        {
1021            // send event to parent first
1022            super.mouseDrag(e, imagePoint, canvas);
1023
1024            // then send it to active slice
1025            if (isActiveFor(canvas))
1026            {
1027                // forward event to current slice
1028                final R slice = getSliceForCanvas(canvas);
1029
1030                if (slice != null)
1031                    slice.getOverlay().mouseDrag(e, imagePoint, canvas);
1032            }
1033        }
1034
1035        @Override
1036        public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1037        {
1038            // send event to parent first
1039            super.mousePressed(e, imagePoint, canvas);
1040
1041            // then send it to active slice
1042            if (isActiveFor(canvas))
1043            {
1044                // forward event to current slice
1045                final R slice = getSliceForCanvas(canvas);
1046
1047                if (slice != null)
1048                    slice.getOverlay().mousePressed(e, imagePoint, canvas);
1049            }
1050        }
1051
1052        @Override
1053        public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1054        {
1055            // send event to parent first
1056            super.mouseReleased(e, imagePoint, canvas);
1057
1058            // then send it to active slice
1059            if (isActiveFor(canvas))
1060            {
1061                // forward event to current slice
1062                final R slice = getSliceForCanvas(canvas);
1063
1064                if (slice != null)
1065                    slice.getOverlay().mouseReleased(e, imagePoint, canvas);
1066            }
1067        }
1068
1069        @Override
1070        public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1071        {
1072            // send event to parent first
1073            super.mouseClick(e, imagePoint, canvas);
1074
1075            // then send it to active slice
1076            if (isActiveFor(canvas))
1077            {
1078                // forward event to current slice
1079                final R slice = getSliceForCanvas(canvas);
1080
1081                if (slice != null)
1082                    slice.getOverlay().mouseClick(e, imagePoint, canvas);
1083            }
1084        }
1085
1086        @Override
1087        public void mouseWheelMoved(MouseWheelEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
1088        {
1089            // send event to parent first
1090            super.mouseWheelMoved(e, imagePoint, canvas);
1091
1092            // then send it to active slice
1093            if (isActiveFor(canvas))
1094            {
1095                // forward event to current slice
1096                final R slice = getSliceForCanvas(canvas);
1097
1098                if (slice != null)
1099                    slice.getOverlay().mouseWheelMoved(e, imagePoint, canvas);
1100            }
1101        }
1102    }
1103}