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.gui.main;
020
021import java.awt.Window;
022import java.awt.event.WindowAdapter;
023import java.awt.event.WindowEvent;
024import java.beans.PropertyVetoException;
025import java.lang.ref.WeakReference;
026import java.util.ArrayList;
027import java.util.HashSet;
028import java.util.List;
029
030import javax.swing.JFrame;
031import javax.swing.JInternalFrame;
032import javax.swing.JLayeredPane;
033import javax.swing.event.EventListenerList;
034
035import icy.common.listener.AcceptListener;
036import icy.common.listener.weak.WeakListener;
037import icy.gui.frame.IcyFrame;
038import icy.gui.inspector.InspectorPanel;
039import icy.gui.inspector.LayersPanel;
040import icy.gui.inspector.RoisPanel;
041import icy.gui.main.MainEvent.MainEventSourceType;
042import icy.gui.main.MainEvent.MainEventType;
043import icy.gui.menu.ApplicationMenu;
044import icy.gui.menu.ROITask;
045import icy.gui.menu.ToolRibbonTask;
046import icy.gui.viewer.Viewer;
047import icy.gui.viewer.ViewerAdapter;
048import icy.gui.viewer.ViewerEvent;
049import icy.gui.viewer.ViewerListener;
050import icy.image.IcyBufferedImage;
051import icy.image.lut.LUT;
052import icy.imagej.ImageJWrapper;
053import icy.main.Icy;
054import icy.painter.Overlay;
055import icy.painter.OverlayWrapper;
056import icy.painter.Painter;
057import icy.plugin.abstract_.Plugin;
058import icy.preferences.GeneralPreferences;
059import icy.preferences.IcyPreferences;
060import icy.preferences.XMLPreferences;
061import icy.roi.ROI;
062import icy.search.SearchEngine;
063import icy.sequence.Sequence;
064import icy.sequence.SequenceAdapter;
065import icy.sequence.SequenceEvent;
066import icy.sequence.SequenceListener;
067import icy.swimmingPool.SwimmingPool;
068import icy.system.thread.ThreadUtil;
069import icy.undo.IcyUndoManager;
070import icy.util.StringUtil;
071
072/**
073 * MainInterfaceGui
074 * 
075 * @author Fabrice de Chaumont & Stephane
076 */
077public class MainInterfaceGui implements MainInterface
078{
079    private class WeakAcceptListener extends WeakListener<AcceptListener> implements AcceptListener
080    {
081        public WeakAcceptListener(AcceptListener listener)
082        {
083            super(listener);
084        }
085
086        @Override
087        public void removeListener(Object source)
088        {
089            internalRemoveCanExitListener(this);
090        }
091
092        @Override
093        public boolean accept(Object source)
094        {
095            final AcceptListener listener = getListener();
096
097            if (listener != null)
098                return listener.accept(source);
099
100            return true;
101        }
102    }
103
104    // private final UpdateEventHandler updater;
105
106    private final EventListenerList listeners;
107
108    // use specific list for faster listeners retrieve
109    private final List<MainListener> mainListeners;
110    private final List<GlobalViewerListener> globalViewerListeners;
111    private final List<GlobalSequenceListener> globalSequenceListeners;
112    private final List<GlobalROIListener> globalROIListeners;
113    private final List<GlobalOverlayListener> globalOverlayListeners;
114
115    /**
116     * used to generate focused sequence & viewer events
117     */
118    private final ViewerListener activeViewerListener;
119    private final SequenceListener sequenceListener;
120
121    private final List<Viewer> viewers;
122    private final List<Sequence> sequences;
123    private final List<WeakReference<Plugin>> activePlugins;
124
125    private final SwimmingPool swimmingPool;
126    private final TaskFrameManager taskFrameManager;
127
128    MainFrame mainFrame;
129
130    Viewer previousActiveViewer;
131    Viewer activeViewer;
132    Sequence activeSequence;
133
134    /**
135     * Take care that MainInterface constructor do not call the {@link Icy#getMainInterface()} method.<br>
136     * We use a separate {@link #init()} for that purpose.
137     */
138    public MainInterfaceGui()
139    {
140        listeners = new EventListenerList();
141        mainListeners = new ArrayList<MainListener>();
142        globalViewerListeners = new ArrayList<GlobalViewerListener>();
143        globalSequenceListeners = new ArrayList<GlobalSequenceListener>();
144        globalROIListeners = new ArrayList<GlobalROIListener>();
145        globalOverlayListeners = new ArrayList<GlobalOverlayListener>();
146
147        viewers = new ArrayList<Viewer>();
148        sequences = new ArrayList<Sequence>();
149        activePlugins = new ArrayList<WeakReference<Plugin>>();
150        swimmingPool = new SwimmingPool();
151        taskFrameManager = new TaskFrameManager();
152
153        // active viewer listener
154        activeViewerListener = new ViewerAdapter()
155        {
156            @Override
157            public void viewerChanged(ViewerEvent event)
158            {
159                activeViewerChanged(event);
160            }
161        };
162
163        // global sequence listener
164        sequenceListener = new SequenceAdapter()
165        {
166            @Override
167            public void sequenceChanged(SequenceEvent event)
168            {
169                MainInterfaceGui.this.sequenceChanged(event);
170            }
171        };
172
173        mainFrame = null;
174
175        previousActiveViewer = null;
176        activeViewer = null;
177        activeSequence = null;
178    }
179
180    @Override
181    public void init()
182    {
183        // build main frame
184        mainFrame = new MainFrame();
185        mainFrame.init();
186        mainFrame.addWindowListener(new WindowAdapter()
187        {
188            @Override
189            public void windowClosing(WindowEvent e)
190            {
191                // exit application
192                Icy.exit(false);
193            }
194        });
195
196        taskFrameManager.init();
197    }
198
199    @Override
200    public boolean isHeadLess()
201    {
202        // we are not head less with this interface
203        return false;
204    }
205
206    @Override
207    public void addSequence(Sequence sequence)
208    {
209        if (sequence != null)
210        {
211            final Sequence seq = sequence;
212
213            // thread safe
214            ThreadUtil.invokeLater(new Runnable()
215            {
216                @Override
217                public void run()
218                {
219                    new Viewer(seq);
220                }
221            });
222        }
223    }
224
225    @Override
226    public ArrayList<JFrame> getExternalFrames()
227    {
228        final ArrayList<JFrame> result = new ArrayList<JFrame>();
229        final Window[] windows = Window.getWindows();
230
231        for (Window w : windows)
232            if (w instanceof JFrame)
233                result.add((JFrame) w);
234
235        return result;
236    }
237
238    @Override
239    public ArrayList<JInternalFrame> getInternalFrames()
240    {
241        if (mainFrame == null)
242            return new ArrayList<JInternalFrame>();
243
244        return mainFrame.getInternalFrames();
245
246    }
247
248    /**
249     * @return the preferences
250     */
251    @Override
252    public XMLPreferences getPreferences()
253    {
254        return IcyPreferences.applicationRoot();
255    }
256
257    @Override
258    public InspectorPanel getInspector()
259    {
260        if (mainFrame == null)
261            return null;
262
263        return mainFrame.getInspector();
264    }
265
266    @Override
267    public RoisPanel getRoisPanel()
268    {
269        if (mainFrame == null)
270            return null;
271
272        final InspectorPanel inspector = mainFrame.getInspector();
273        if (inspector == null)
274            return null;
275
276        return inspector.getRoisPanel();
277    }
278
279    @Override
280    public LayersPanel getLayersPanel()
281    {
282        if (mainFrame == null)
283            return null;
284
285        final InspectorPanel inspector = mainFrame.getInspector();
286        if (inspector == null)
287            return null;
288
289        return inspector.getLayersPanel();
290    }
291
292    @Override
293    public ArrayList<Plugin> getActivePlugins()
294    {
295        final ArrayList<Plugin> result = new ArrayList<Plugin>();
296
297        synchronized (activePlugins)
298        {
299            for (WeakReference<Plugin> ref : activePlugins)
300            {
301                final Plugin plugin = ref.get();
302
303                if (plugin != null)
304                    result.add(plugin);
305            }
306        }
307
308        return result;
309    }
310
311    @Override
312    public LUT getActiveLUT()
313    {
314        if (activeViewer != null)
315            return activeViewer.getLut();
316
317        return null;
318    }
319
320    @Override
321    public Viewer getActiveViewer()
322    {
323        return activeViewer;
324    }
325
326    @Override
327    public Sequence getActiveSequence()
328    {
329        return activeSequence;
330    }
331
332    @Override
333    public IcyBufferedImage getActiveImage()
334    {
335        if (activeViewer != null)
336            return activeViewer.getCurrentImage();
337
338        return null;
339    }
340
341    @Override
342    @Deprecated
343    public Viewer getFocusedViewer()
344    {
345        return getActiveViewer();
346    }
347
348    @Override
349    @Deprecated
350    public Sequence getFocusedSequence()
351    {
352        return getActiveSequence();
353    }
354
355    @Override
356    @Deprecated
357    public IcyBufferedImage getFocusedImage()
358    {
359        return getActiveImage();
360    }
361
362    @Override
363    public IcyUndoManager getUndoManager()
364    {
365        if (activeSequence != null)
366            return activeSequence.getUndoManager();
367
368        return null;
369    }
370
371    @Override
372    public boolean undo()
373    {
374        if (activeSequence != null)
375            return activeSequence.undo();
376
377        return false;
378    }
379
380    @Override
381    public boolean redo()
382    {
383        if (activeSequence != null)
384            return activeSequence.redo();
385
386        return false;
387    }
388
389    @Override
390    public ArrayList<Viewer> getViewers()
391    {
392        synchronized (viewers)
393        {
394            return new ArrayList<Viewer>(viewers);
395        }
396    }
397
398    @Override
399    public synchronized void setActiveViewer(Viewer viewer)
400    {
401        if (activeViewer == viewer)
402            return;
403
404        // got a previously active viewer ?
405        if (activeViewer != null)
406        {
407            // remove active viewer listener
408            activeViewer.removeListener(activeViewerListener);
409
410            // force previous viewer internal frame to release focus
411            try
412            {
413                activeViewer.getInternalFrame().setSelected(false);
414            }
415            catch (PropertyVetoException e)
416            {
417                // ignore
418            }
419        }
420
421        previousActiveViewer = activeViewer;
422        activeViewer = viewer;
423
424        // add active viewer listener
425        if (activeViewer != null)
426            activeViewer.addListener(activeViewerListener);
427
428        // activation changed
429        viewerActivationChanged(previousActiveViewer, activeViewer);
430    }
431
432    @Override
433    @Deprecated
434    public synchronized void setFocusedViewer(Viewer viewer)
435    {
436        setActiveViewer(viewer);
437    }
438
439    @Override
440    public synchronized void addToDesktopPane(JInternalFrame internalFrame)
441    {
442        getDesktopPane().add(internalFrame, JLayeredPane.DEFAULT_LAYER);
443    }
444
445    @Override
446    public IcyDesktopPane getDesktopPane()
447    {
448        if (mainFrame == null)
449            return null;
450
451        return mainFrame.getDesktopPane();
452    }
453
454    @Override
455    public ApplicationMenu getApplicationMenu()
456    {
457        if (mainFrame == null)
458            return null;
459
460        return mainFrame.getApplicationMenu();
461    }
462
463    @Override
464    public TaskFrameManager getTaskWindowManager()
465    {
466        return taskFrameManager;
467    }
468
469    private WeakReference<Plugin> getPluginReference(Plugin plugin)
470    {
471        synchronized (activePlugins)
472        {
473            for (WeakReference<Plugin> ref : activePlugins)
474                if (ref.get() == plugin)
475                    return ref;
476        }
477
478        return null;
479    }
480
481    @Deprecated
482    @Override
483    public void registerExternalFrame(JFrame frame)
484    {
485
486    }
487
488    @Deprecated
489    @Override
490    public void unRegisterExternalFrame(JFrame frame)
491    {
492
493    }
494
495    @Override
496    public synchronized void registerPlugin(Plugin plugin)
497    {
498        synchronized (activePlugins)
499        {
500            activePlugins.add(new WeakReference<Plugin>(plugin));
501        }
502
503        // plugin opened
504        pluginStarted(plugin);
505    }
506
507    @Override
508    public synchronized void unRegisterPlugin(Plugin plugin)
509    {
510        final WeakReference<Plugin> ref = getPluginReference(plugin);
511
512        synchronized (activePlugins)
513        {
514            // reference found
515            if (ref != null)
516                activePlugins.remove(ref);
517        }
518
519        // plugin closed
520        pluginEnded(plugin);
521    }
522
523    @Override
524    public synchronized void registerViewer(Viewer viewer)
525    {
526        if (viewer == null)
527            return;
528
529        // viewer opened
530        viewerOpened(viewer);
531    }
532
533    @Override
534    public synchronized void unRegisterViewer(Viewer viewer)
535    {
536        if (viewer == null)
537            return;
538
539        // viewer closed
540        viewerClosed(viewer);
541
542        // no more opened viewer ?
543        if (viewers.isEmpty())
544            // set focus to null
545            setActiveViewer(null);
546        else
547        {
548            final IcyFrame frame = IcyFrame.findIcyFrame(getDesktopPane().getSelectedFrame());
549
550            if (frame instanceof Viewer)
551                ((Viewer) frame).requestFocus();
552            else
553            {
554                // it was the active viewer ?
555                if (activeViewer == viewer)
556                {
557                    // restore focus to previous active
558                    if (previousActiveViewer != null)
559                    {
560                        setActiveViewer(previousActiveViewer);
561                        // no more previous active now
562                        previousActiveViewer = null;
563                    }
564                    else
565                        // or just focus another one
566                        setActiveViewer(viewers.get(viewers.size() - 1));
567                }
568            }
569        }
570    }
571
572    @Override
573    @Deprecated
574    public MainFrame getFrame()
575    {
576        return getMainFrame();
577    }
578
579    @Override
580    public MainFrame getMainFrame()
581    {
582        return mainFrame;
583    }
584
585    @Override
586    public SearchEngine getSearchEngine()
587    {
588        return mainFrame.getSearchBar().getSearchEngine();
589    }
590
591    @Override
592    public void closeSequence(Sequence sequence)
593    {
594        // use copy as this actually modify viewers list
595        for (Viewer v : getViewers())
596            if (v.getSequence() == sequence)
597                v.close();
598    }
599
600    @Deprecated
601    @Override
602    public void closeViewersOfSequence(Sequence sequence)
603    {
604        closeSequence(sequence);
605    }
606
607    @Override
608    public synchronized void closeAllViewers()
609    {
610        // use copy as this actually modify viewers list
611        for (Viewer viewer : getViewers())
612            viewer.close();
613    }
614
615    @Override
616    public Viewer getFirstViewerContaining(ROI roi)
617    {
618        return getFirstViewer(getFirstSequenceContaining(roi));
619    }
620
621    @Deprecated
622    @Override
623    public Viewer getFirstViewerContaining(Painter painter)
624    {
625        return getFirstViewer(getFirstSequenceContaining(painter));
626    }
627
628    @Override
629    public Viewer getFirstViewerContaining(Overlay overlay)
630    {
631        return getFirstViewer(getFirstSequenceContaining(overlay));
632    }
633
634    @Override
635    public Viewer getFirstViewer(Sequence sequence)
636    {
637        if (sequence != null)
638        {
639            for (Viewer viewer : getViewers())
640                if (viewer.getSequence() == sequence)
641                    return viewer;
642        }
643
644        return null;
645    }
646
647    @Override
648    public ArrayList<Viewer> getViewers(Sequence sequence)
649    {
650        final ArrayList<Viewer> result = new ArrayList<Viewer>();
651
652        for (Viewer v : getViewers())
653            if (v.getSequence() == sequence)
654                result.add(v);
655
656        return result;
657    }
658
659    @Override
660    public boolean isUniqueViewer(Viewer viewer)
661    {
662        final List<Viewer> viewers = getViewers(viewer.getSequence());
663
664        return (viewers.size() == 1) && (viewers.get(0) == viewer);
665    }
666
667    @Override
668    public ArrayList<Sequence> getSequences()
669    {
670        synchronized (sequences)
671        {
672            return new ArrayList<Sequence>(sequences);
673        }
674    }
675
676    @Override
677    public ArrayList<Sequence> getSequences(String name)
678    {
679        final ArrayList<Sequence> result = new ArrayList<Sequence>();
680
681        synchronized (viewers)
682        {
683            for (Viewer viewer : viewers)
684            {
685                final Sequence sequence = viewer.getSequence();
686
687                // matching name and no duplicate
688                if (!result.contains(sequence) && StringUtil.equals(name, sequence.getName()))
689                    result.add(sequence);
690            }
691        }
692
693        return result;
694    }
695
696    @Override
697    public boolean isOpened(Sequence sequence)
698    {
699        return getSequences().contains(sequence);
700    }
701
702    @Deprecated
703    @Override
704    public Sequence getFirstSequencesContaining(ROI roi)
705    {
706        return getFirstSequenceContaining(roi);
707    }
708
709    @Override
710    public Sequence getFirstSequenceContaining(ROI roi)
711    {
712        for (Sequence seq : getSequences())
713            if (seq.contains(roi))
714                return seq;
715
716        return null;
717    }
718
719    @Deprecated
720    @Override
721    public Sequence getFirstSequencesContaining(Painter painter)
722    {
723        return getFirstSequenceContaining(painter);
724    }
725
726    @Deprecated
727    @Override
728    public Sequence getFirstSequenceContaining(Painter painter)
729    {
730        for (Sequence seq : getSequences())
731            if (seq.contains(painter))
732                return seq;
733
734        return null;
735    }
736
737    @Override
738    public Sequence getFirstSequenceContaining(Overlay overlay)
739    {
740        for (Sequence seq : getSequences())
741            if (seq.contains(overlay))
742                return seq;
743
744        return null;
745    }
746
747    @Override
748    public ArrayList<Sequence> getSequencesContaining(ROI roi)
749    {
750        final ArrayList<Sequence> result = getSequences();
751
752        for (int i = result.size() - 1; i >= 0; i--)
753            if (!result.get(i).contains(roi))
754                result.remove(i);
755
756        return result;
757    }
758
759    @Override
760    @Deprecated
761    public ArrayList<Sequence> getSequencesContaining(Painter painter)
762    {
763        final ArrayList<Sequence> result = getSequences();
764
765        for (int i = result.size() - 1; i >= 0; i--)
766            if (!result.get(i).contains(painter))
767                result.remove(i);
768
769        return result;
770    }
771
772    @Override
773    public List<Sequence> getSequencesContaining(Overlay overlay)
774    {
775        final ArrayList<Sequence> result = getSequences();
776
777        for (int i = result.size() - 1; i >= 0; i--)
778            if (!result.get(i).contains(overlay))
779                result.remove(i);
780
781        return result;
782    }
783
784    @Override
785    public ArrayList<ROI> getROIs()
786    {
787        // HashSet is better suited for add elements
788        final HashSet<ROI> result = new HashSet<ROI>();
789
790        for (Sequence seq : getSequences())
791            for (ROI roi : seq.getROISet())
792                result.add(roi);
793
794        // TODO: add ROI from swimming pool ?
795
796        return new ArrayList<ROI>(result);
797    }
798
799    @Override
800    @Deprecated
801    public ROI getROI(Painter painter)
802    {
803        if (painter instanceof Overlay)
804            return getROI((Overlay) painter);
805
806        return null;
807    }
808
809    @Override
810    public ROI getROI(Overlay overlay)
811    {
812        final List<ROI> rois = getROIs();
813
814        for (ROI roi : rois)
815            if (roi.getOverlay() == overlay)
816                return roi;
817
818        return null;
819    }
820
821    @Override
822    @Deprecated
823    public ArrayList<Painter> getPainters()
824    {
825        // HashSet better suited for add element
826        final HashSet<Painter> result = new HashSet<Painter>();
827
828        for (Sequence seq : getSequences())
829            result.addAll(seq.getPainterSet());
830
831        // TODO: add Painter from swimming pool ?
832
833        return new ArrayList<Painter>();
834    }
835
836    @Override
837    public List<Overlay> getOverlays()
838    {
839        // HashSet better suited for add element
840        final HashSet<Overlay> result = new HashSet<Overlay>();
841
842        for (Sequence seq : getSequences())
843            result.addAll(seq.getOverlaySet());
844
845        // TODO: add Overlay from swimming pool ?
846
847        return new ArrayList<Overlay>();
848    }
849
850    @Override
851    public SwimmingPool getSwimmingPool()
852    {
853        return swimmingPool;
854    }
855
856    @Override
857    public ImageJWrapper getImageJ()
858    {
859        if (mainFrame == null)
860            return null;
861
862        return mainFrame.getMainRibbon().getImageJ();
863    }
864
865    @Override
866    public String getSelectedTool()
867    {
868        if (mainFrame == null)
869            return null;
870
871        return getROIRibbonTask().getSelected();
872    }
873
874    @Override
875    public void setSelectedTool(String command)
876    {
877        if (mainFrame != null)
878            getROIRibbonTask().setSelected(command);
879    }
880
881    @Override
882    public ROITask getROIRibbonTask()
883    {
884        if (mainFrame == null)
885            return null;
886
887        return mainFrame.getMainRibbon().getROIRibbonTask();
888    }
889
890    @Deprecated
891    @Override
892    public ToolRibbonTask getToolRibbon()
893    {
894        if (mainFrame == null)
895            return null;
896
897        return mainFrame.getMainRibbon().getToolRibbon();
898    }
899
900    @Override
901    public boolean isAlwaysOnTop()
902    {
903        if (mainFrame == null)
904            return false;
905
906        return mainFrame.isAlwaysOnTop();
907    }
908
909    @Override
910    public void setAlwaysOnTop(boolean value)
911    {
912        if (mainFrame != null)
913            mainFrame.setAlwaysOnTop(value);
914    }
915
916    @Override
917    public boolean isDetachedMode()
918    {
919        if (mainFrame == null)
920            return false;
921
922        return mainFrame.isDetachedMode();
923    }
924
925    @Override
926    public void setDetachedMode(boolean value)
927    {
928        if (mainFrame != null)
929            mainFrame.setDetachedMode(value);
930    }
931
932    @Override
933    @Deprecated
934    public synchronized void addListener(MainListener listener)
935    {
936        if (listener != null)
937            mainListeners.add(listener);
938    }
939
940    @Override
941    @Deprecated
942    public synchronized void removeListener(MainListener listener)
943    {
944        mainListeners.remove(listener);
945    }
946
947    @Override
948    public synchronized void addGlobalViewerListener(GlobalViewerListener listener)
949    {
950        if (listener != null)
951            globalViewerListeners.add(listener);
952    }
953
954    @Override
955    public synchronized void removeGlobalViewerListener(GlobalViewerListener listener)
956    {
957        globalViewerListeners.remove(listener);
958    }
959
960    @Override
961    public synchronized void addGlobalSequenceListener(GlobalSequenceListener listener)
962    {
963        if (listener != null)
964            globalSequenceListeners.add(listener);
965    }
966
967    @Override
968    public synchronized void removeGlobalSequenceListener(GlobalSequenceListener listener)
969    {
970        globalSequenceListeners.remove(listener);
971    }
972
973    @Override
974    public synchronized void addGlobalROIListener(GlobalROIListener listener)
975    {
976        if (listener != null)
977            globalROIListeners.add(listener);
978    }
979
980    @Override
981    public synchronized void removeGlobalROIListener(GlobalROIListener listener)
982    {
983        globalROIListeners.remove(listener);
984    }
985
986    @Override
987    public synchronized void addGlobalOverlayListener(GlobalOverlayListener listener)
988    {
989        if (listener != null)
990            globalOverlayListeners.add(listener);
991    }
992
993    @Override
994    public synchronized void removeGlobalOverlayListener(GlobalOverlayListener listener)
995    {
996        globalOverlayListeners.remove(listener);
997    }
998
999    @Override
1000    public synchronized void addGlobalPluginListener(GlobalPluginListener listener)
1001    {
1002        if (listener != null)
1003            listeners.add(GlobalPluginListener.class, listener);
1004    }
1005
1006    @Override
1007    public synchronized void removeGlobalPluginListener(GlobalPluginListener listener)
1008    {
1009        listeners.remove(GlobalPluginListener.class, listener);
1010    }
1011
1012    @Override
1013    public synchronized void addCanExitListener(AcceptListener listener)
1014    {
1015        if (listener != null)
1016            listeners.add(WeakAcceptListener.class, new WeakAcceptListener(listener));
1017    }
1018
1019    @Override
1020    public synchronized void removeCanExitListener(AcceptListener listener)
1021    {
1022        // we use weak reference so we have to find base listener...
1023        for (WeakAcceptListener l : listeners.getListeners(WeakAcceptListener.class))
1024            if (listener == l.getListener())
1025                internalRemoveCanExitListener(l);
1026    }
1027
1028    public synchronized void internalRemoveCanExitListener(WeakAcceptListener listener)
1029    {
1030        listeners.remove(WeakAcceptListener.class, listener);
1031    }
1032
1033    @Deprecated
1034    @Override
1035    public synchronized void addFocusedViewerListener(FocusedViewerListener listener)
1036    {
1037        listeners.add(FocusedViewerListener.class, listener);
1038    }
1039
1040    @Deprecated
1041    @Override
1042    public synchronized void removeFocusedViewerListener(FocusedViewerListener listener)
1043    {
1044        listeners.remove(FocusedViewerListener.class, listener);
1045    }
1046
1047    @Deprecated
1048    @Override
1049    public synchronized void addFocusedSequenceListener(FocusedSequenceListener listener)
1050    {
1051        listeners.add(FocusedSequenceListener.class, listener);
1052    }
1053
1054    @Deprecated
1055    @Override
1056    public synchronized void removeFocusedSequenceListener(FocusedSequenceListener listener)
1057    {
1058        listeners.remove(FocusedSequenceListener.class, listener);
1059    }
1060
1061    @Override
1062    public synchronized void addActiveViewerListener(ActiveViewerListener listener)
1063    {
1064        listeners.add(ActiveViewerListener.class, listener);
1065    }
1066
1067    @Override
1068    public synchronized void removeActiveViewerListener(ActiveViewerListener listener)
1069    {
1070        listeners.remove(ActiveViewerListener.class, listener);
1071    }
1072
1073    @Override
1074    public synchronized void addActiveSequenceListener(ActiveSequenceListener listener)
1075    {
1076        listeners.add(ActiveSequenceListener.class, listener);
1077    }
1078
1079    @Override
1080    public synchronized void removeActiveSequenceListener(ActiveSequenceListener listener)
1081    {
1082        listeners.remove(ActiveSequenceListener.class, listener);
1083    }
1084
1085    /**
1086     * fire plugin opened event
1087     */
1088    @SuppressWarnings("deprecation")
1089    private void firePluginStartedEvent(Plugin plugin)
1090    {
1091        for (GlobalPluginListener listener : listeners.getListeners(GlobalPluginListener.class))
1092            listener.pluginStarted(plugin);
1093
1094        // backward compatibility
1095        final MainEvent event = new MainEvent(MainEventSourceType.PLUGIN, MainEventType.OPENED, plugin);
1096        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1097            listener.pluginOpened(event);
1098    }
1099
1100    /**
1101     * fire plugin closed event
1102     */
1103    @SuppressWarnings("deprecation")
1104    private void firePluginEndedEvent(Plugin plugin)
1105    {
1106        for (GlobalPluginListener listener : listeners.getListeners(GlobalPluginListener.class))
1107            listener.pluginEnded(plugin);
1108
1109        // backward compatibility
1110        final MainEvent event = new MainEvent(MainEventSourceType.PLUGIN, MainEventType.CLOSED, plugin);
1111        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1112            listener.pluginClosed(event);
1113    }
1114
1115    /**
1116     * fire viewer opened event
1117     */
1118    @SuppressWarnings("deprecation")
1119    private void fireViewerOpenedEvent(Viewer viewer)
1120    {
1121        for (GlobalViewerListener listener : new ArrayList<GlobalViewerListener>(globalViewerListeners))
1122            listener.viewerOpened(viewer);
1123
1124        // backward compatibility
1125        final MainEvent event = new MainEvent(MainEventSourceType.VIEWER, MainEventType.OPENED, viewer);
1126        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1127            listener.viewerOpened(event);
1128    }
1129
1130    /**
1131     * fire viewer close event
1132     */
1133    @SuppressWarnings("deprecation")
1134    private void fireViewerClosedEvent(Viewer viewer)
1135    {
1136        for (GlobalViewerListener listener : new ArrayList<GlobalViewerListener>(globalViewerListeners))
1137            listener.viewerClosed(viewer);
1138
1139        // backward compatibility
1140        final MainEvent event = new MainEvent(MainEventSourceType.VIEWER, MainEventType.CLOSED, viewer);
1141        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1142            listener.viewerClosed(event);
1143    }
1144
1145    /**
1146     * fire viewer deactive event
1147     */
1148    private void fireViewerDeactivatedEvent(Viewer viewer)
1149    {
1150        for (ActiveViewerListener listener : listeners.getListeners(ActiveViewerListener.class))
1151            listener.viewerDeactivated(viewer);
1152    }
1153
1154    /**
1155     * fire viewer active event
1156     */
1157    @SuppressWarnings("deprecation")
1158    private void fireViewerActivatedEvent(Viewer viewer)
1159    {
1160        for (ActiveViewerListener listener : listeners.getListeners(ActiveViewerListener.class))
1161            listener.viewerActivated(viewer);
1162
1163        // backward compatibility
1164        final MainEvent event = new MainEvent(MainEventSourceType.VIEWER, MainEventType.FOCUSED, viewer);
1165        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1166            listener.viewerFocused(event);
1167        for (FocusedViewerListener listener : listeners.getListeners(FocusedViewerListener.class))
1168            listener.focusChanged(viewer);
1169    }
1170
1171    /**
1172     * fire sequence opened event
1173     */
1174    @SuppressWarnings("deprecation")
1175    private void fireSequenceOpenedEvent(Sequence sequence)
1176    {
1177        for (GlobalSequenceListener listener : new ArrayList<GlobalSequenceListener>(globalSequenceListeners))
1178            listener.sequenceOpened(sequence);
1179
1180        // backward compatibility
1181        final MainEvent event = new MainEvent(MainEventSourceType.SEQUENCE, MainEventType.OPENED, sequence);
1182        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1183            listener.sequenceOpened(event);
1184    }
1185
1186    /**
1187     * fire sequence active event
1188     */
1189    @SuppressWarnings("deprecation")
1190    private void fireSequenceClosedEvent(Sequence sequence)
1191    {
1192        for (GlobalSequenceListener listener : new ArrayList<GlobalSequenceListener>(globalSequenceListeners))
1193            listener.sequenceClosed(sequence);
1194
1195        // backward compatibility
1196        final MainEvent event = new MainEvent(MainEventSourceType.SEQUENCE, MainEventType.CLOSED, sequence);
1197        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1198            listener.sequenceClosed(event);
1199    }
1200
1201    /**
1202     * fire sequence deactive event
1203     */
1204    private void fireSequenceDeactivatedEvent(Sequence sequence)
1205    {
1206        for (ActiveSequenceListener listener : listeners.getListeners(ActiveSequenceListener.class))
1207            listener.sequenceDeactivated(sequence);
1208    }
1209
1210    /**
1211     * fire sequence active event
1212     */
1213    @SuppressWarnings("deprecation")
1214    private void fireSequenceActivatedEvent(Sequence sequence)
1215    {
1216        for (ActiveSequenceListener listener : listeners.getListeners(ActiveSequenceListener.class))
1217            listener.sequenceActivated(sequence);
1218
1219        // backward compatibility
1220        final MainEvent event = new MainEvent(MainEventSourceType.SEQUENCE, MainEventType.FOCUSED, sequence);
1221        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1222            listener.sequenceFocused(event);
1223        for (FocusedSequenceListener listener : listeners.getListeners(FocusedSequenceListener.class))
1224            listener.focusChanged(sequence);
1225    }
1226
1227    /**
1228     * fire ROI added event
1229     */
1230    @SuppressWarnings("deprecation")
1231    private void fireRoiAddedEvent(ROI roi)
1232    {
1233        for (GlobalROIListener listener : new ArrayList<GlobalROIListener>(globalROIListeners))
1234            listener.roiAdded(roi);
1235
1236        // backward compatibility
1237        final MainEvent event = new MainEvent(MainEventSourceType.ROI, MainEventType.ADDED, roi);
1238        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1239            listener.roiAdded(event);
1240    }
1241
1242    /**
1243     * fire ROI removed event
1244     */
1245    @SuppressWarnings("deprecation")
1246    private void fireRoiRemovedEvent(ROI roi)
1247    {
1248        for (GlobalROIListener listener : new ArrayList<GlobalROIListener>(globalROIListeners))
1249            listener.roiRemoved(roi);
1250
1251        // backward compatibility
1252        final MainEvent event = new MainEvent(MainEventSourceType.ROI, MainEventType.REMOVED, roi);
1253        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1254            listener.roiRemoved(event);
1255    }
1256
1257    /**
1258     * fire painter added event
1259     */
1260    @SuppressWarnings("deprecation")
1261    private void fireOverlayAddedEvent(Overlay overlay)
1262    {
1263        for (GlobalOverlayListener listener : new ArrayList<GlobalOverlayListener>(globalOverlayListeners))
1264            listener.overlayAdded(overlay);
1265
1266        // backward compatibility
1267        final Painter painter;
1268
1269        if (overlay instanceof OverlayWrapper)
1270            painter = ((OverlayWrapper) overlay).getPainter();
1271        else
1272            painter = overlay;
1273
1274        final MainEvent event = new MainEvent(MainEventSourceType.PAINTER, MainEventType.ADDED, painter);
1275        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1276            listener.painterAdded(event);
1277    }
1278
1279    /**
1280     * fire painter removed event
1281     */
1282    @SuppressWarnings("deprecation")
1283    private void fireOverlayRemovedEvent(Overlay overlay)
1284    {
1285        for (GlobalOverlayListener listener : new ArrayList<GlobalOverlayListener>(globalOverlayListeners))
1286            listener.overlayRemoved(overlay);
1287
1288        // backward compatibility
1289        final Painter painter;
1290
1291        if (overlay instanceof OverlayWrapper)
1292            painter = ((OverlayWrapper) overlay).getPainter();
1293        else
1294            painter = overlay;
1295
1296        final MainEvent event = new MainEvent(MainEventSourceType.PAINTER, MainEventType.REMOVED, painter);
1297        for (MainListener listener : new ArrayList<MainListener>(mainListeners))
1298            listener.painterRemoved(event);
1299    }
1300
1301    /**
1302     * fire active viewer changed event
1303     */
1304    @SuppressWarnings("deprecation")
1305    private void fireActiveViewerChangedEvent(ViewerEvent event)
1306    {
1307        for (ActiveViewerListener listener : listeners.getListeners(ActiveViewerListener.class))
1308            listener.activeViewerChanged(event);
1309
1310        // backward compatibility
1311        for (FocusedViewerListener listener : listeners.getListeners(FocusedViewerListener.class))
1312            listener.focusedViewerChanged(event);
1313    }
1314
1315    /**
1316     * fire active sequence changed event
1317     */
1318    @SuppressWarnings("deprecation")
1319    private void fireActiveSequenceChangedEvent(SequenceEvent event)
1320    {
1321        for (ActiveSequenceListener listener : listeners.getListeners(ActiveSequenceListener.class))
1322            listener.activeSequenceChanged(event);
1323
1324        // backward compatibility
1325        for (FocusedSequenceListener listener : listeners.getListeners(FocusedSequenceListener.class))
1326            listener.focusedSequenceChanged(event);
1327    }
1328
1329    @Override
1330    public boolean canExitExternal()
1331    {
1332        for (AcceptListener listener : listeners.getListeners(WeakAcceptListener.class))
1333            if (!listener.accept(mainFrame))
1334                return false;
1335
1336        return true;
1337    }
1338
1339    @Deprecated
1340    @Override
1341    public void beginUpdate()
1342    {
1343        // updater.beginUpdate();
1344    }
1345
1346    @Deprecated
1347    @Override
1348    public void endUpdate()
1349    {
1350        // updater.endUpdate();
1351    }
1352
1353    @Deprecated
1354    @Override
1355    public boolean isUpdating()
1356    {
1357        return false;
1358        // return updater.isUpdating();
1359    }
1360
1361    /**
1362     * called when a plugin is opened
1363     */
1364    private void pluginStarted(Plugin plugin)
1365    {
1366        firePluginStartedEvent(plugin);
1367    }
1368
1369    /**
1370     * called when a plugin is closed
1371     */
1372    private void pluginEnded(Plugin plugin)
1373    {
1374        firePluginEndedEvent(plugin);
1375    }
1376
1377    /**
1378     * called when a viewer is opened
1379     */
1380    private void viewerOpened(Viewer viewer)
1381    {
1382        final Sequence sequence = viewer.getSequence();
1383        boolean opened = true;
1384
1385        synchronized (viewers)
1386        {
1387            // check if the sequence has just been opened
1388            if (sequence != null)
1389            {
1390                for (Viewer v : viewers)
1391                {
1392                    if (v.getSequence() == sequence)
1393                    {
1394                        opened = false;
1395                        break;
1396                    }
1397                }
1398            }
1399
1400            // add viewer to the viewer list
1401            viewers.add(viewer);
1402        }
1403
1404        // single viewer for this sequence ?
1405        if ((sequence != null) && opened)
1406            // send opened event
1407            sequenceOpened(sequence);
1408
1409        // fire viewer open event (after sequence open)
1410        fireViewerOpenedEvent(viewer);
1411    }
1412
1413    /**
1414     * called when viewer activation changed
1415     */
1416    private void viewerActivationChanged(Viewer oldActive, Viewer newActive)
1417    {
1418        final Sequence sequence;
1419
1420        // new active viewer is not null ?
1421        if (newActive != null)
1422        {
1423            // remove focus on ImageJ image
1424            final ImageJWrapper ij = Icy.getMainInterface().getImageJ();
1425            if (ij != null)
1426                ij.setActiveImage(null);
1427
1428            // get active sequence
1429            sequence = newActive.getSequence();
1430        }
1431        else
1432            sequence = null;
1433
1434        final Sequence oldActiveSequence = activeSequence;
1435
1436        // sequence active changed ?
1437        if (oldActiveSequence != sequence)
1438        {
1439            activeSequence = sequence;
1440            sequenceActivationChanged(oldActiveSequence, sequence);
1441        }
1442
1443        // fire deactivated / activated events
1444        fireViewerDeactivatedEvent(oldActive);
1445        fireViewerActivatedEvent(newActive);
1446    }
1447
1448    /**
1449     * called when the active viewer changed
1450     */
1451    void activeViewerChanged(ViewerEvent event)
1452    {
1453        // propagate event if it comes from the active viewer
1454        // FIXME: why we need to test that ? it should always be the active viewer ?
1455        if (event.getSource() == activeViewer)
1456            fireActiveViewerChangedEvent(event);
1457    }
1458
1459    /**
1460     * called when a viewer is closed
1461     */
1462    private void viewerClosed(Viewer viewer)
1463    {
1464        // retrieve the viewer LUT before we release it
1465        final LUT lut = viewer.getLut();
1466
1467        // remove active viewer listener
1468        if (viewer == activeViewer)
1469            viewer.removeListener(activeViewerListener);
1470        // remove viewer from the viewer list
1471        synchronized (viewers)
1472        {
1473            viewers.remove(viewer);
1474        }
1475        // fire viewer closed event (before sequence close)
1476        fireViewerClosedEvent(viewer);
1477
1478        final Sequence sequence = viewer.getSequence();
1479
1480        // check if a sequence has been closed
1481        if (sequence != null)
1482        {
1483            // if no viewer for this sequence
1484            if (getViewers(sequence).isEmpty())
1485                // sequence close
1486                sequenceClosed(sequence, lut);
1487        }
1488    }
1489
1490    /**
1491     * called when a sequence is opened
1492     */
1493    private void sequenceOpened(Sequence sequence)
1494    {
1495        // add to sequence list
1496        synchronized (sequences)
1497        {
1498            sequences.add(sequence);
1499        }
1500        // listen the sequence
1501        sequence.addListener(sequenceListener);
1502        // fire sequence opened event (before roi / overlay events)
1503        fireSequenceOpenedEvent(sequence);
1504
1505        // check about ROI add event
1506        for (ROI roi : sequence.getROIs())
1507            checkRoiAdded(roi, false);
1508        // check about Overlay add event
1509        for (Overlay overlay : sequence.getOverlays())
1510            checkOverlayAdded(overlay, false);
1511    }
1512
1513    /**
1514     * called when sequence activation changed
1515     */
1516    private void sequenceActivationChanged(Sequence oldActive, Sequence newActive)
1517    {
1518        // fire events
1519        fireSequenceDeactivatedEvent(oldActive);
1520        fireSequenceActivatedEvent(newActive);
1521    }
1522
1523    /**
1524     * called when a sequence changed
1525     */
1526    void sequenceChanged(SequenceEvent event)
1527    {
1528        final Sequence sequence = event.getSequence();
1529
1530        // handle event for active sequence only
1531        if (isOpened(sequence))
1532        {
1533            switch (event.getSourceType())
1534            {
1535                case SEQUENCE_ROI:
1536                    switch (event.getType())
1537                    {
1538                        case ADDED:
1539                            checkRoiAdded((ROI) event.getSource(), false);
1540                            break;
1541
1542                        case REMOVED:
1543                            checkRoiRemoved((ROI) event.getSource(), false);
1544                            break;
1545                    }
1546                    break;
1547
1548                case SEQUENCE_OVERLAY:
1549                    switch (event.getType())
1550                    {
1551                        case ADDED:
1552                            checkOverlayAdded((Overlay) event.getSource(), false);
1553                            break;
1554
1555                        case REMOVED:
1556                            checkOverlayRemoved((Overlay) event.getSource(), false);
1557                            break;
1558                    }
1559                    break;
1560            }
1561        }
1562
1563        // propagate event if it comes from the active sequence
1564        if (sequence == activeSequence)
1565            fireActiveSequenceChangedEvent(event);
1566    }
1567
1568    /**
1569     * Called when a sequence is closed.
1570     * 
1571     * @param sequence
1572     *        the sequence which has been closed.
1573     * @param userLut
1574     *        the viewer's LUT used when sequence has been closed.
1575     */
1576    private void sequenceClosed(Sequence sequence, LUT userLut)
1577    {
1578        // check about Overlay remove event
1579        for (Overlay overlay : sequence.getOverlays())
1580            checkOverlayRemoved(overlay, true);
1581        // check about ROI remove event
1582        for (ROI roi : sequence.getROIs())
1583            checkRoiRemoved(roi, true);
1584
1585        // remove from sequence listener
1586        sequence.removeListener(sequenceListener);
1587        // remove from the sequence list
1588        synchronized (sequences)
1589        {
1590            sequences.remove(sequence);
1591        }
1592
1593        // set the user LUT in the sequence
1594        if (userLut != null)
1595            sequence.setUserLUT(userLut);
1596        // inform sequence is now closed
1597        sequence.closed();
1598
1599        // fire sequence closed event (after roi / overlay events)
1600        fireSequenceClosedEvent(sequence);
1601    }
1602
1603    private void checkRoiAdded(ROI roi, boolean checkBeforeAdd)
1604    {
1605        final List<Sequence> sequencesContainingRoi = getSequencesContaining(roi);
1606
1607        // only 1 sequence contains (or will contain) this roi ?
1608        if (sequencesContainingRoi.size() == (checkBeforeAdd ? 0 : 1))
1609            // roi added
1610            roiAdded(roi);
1611    }
1612
1613    private void checkRoiRemoved(ROI roi, boolean checkBeforeRemove)
1614    {
1615        final List<Sequence> sequencesContainingRoi = getSequencesContaining(roi);
1616
1617        // no more sequence contains (or will contain) this roi ?
1618        if (sequencesContainingRoi.size() == (checkBeforeRemove ? 1 : 0))
1619            // roi removed
1620            roiRemoved(roi);
1621    }
1622
1623    private void checkOverlayAdded(Overlay overlay, boolean checkBeforeAdd)
1624    {
1625        final List<Sequence> sequencesContainingOverlay = getSequencesContaining(overlay);
1626
1627        // only 1 sequence contains (or will contain) this overlay ?
1628        if (sequencesContainingOverlay.size() == (checkBeforeAdd ? 0 : 1))
1629            // overlay added
1630            overlayAdded(overlay);
1631    }
1632
1633    private void checkOverlayRemoved(Overlay overlay, boolean checkBeforeRemove)
1634    {
1635        final List<Sequence> sequencesContainingOverlay = getSequencesContaining(overlay);
1636
1637        // no more sequence contains (or will contain) this overlay ?
1638        if (sequencesContainingOverlay.size() == (checkBeforeRemove ? 1 : 0))
1639            // overlay removed
1640            overlayRemoved(overlay);
1641    }
1642
1643    /**
1644     * called when a roi is added for the first time in a sequence
1645     */
1646    private void roiAdded(ROI roi)
1647    {
1648        fireRoiAddedEvent(roi);
1649    }
1650
1651    /**
1652     * called when a roi is removed from all sequence
1653     */
1654    private void roiRemoved(ROI roi)
1655    {
1656        fireRoiRemovedEvent(roi);
1657    }
1658
1659    /**
1660     * called when an overlay is added for the first time in a sequence
1661     */
1662    private void overlayAdded(Overlay overlay)
1663    {
1664        fireOverlayAddedEvent(overlay);
1665    }
1666
1667    /**
1668     * called when an overlay is removed from all sequence
1669     */
1670    private void overlayRemoved(Overlay overlay)
1671    {
1672        fireOverlayRemovedEvent(overlay);
1673    }
1674
1675    @Override
1676    public void setGlobalViewSyncId(int id)
1677    {
1678        for (Viewer viewer : getViewers())
1679            viewer.setViewSyncId(id);
1680    }
1681
1682    @Override
1683    public boolean isVirtualMode()
1684    {
1685        return GeneralPreferences.getVirtualMode();
1686    }
1687
1688    @Override
1689    public void setVirtualMode(boolean value)
1690    {
1691        final InspectorPanel inspector = getInspector();
1692        if (inspector != null)
1693            inspector.setVirtualMode(value);
1694    }
1695}