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.viewer;
020
021import java.awt.AlphaComposite;
022import java.awt.BorderLayout;
023import java.awt.Color;
024import java.awt.Dimension;
025import java.awt.Font;
026import java.awt.Graphics;
027import java.awt.Graphics2D;
028import java.awt.KeyboardFocusManager;
029import java.awt.RenderingHints;
030import java.awt.event.ActionEvent;
031import java.awt.event.ActionListener;
032import java.awt.event.KeyEvent;
033import java.awt.event.KeyListener;
034import java.awt.geom.Rectangle2D;
035import java.awt.image.BufferedImage;
036import java.beans.PropertyChangeEvent;
037import java.util.ArrayList;
038
039import javax.swing.ActionMap;
040import javax.swing.BorderFactory;
041import javax.swing.Box;
042import javax.swing.InputMap;
043import javax.swing.JComboBox;
044import javax.swing.JComponent;
045import javax.swing.JLabel;
046import javax.swing.JMenu;
047import javax.swing.JMenuItem;
048import javax.swing.JOptionPane;
049import javax.swing.JPanel;
050import javax.swing.JToolBar;
051import javax.swing.WindowConstants;
052import javax.swing.event.EventListenerList;
053
054import icy.action.CanvasActions;
055import icy.action.CanvasActions.ToggleLayersAction;
056import icy.action.SequenceOperationActions.ToggleVirtualSequenceAction;
057import icy.action.ViewerActions;
058import icy.action.WindowActions;
059import icy.canvas.IcyCanvas;
060import icy.canvas.IcyCanvas2D;
061import icy.canvas.IcyCanvasEvent;
062import icy.canvas.IcyCanvasListener;
063import icy.common.MenuCallback;
064import icy.common.listener.ProgressListener;
065import icy.gui.component.button.IcyButton;
066import icy.gui.component.button.IcyToggleButton;
067import icy.gui.component.renderer.LabelComboBoxRenderer;
068import icy.gui.dialog.ConfirmDialog;
069import icy.gui.dialog.MessageDialog;
070import icy.gui.dialog.SaverDialog;
071import icy.gui.frame.IcyFrame;
072import icy.gui.frame.IcyFrameAdapter;
073import icy.gui.frame.IcyFrameEvent;
074import icy.gui.frame.progress.ToolTipFrame;
075import icy.gui.lut.LUTViewer;
076import icy.gui.lut.abstract_.IcyLutViewer;
077import icy.gui.plugin.PluginComboBoxRenderer;
078import icy.gui.util.ComponentUtil;
079import icy.gui.viewer.ViewerEvent.ViewerEventType;
080import icy.image.IcyBufferedImage;
081import icy.image.cache.ImageCache;
082import icy.image.lut.LUT;
083import icy.main.Icy;
084import icy.plugin.PluginLoader;
085import icy.plugin.PluginLoader.PluginLoaderEvent;
086import icy.plugin.PluginLoader.PluginLoaderListener;
087import icy.plugin.interface_.PluginCanvas;
088import icy.preferences.GeneralPreferences;
089import icy.sequence.DimensionId;
090import icy.sequence.Sequence;
091import icy.sequence.SequenceEvent;
092import icy.sequence.SequenceListener;
093import icy.system.IcyExceptionHandler;
094import icy.system.IcyHandledException;
095import icy.system.thread.ThreadUtil;
096import icy.util.GraphicsUtil;
097import icy.util.Random;
098import icy.util.StringUtil;
099
100/**
101 * Viewer send an event if the IcyCanvas change.
102 * 
103 * @author Fabrice de Chaumont & Stephane
104 */
105public class Viewer extends IcyFrame implements KeyListener, SequenceListener, IcyCanvasListener, PluginLoaderListener
106{
107    private class ViewerMainPanel extends JPanel
108    {
109        public ViewerMainPanel()
110        {
111            super();
112        }
113
114        public void drawTextCenter(Graphics2D g, String text, float alpha)
115        {
116            final Rectangle2D rect = GraphicsUtil.getStringBounds(g, text);
117            final int w = (int) rect.getWidth();
118            final int h = (int) rect.getHeight();
119            final int x = (getWidth() - (w + 8 + 2)) / 2;
120            final int y = (getHeight() - (h + 8 + 2)) / 2;
121
122            g.setColor(Color.gray);
123            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
124            g.fillRoundRect(x, y, w + 8, h + 8, 8, 8);
125
126            g.setColor(Color.white);
127            g.drawString(text, x + 4, y + 2 + h);
128        }
129
130        @Override
131        public void paint(Graphics g)
132        {
133            // currently modifying canvas ?
134            super.paint(g);
135
136            // display a message
137            if (settingCanvas)
138            {
139                final Graphics2D g2 = (Graphics2D) g.create();
140
141                g2.setFont(new Font("Arial", Font.BOLD, 16));
142                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
143                drawTextCenter(g2, "Loading canvas...", 0.8f);
144                g2.dispose();
145            }
146        }
147    }
148
149    /**
150     * only show it once per Icy session
151     */
152    private static boolean toolTipVirtualDone = false;
153
154    /**
155     * associated LUT
156     */
157    private LUT lut;
158    /**
159     * associated canvas
160     */
161    private IcyCanvas canvas;
162    /**
163     * associated sequence
164     */
165    Sequence sequence;
166
167    /***/
168    private final EventListenerList listeners = new EventListenerList();
169
170    /**
171     * GUI
172     */
173    private JToolBar toolBar;
174    private ViewerMainPanel mainPanel;
175    private LUTViewer lutViewer;
176
177    JComboBox canvasComboBox;
178    JComboBox lockComboBox;
179    IcyToggleButton layersEnabledButton;
180    IcyButton screenShotButton;
181    IcyButton screenShotAlternateButton;
182    IcyButton duplicateButton;
183    IcyButton switchStateButton;
184    IcyToggleButton virtualButton;
185
186    /**
187     * internals
188     */
189    boolean initialized;
190    private final Runnable lutUpdater;
191    boolean settingCanvas;
192
193    public Viewer(Sequence sequence, boolean visible)
194    {
195        super("Viewer", true, true, true, true);
196
197        if (sequence == null)
198            throw new IllegalArgumentException("Can't open a null sequence.");
199
200        this.sequence = sequence;
201
202        // default
203        canvas = null;
204        lut = null;
205        lutViewer = null;
206        initialized = false;
207        settingCanvas = false;
208
209        lutUpdater = new Runnable()
210        {
211            @Override
212            public void run()
213            {
214                // don't need to update that too much
215                ThreadUtil.sleep(1);
216
217                ThreadUtil.invokeNow(new Runnable()
218                {
219                    @Override
220                    public void run()
221                    {
222                        final LUT lut = getLut();
223
224                        // closed --> ignore
225                        if (lut != null)
226                        {
227                            // refresh LUT viewer
228                            setLutViewer(new LUTViewer(Viewer.this, lut));
229                            // notify
230                            fireViewerChanged(ViewerEventType.LUT_CHANGED);
231                        }
232                    }
233                });
234            }
235        };
236
237        mainPanel = new ViewerMainPanel();
238        mainPanel.setLayout(new BorderLayout());
239
240        // set menu directly in system menu so we don't need a extra MenuBar
241        setSystemMenuCallback(new MenuCallback()
242        {
243            @Override
244            public JMenu getMenu()
245            {
246                return Viewer.this.getMenu();
247            }
248        });
249
250        // build tool bar
251        buildToolBar();
252
253        // create a new compatible LUT
254        final LUT lut = sequence.createCompatibleLUT();
255        // restore user LUT if needed
256        if (sequence.hasUserLUT())
257        {
258            final LUT userLut = sequence.getUserLUT();
259
260            // restore colormaps (without alpha)
261            lut.setColorMaps(userLut, false);
262            // then restore scalers
263            lut.setScalers(userLut);
264        }
265
266        // set lut (this modify lutPanel)
267        setLut(lut);
268        // set default canvas to first available canvas plugin (Canvas2D should be first)
269        setCanvas(IcyCanvas.getCanvasPluginNames().get(0));
270
271        setLayout(new BorderLayout());
272
273        add(toolBar, BorderLayout.NORTH);
274        add(mainPanel, BorderLayout.CENTER);
275
276        // setting frame
277        refreshViewerTitle();
278        setFocusable(true);
279        // set position depending window mode
280        setLocationInternal(20 + Random.nextInt(100), 20 + Random.nextInt(60));
281        setLocationExternal(100 + Random.nextInt(200), 100 + Random.nextInt(150));
282        setSize(640, 480);
283
284        // initial position in sequence
285        if (sequence.isEmpty())
286            setPositionZ(0);
287        else
288            setPositionZ(((sequence.getSizeZ() + 1) / 2) - 1);
289
290        addFrameListener(new IcyFrameAdapter()
291        {
292            @Override
293            public void icyFrameOpened(IcyFrameEvent e)
294            {
295                if (!initialized)
296                {
297                    if ((Viewer.this.sequence != null) && !Viewer.this.sequence.isEmpty())
298                    {
299                        adjustViewerToImageSize();
300                        initialized = true;
301                    }
302                }
303            }
304
305            @Override
306            public void icyFrameActivated(IcyFrameEvent e)
307            {
308                Icy.getMainInterface().setActiveViewer(Viewer.this);
309            }
310
311            @Override
312            public void icyFrameExternalized(IcyFrameEvent e)
313            {
314                refreshToolBar();
315            }
316
317            @Override
318            public void icyFrameInternalized(IcyFrameEvent e)
319            {
320                refreshToolBar();
321            }
322
323            @Override
324            public void icyFrameClosing(IcyFrameEvent e)
325            {
326                onClosing();
327            }
328        });
329
330        addKeyListener(this);
331        sequence.addListener(this);
332        PluginLoader.addListener(this);
333
334        // do this when viewer is initialized
335        Icy.getMainInterface().registerViewer(this);
336        // automatically add it to the desktop pane
337        addToDesktopPane();
338
339        if (visible)
340        {
341            setVisible(true);
342            requestFocus();
343            toFront();
344        }
345        else
346            setVisible(false);
347
348        // can be done after setVisible
349        buildActionMap();
350    }
351
352    public Viewer(Sequence sequence)
353    {
354        this(sequence, true);
355    }
356
357    void buildActionMap()
358    {
359        // global input map
360        buildActionMap(getInputMap(JComponent.WHEN_FOCUSED), getActionMap());
361    }
362
363    protected void buildActionMap(InputMap imap, ActionMap amap)
364    {
365        imap.put(WindowActions.gridTileAction.getKeyStroke(), WindowActions.gridTileAction.getName());
366        imap.put(WindowActions.horizontalTileAction.getKeyStroke(), WindowActions.horizontalTileAction.getName());
367        imap.put(WindowActions.verticalTileAction.getKeyStroke(), WindowActions.verticalTileAction.getName());
368        imap.put(CanvasActions.globalDisableSyncAction.getKeyStroke(), CanvasActions.globalDisableSyncAction.getName());
369        imap.put(CanvasActions.globalSyncGroup1Action.getKeyStroke(), CanvasActions.globalSyncGroup1Action.getName());
370        imap.put(CanvasActions.globalSyncGroup2Action.getKeyStroke(), CanvasActions.globalSyncGroup2Action.getName());
371        imap.put(CanvasActions.globalSyncGroup3Action.getKeyStroke(), CanvasActions.globalSyncGroup3Action.getName());
372        imap.put(CanvasActions.globalSyncGroup4Action.getKeyStroke(), CanvasActions.globalSyncGroup4Action.getName());
373
374        amap.put(WindowActions.gridTileAction.getName(), WindowActions.gridTileAction);
375        amap.put(WindowActions.horizontalTileAction.getName(), WindowActions.horizontalTileAction);
376        amap.put(WindowActions.verticalTileAction.getName(), WindowActions.verticalTileAction);
377        amap.put(CanvasActions.globalDisableSyncAction.getName(), CanvasActions.globalDisableSyncAction);
378        amap.put(CanvasActions.globalSyncGroup1Action.getName(), CanvasActions.globalSyncGroup1Action);
379        amap.put(CanvasActions.globalSyncGroup2Action.getName(), CanvasActions.globalSyncGroup2Action);
380        amap.put(CanvasActions.globalSyncGroup3Action.getName(), CanvasActions.globalSyncGroup3Action);
381        amap.put(CanvasActions.globalSyncGroup4Action.getName(), CanvasActions.globalSyncGroup4Action);
382    }
383
384    /**
385     * Called when user want to close viewer
386     */
387    protected void onClosing()
388    {
389        // this sequence was not saved
390        if ((sequence != null) && (sequence.getFilename() == null))
391        {
392            // save new sequence enabled ?
393            if (GeneralPreferences.getSaveNewSequence())
394            {
395                final int res = ConfirmDialog.confirmEx("Save sequence",
396                        "Do you want to save '" + sequence.getName() + "' before closing it ?",
397                        ConfirmDialog.YES_NO_CANCEL_OPTION);
398
399                switch (res)
400                {
401                    case JOptionPane.YES_OPTION:
402                        // save the image
403                        new SaverDialog(sequence);
404
405                    case JOptionPane.NO_OPTION:
406                        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
407                        break;
408
409                    case JOptionPane.CANCEL_OPTION:
410                    default:
411                        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
412                        break;
413                }
414
415                return;
416            }
417        }
418
419        // just close it
420        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
421    }
422
423    /**
424     * Called when viewer is closed.<br>
425     * Release as much references we can here because of the
426     * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4759312">
427     * JInternalFrame bug</a>.
428     */
429    @Override
430    public void onClosed()
431    {
432        // notify close
433        fireViewerClosed();
434
435        // remove listeners
436        sequence.removeListener(this);
437        if (canvas != null)
438            canvas.removeCanvasListener(this);
439        PluginLoader.removeListener(this);
440
441        icy.main.Icy.getMainInterface().unRegisterViewer(this);
442
443        // AWT JDesktopPane keep reference on last closed JInternalFrame
444        // it's good to free as much reference we can here
445        if (canvas != null)
446            canvas.shutDown();
447        if (lutViewer != null)
448            lutViewer.dispose();
449        if (mainPanel != null)
450            mainPanel.removeAll();
451        if (toolBar != null)
452            toolBar.removeAll();
453
454        // remove all listeners for this viewer
455        ViewerListener[] vls = listeners.getListeners(ViewerListener.class);
456        for (ViewerListener vl : vls)
457            listeners.remove(ViewerListener.class, vl);
458
459        lutViewer = null;
460        mainPanel = null;
461
462        canvas = null;
463        sequence = null;
464        lut = null;
465        toolBar = null;
466        canvasComboBox = null;
467        lockComboBox = null;
468        duplicateButton = null;
469        layersEnabledButton = null;
470        screenShotAlternateButton = null;
471        screenShotButton = null;
472        switchStateButton = null;
473        virtualButton = null;
474
475        super.onClosed();
476    }
477
478    void adjustViewerToImageSize()
479    {
480        if (canvas instanceof IcyCanvas2D)
481        {
482            final IcyCanvas2D cnv = (IcyCanvas2D) canvas;
483
484            final int ix = cnv.getImageSizeX();
485            final int iy = cnv.getImageSizeY();
486
487            if ((ix > 0) && (iy > 0))
488            {
489                // find scale factor to fit image in a 640x540 sized window
490                // and limit zoom to 100%
491                final double scale = Math.min(Math.min(640d / ix, 540d / iy), 1d);
492
493                cnv.setScaleX(scale);
494                cnv.setScaleY(scale);
495
496                // this actually resize viewer as canvas size depend from it
497                cnv.fitCanvasToImage();
498            }
499        }
500
501        // minimum size to start : 400, 240
502        final Dimension size = new Dimension(Math.max(getWidth(), 400), Math.max(getHeight(), 240));
503        // minimum size global : 200, 140
504        final Dimension minSize = new Dimension(200, 140);
505
506        // adjust size of both frames
507        setSizeExternal(size);
508        setSizeInternal(size);
509        setMinimumSizeInternal(minSize);
510        setMinimumSizeExternal(minSize);
511    }
512
513    /**
514     * Rebuild and return viewer menu
515     */
516    JMenu getMenu()
517    {
518        final JMenu result = getDefaultSystemMenu();
519
520        final JMenuItem overlayItem = new JMenuItem(CanvasActions.toggleLayersAction);
521        if ((canvas != null) && canvas.isLayersVisible())
522            overlayItem.setText("Hide layers");
523        else
524            overlayItem.setText("Show layers");
525        final JMenuItem duplicateItem = new JMenuItem(ViewerActions.duplicateAction);
526
527        // set menu
528        result.insert(overlayItem, 0);
529        result.insertSeparator(1);
530        result.insert(duplicateItem, 2);
531
532        return result;
533    }
534
535    protected void buildLockCombo()
536    {
537        final ArrayList<JLabel> labels = new ArrayList<JLabel>();
538
539        // get sync action labels
540        labels.add(CanvasActions.disableSyncAction.getLabelComponent(true, false));
541        labels.add(CanvasActions.syncGroup1Action.getLabelComponent(true, false));
542        labels.add(CanvasActions.syncGroup2Action.getLabelComponent(true, false));
543        labels.add(CanvasActions.syncGroup3Action.getLabelComponent(true, false));
544        labels.add(CanvasActions.syncGroup4Action.getLabelComponent(true, false));
545
546        // build comboBox with lock id
547        lockComboBox = new JComboBox(labels.toArray());
548        // set specific renderer
549        lockComboBox.setRenderer(new LabelComboBoxRenderer(lockComboBox));
550        // limit size
551        ComponentUtil.setFixedWidth(lockComboBox, 48);
552        lockComboBox.setToolTipText("Select synchronisation group");
553        // don't want focusable here
554        lockComboBox.setFocusable(false);
555        // needed because of VTK
556        lockComboBox.setLightWeightPopupEnabled(false);
557
558        // action on canvas change
559        lockComboBox.addActionListener(new ActionListener()
560        {
561            @Override
562            public void actionPerformed(ActionEvent e)
563            {
564                // adjust lock id
565                setViewSyncId(lockComboBox.getSelectedIndex());
566            }
567        });
568    }
569
570    void buildCanvasCombo()
571    {
572        // build comboBox with canvas plugins
573        canvasComboBox = new JComboBox(IcyCanvas.getCanvasPluginNames().toArray());
574        // specific renderer
575        canvasComboBox.setRenderer(new PluginComboBoxRenderer(canvasComboBox, false));
576        // limit size
577        ComponentUtil.setFixedWidth(canvasComboBox, 48);
578        canvasComboBox.setToolTipText("Select canvas type");
579        // don't want focusable here
580        canvasComboBox.setFocusable(false);
581        // needed because of VTK
582        canvasComboBox.setLightWeightPopupEnabled(false);
583
584        // action on canvas change
585        canvasComboBox.addActionListener(new ActionListener()
586        {
587            @Override
588            public void actionPerformed(ActionEvent e)
589            {
590                // set selected canvas
591                setCanvas((String) canvasComboBox.getSelectedItem());
592            }
593        });
594    }
595
596    /**
597     * build the toolBar
598     */
599    protected void buildToolBar()
600    {
601        // build combo box
602        buildLockCombo();
603        buildCanvasCombo();
604
605        // build buttons
606        layersEnabledButton = new IcyToggleButton(new ToggleLayersAction(true));
607        layersEnabledButton.setHideActionText(true);
608        layersEnabledButton.setFocusable(false);
609        layersEnabledButton.setSelected(true);
610        screenShotButton = new IcyButton(CanvasActions.screenShotAction);
611        screenShotButton.setFocusable(false);
612        screenShotButton.setHideActionText(true);
613        screenShotAlternateButton = new IcyButton(CanvasActions.screenShotAlternateAction);
614        screenShotAlternateButton.setFocusable(false);
615        screenShotAlternateButton.setHideActionText(true);
616        duplicateButton = new IcyButton(ViewerActions.duplicateAction);
617        duplicateButton.setFocusable(false);
618        duplicateButton.setHideActionText(true);
619        // duplicateButton.setToolTipText("Duplicate view (no data duplication)");
620        switchStateButton = new IcyButton(getSwitchStateAction());
621        switchStateButton.setFocusable(false);
622        switchStateButton.setHideActionText(true);
623        virtualButton = new IcyToggleButton(new ToggleVirtualSequenceAction(false));
624        virtualButton.setFocusable(false);
625        virtualButton.setHideActionText(true);
626
627        // and build the toolbar
628        toolBar = new JToolBar();
629        toolBar.setFloatable(false);
630        // so we don't have any border
631        toolBar.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0));
632        ComponentUtil.setPreferredHeight(toolBar, 26);
633
634        updateToolbarComponents();
635    }
636
637    void updateToolbarComponents()
638    {
639        if (toolBar != null)
640        {
641            toolBar.removeAll();
642
643            toolBar.add(lockComboBox);
644            toolBar.addSeparator();
645            toolBar.add(canvasComboBox);
646            toolBar.addSeparator();
647            toolBar.add(layersEnabledButton);
648            if (canvas != null)
649                canvas.customizeToolbar(toolBar);
650            toolBar.add(Box.createHorizontalGlue());
651            toolBar.addSeparator();
652            toolBar.add(screenShotButton);
653            toolBar.add(screenShotAlternateButton);
654            toolBar.addSeparator();
655            toolBar.add(duplicateButton);
656            toolBar.add(switchStateButton);
657            toolBar.addSeparator();
658            toolBar.add(virtualButton);
659        }
660    }
661
662    void refreshLockCombo()
663    {
664        final int syncId = getViewSyncId();
665
666        lockComboBox.setEnabled(isSynchronizedViewSupported());
667        lockComboBox.setSelectedIndex(syncId);
668
669        switch (syncId)
670        {
671            case 0:
672                lockComboBox.setBackground(Color.gray);
673                lockComboBox.setToolTipText("Synchronization disabled");
674                break;
675
676            case 1:
677                lockComboBox.setBackground(Color.green);
678                lockComboBox.setToolTipText("Full synchronization group 1 (view and Z/T position)");
679                break;
680
681            case 2:
682                lockComboBox.setBackground(Color.yellow);
683                lockComboBox.setToolTipText("Full synchronization group 2 (view and Z/T position)");
684                break;
685
686            case 3:
687                lockComboBox.setBackground(Color.blue);
688                lockComboBox.setToolTipText("View synchronization group (view synched but not Z/T position)");
689                break;
690
691            case 4:
692                lockComboBox.setBackground(Color.red);
693                lockComboBox.setToolTipText("Slice synchronization group (Z/T position synched but not view)");
694                break;
695        }
696    }
697
698    void refreshCanvasCombo()
699    {
700        if (canvas != null)
701        {
702            // get plugin class name for this canvas
703            final String pluginName = IcyCanvas.getPluginClassName(canvas.getClass().getName());
704
705            if (pluginName != null)
706            {
707                // align canvas combo to plugin name
708                if (!canvasComboBox.getSelectedItem().equals(pluginName))
709                    canvasComboBox.setSelectedItem(pluginName);
710            }
711        }
712    }
713
714    void refreshToolBar()
715    {
716        // FIXME : switchStateButton stay selected after action
717        final boolean layersVisible = (canvas != null) ? canvas.isLayersVisible() : false;
718
719        layersEnabledButton.setSelected(layersVisible);
720        if (layersVisible)
721            layersEnabledButton.setToolTipText("Hide layers");
722        else
723            layersEnabledButton.setToolTipText("Show layers");
724
725        final Sequence seq = getSequence();
726        final boolean virtual = (seq != null) && seq.isVirtual();
727        // update virtual state
728        virtualButton.setSelected(virtual);
729        if (!ImageCache.isEnabled())
730            virtualButton.setToolTipText("Image cache is disabled, cannot use virtual sequence");
731        else
732        {
733            if (virtual)
734            {
735                virtualButton.setToolTipText("Disable virtual sequence (caching)");
736
737                // virtual was enabled for this sequence --> show tooltip to explain
738                if (!toolTipVirtualDone)
739                {
740                    final ToolTipFrame tooltip = new ToolTipFrame("<html>" + "<img src=\""
741                            + Icy.class.getResource("/res/image/help/viewer_virtual.jpg").toString() + "\" /><br>"
742                            + "<b>Your image has been made <i>virtual</i></b>.<br> This means that its data can be stored on disk to spare memory but this is at the cost of slower display / processing.<br>"
743                            + "Also you should note that <b>some plugins aren't compatible with <i>virtual</i> images</b> and so the result may be inconsistent (possible data lost)."
744                            + "</html>", 30, "viewerVirtual");
745                    tooltip.setSize(400, 180);
746                    toolTipVirtualDone = true;
747                }
748            }
749            else
750                virtualButton.setToolTipText("Enable virtual sequence (caching)");
751        }
752
753        // refresh combos
754        refreshLockCombo();
755        refreshCanvasCombo();
756    }
757
758    /**
759     * @return the sequence
760     */
761    public Sequence getSequence()
762    {
763        return sequence;
764    }
765
766    /**
767     * Set the specified LUT for the viewer.
768     */
769    public void setLut(LUT value)
770    {
771        if ((lut != value) && sequence.isLutCompatible(value))
772        {
773            // set new lut & notify change
774            lut = value;
775            lutChanged();
776        }
777    }
778
779    /**
780     * Returns the viewer LUT
781     */
782    public LUT getLut()
783    {
784        // have to test this as we release sequence reference on closed
785        if (sequence == null)
786            return lut;
787
788        // sequence can be asynchronously modified so we have to test change on Getter
789        if ((lut == null) || !sequence.isLutCompatible(lut))
790        {
791            // sequence type has changed, we need to recreate a compatible LUT
792            final LUT newLut = sequence.createCompatibleLUT();
793
794            // keep the color map of previous LUT if they have the same number of channels
795            if ((lut != null) && (lut.getNumChannel() == newLut.getNumChannel()))
796                newLut.getColorSpace().setColorMaps(lut.getColorSpace(), true);
797
798            // set the new lut
799            setLut(newLut);
800        }
801
802        return lut;
803    }
804
805    /**
806     * Set the specified canvas for the viewer (from the {@link PluginCanvas} class name).
807     * 
808     * @see IcyCanvas#getCanvasPluginNames()
809     */
810    public void setCanvas(String pluginClassName)
811    {
812        // not the same canvas ?
813        if ((canvas == null) || !canvas.getClass().getName().equals(IcyCanvas.getCanvasClassName(pluginClassName)))
814        {
815            try
816            {
817                IcyCanvas newCanvas;
818
819                settingCanvas = true;
820                // show loading message
821                mainPanel.paintImmediately(mainPanel.getBounds());
822                try
823                {
824                    // try to create the new canvas
825                    newCanvas = IcyCanvas.create(pluginClassName, this);
826                }
827                catch (Throwable e)
828                {
829                    if (e instanceof IcyHandledException)
830                    {
831                        // just ignore
832                    }
833                    else if (e instanceof UnsupportedOperationException)
834                    {
835                        MessageDialog.showDialog(e.getLocalizedMessage(), MessageDialog.ERROR_MESSAGE);
836                    }
837                    else if (e instanceof Exception)
838                    {
839                        IcyExceptionHandler.handleException(
840                                new ClassNotFoundException(
841                                        "Cannot find '" + pluginClassName + "' class --> cannot create the canvas.", e),
842                                true);
843                    }
844                    else
845                        IcyExceptionHandler.handleException(e, true);
846
847                    // create a new instance of current canvas
848                    newCanvas = IcyCanvas.create(canvas.getClass().getName(), this);
849                }
850                finally
851                {
852                    settingCanvas = false;
853                }
854
855                final int saveX;
856                final int saveY;
857                final int saveZ;
858                final int saveT;
859                final int saveC;
860
861                // save properties and shutdown previous canvas
862                if (canvas != null)
863                {
864                    // save position
865                    saveX = canvas.getPositionX();
866                    saveY = canvas.getPositionY();
867                    saveZ = canvas.getPositionZ();
868                    saveT = canvas.getPositionT();
869                    saveC = canvas.getPositionC();
870
871                    canvas.removePropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this);
872                    canvas.removeCanvasListener(this);
873                    // --> this actually can do some restore operation (as the palette) after creation of the new canvas
874                    canvas.shutDown();
875                    // remove from mainPanel
876                    mainPanel.remove(canvas);
877                }
878                else
879                    saveX = saveY = saveZ = saveT = saveC = -1;
880
881                // prepare new canvas
882                newCanvas.addCanvasListener(this);
883                newCanvas.addPropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this);
884                // add to mainPanel
885                mainPanel.add(newCanvas, BorderLayout.CENTER);
886
887                // restore position
888                if (saveX != -1)
889                    newCanvas.setPositionX(saveX);
890                if (saveY != -1)
891                    newCanvas.setPositionY(saveY);
892                if (saveZ != -1)
893                    newCanvas.setPositionZ(saveZ);
894                if (saveT != -1)
895                    newCanvas.setPositionT(saveT);
896                if (saveC != -1)
897                    newCanvas.setPositionC(saveC);
898
899                // canvas set :)
900                canvas = newCanvas;
901            }
902            catch (Throwable e)
903            {
904                IcyExceptionHandler.handleException(e, true);
905            }
906
907            mainPanel.revalidate();
908
909            // refresh viewer menu (so overlay checkbox is correctly set)
910            updateSystemMenu();
911            updateToolbarComponents();
912            refreshToolBar();
913
914            // fix the OSX lost keyboard focus on canvas change in detached mode.
915            KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle(getCanvas());
916
917            // notify canvas changed to listener
918            fireViewerChanged(ViewerEventType.CANVAS_CHANGED);
919        }
920    }
921
922    /**
923     * @deprecated Use {@link #setCanvas(String)} instead.
924     */
925    @Deprecated
926    public void setCanvas(IcyCanvas value)
927    {
928        if (canvas == value)
929            return;
930
931        final int saveX;
932        final int saveY;
933        final int saveZ;
934        final int saveT;
935        final int saveC;
936
937        if (canvas != null)
938        {
939            // save position
940            saveX = canvas.getPositionX();
941            saveY = canvas.getPositionY();
942            saveZ = canvas.getPositionZ();
943            saveT = canvas.getPositionT();
944            saveC = canvas.getPositionC();
945
946            canvas.removePropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this);
947            canvas.removeCanvasListener(this);
948            canvas.shutDown();
949            // remove from mainPanel
950            mainPanel.remove(canvas);
951        }
952        else
953            saveX = saveY = saveZ = saveT = saveC = -1;
954
955        // set new canvas
956        canvas = value;
957
958        if (canvas != null)
959        {
960            canvas.addCanvasListener(this);
961            canvas.addPropertyChangeListener(IcyCanvas.PROPERTY_LAYERS_VISIBLE, this);
962            // add to mainPanel
963            mainPanel.add(canvas, BorderLayout.CENTER);
964
965            // restore position
966            if (saveX != -1)
967                canvas.setPositionX(saveX);
968            if (saveY != -1)
969                canvas.setPositionY(saveY);
970            if (saveZ != -1)
971                canvas.setPositionZ(saveZ);
972            if (saveT != -1)
973                canvas.setPositionT(saveT);
974            if (saveC != -1)
975                canvas.setPositionC(saveC);
976        }
977
978        mainPanel.revalidate();
979
980        // refresh viewer menu (so overlay checkbox is correctly set)
981        updateSystemMenu();
982        updateToolbarComponents();
983        refreshToolBar();
984
985        // fix the OSX lost keyboard focus on canvas change in detached mode.
986        KeyboardFocusManager.getCurrentKeyboardFocusManager().upFocusCycle(getCanvas());
987
988        // notify canvas changed to listener
989        fireViewerChanged(ViewerEventType.CANVAS_CHANGED);
990    }
991
992    /**
993     * Returns true if the viewer initialization (correct image resizing) is completed.
994     */
995    public boolean isInitialized()
996    {
997        return initialized;
998    }
999
1000    /**
1001     * Return the viewer Canvas object
1002     */
1003    public IcyCanvas getCanvas()
1004    {
1005        return canvas;
1006    }
1007
1008    /**
1009     * Return the viewer Canvas panel
1010     */
1011    public JPanel getCanvasPanel()
1012    {
1013        if (canvas != null)
1014            return canvas.getPanel();
1015
1016        return null;
1017    }
1018
1019    /**
1020     * Return the viewer Lut panel
1021     */
1022    public LUTViewer getLutViewer()
1023    {
1024        return lutViewer;
1025    }
1026
1027    /**
1028     * Set the {@link LUTViewer} for this viewer.
1029     */
1030    public void setLutViewer(LUTViewer value)
1031    {
1032        if (lutViewer != value)
1033        {
1034            if (lutViewer != null)
1035                lutViewer.dispose();
1036            lutViewer = value;
1037        }
1038    }
1039
1040    /**
1041     * @deprecated Use {@link #getLutViewer()} instead
1042     */
1043    @Deprecated
1044    public IcyLutViewer getLutPanel()
1045    {
1046        return getLutViewer();
1047    }
1048
1049    /**
1050     * @deprecated Use {@link #setLutViewer(LUTViewer)} instead.
1051     */
1052    @Deprecated
1053    public void setLutPanel(IcyLutViewer lutViewer)
1054    {
1055        setLutViewer((LUTViewer) lutViewer);
1056    }
1057
1058    /**
1059     * Return the viewer ToolBar object
1060     */
1061    public JToolBar getToolBar()
1062    {
1063        return toolBar;
1064    }
1065
1066    /**
1067     * @return current T (-1 if all selected/displayed)
1068     */
1069    public int getPositionT()
1070    {
1071        if (canvas != null)
1072            return canvas.getPositionT();
1073
1074        return 0;
1075    }
1076
1077    /**
1078     * Set the current T position (for multi frame sequence).
1079     */
1080    public void setPositionT(int t)
1081    {
1082        if (canvas != null)
1083            canvas.setPositionT(t);
1084    }
1085
1086    /**
1087     * @return current Z (-1 if all selected/displayed)
1088     */
1089    public int getPositionZ()
1090    {
1091        if (canvas != null)
1092            return canvas.getPositionZ();
1093
1094        return 0;
1095    }
1096
1097    /**
1098     * Set the current Z position (for stack sequence).
1099     */
1100    public void setPositionZ(int z)
1101    {
1102        if (canvas != null)
1103            canvas.setPositionZ(z);
1104    }
1105
1106    /**
1107     * @return current C (-1 if all selected/displayed)
1108     */
1109    public int getPositionC()
1110    {
1111        if (canvas != null)
1112            return canvas.getPositionC();
1113
1114        return 0;
1115    }
1116
1117    /**
1118     * Set the current C (channel) position (multi channel sequence)
1119     */
1120    public void setPositionC(int c)
1121    {
1122        if (canvas != null)
1123            canvas.setPositionC(c);
1124    }
1125
1126    /**
1127     * @deprecated Use {@link #getPositionT()} instead.
1128     */
1129    @Deprecated
1130    public int getT()
1131    {
1132        return getPositionT();
1133    }
1134
1135    /**
1136     * @deprecated Use {@link #setPositionT(int)} instead.
1137     */
1138    @Deprecated
1139    public void setT(int t)
1140    {
1141        setPositionT(t);
1142    }
1143
1144    /**
1145     * @deprecated Use {@link #getPositionZ()} instead.
1146     */
1147    @Deprecated
1148    public int getZ()
1149    {
1150        return getPositionZ();
1151    }
1152
1153    /**
1154     * @deprecated Use {@link #setPositionZ(int)} instead.
1155     */
1156    @Deprecated
1157    public void setZ(int z)
1158    {
1159        setPositionZ(z);
1160    }
1161
1162    /**
1163     * @deprecated Use {@link #getPositionZ()} instead.
1164     */
1165    @Deprecated
1166    public int getC()
1167    {
1168        return getPositionC();
1169    }
1170
1171    /**
1172     * @deprecated Use {@link #setPositionZ(int)} instead.
1173     */
1174    @Deprecated
1175    public void setC(int c)
1176    {
1177        setPositionC(c);
1178    }
1179
1180    /**
1181     * Get maximum T value
1182     */
1183    public int getMaxT()
1184    {
1185        if (canvas != null)
1186            return canvas.getMaxPositionT();
1187
1188        return 0;
1189    }
1190
1191    /**
1192     * Get maximum Z value
1193     */
1194    public int getMaxZ()
1195    {
1196        if (canvas != null)
1197            return canvas.getMaxPositionZ();
1198
1199        return 0;
1200    }
1201
1202    /**
1203     * Get maximum C value
1204     */
1205    public int getMaxC()
1206    {
1207        if (canvas != null)
1208            return canvas.getMaxPositionC();
1209
1210        return 0;
1211    }
1212
1213    /**
1214     * return true if current canvas's viewer does support synchronized view
1215     */
1216    public boolean isSynchronizedViewSupported()
1217    {
1218        if (canvas != null)
1219            return canvas.isSynchronizationSupported();
1220
1221        return false;
1222    }
1223
1224    /**
1225     * @return the viewSyncId
1226     */
1227    public int getViewSyncId()
1228    {
1229        if (canvas != null)
1230            return canvas.getSyncId();
1231
1232        return -1;
1233    }
1234
1235    /**
1236     * Set the view synchronization group id (0 means unsynchronized).
1237     * 
1238     * @param id
1239     *        the view synchronization id to set
1240     * @see IcyCanvas#setSyncId(int)
1241     */
1242    public boolean setViewSyncId(int id)
1243    {
1244        if (canvas != null)
1245            return canvas.setSyncId(id);
1246
1247        return false;
1248    }
1249
1250    /**
1251     * Return true if this viewer has its view synchronized
1252     */
1253    public boolean isViewSynchronized()
1254    {
1255        if (canvas != null)
1256            return canvas.isSynchronized();
1257
1258        return false;
1259    }
1260
1261    /**
1262     * Delegation for {@link IcyCanvas#getImage(int, int, int)}
1263     */
1264    public IcyBufferedImage getImage(int t, int z, int c)
1265    {
1266        if (canvas != null)
1267            return canvas.getImage(t, z, c);
1268
1269        return null;
1270    }
1271
1272    /**
1273     * @deprecated Use {@link #getImage(int, int, int)} with C = -1 instead.
1274     */
1275    @Deprecated
1276    public IcyBufferedImage getImage(int t, int z)
1277    {
1278        return getImage(t, z, -1);
1279    }
1280
1281    /**
1282     * Get the current image
1283     * 
1284     * @return current image
1285     */
1286    public IcyBufferedImage getCurrentImage()
1287    {
1288        if (canvas != null)
1289            return canvas.getCurrentImage();
1290
1291        return null;
1292    }
1293
1294    /**
1295     * Return the number of "selected" samples
1296     */
1297    public int getNumSelectedSamples()
1298    {
1299        if (canvas != null)
1300            return canvas.getNumSelectedSamples();
1301
1302        return 0;
1303    }
1304
1305    /**
1306     * @see icy.canvas.IcyCanvas#getRenderedImage(int, int, int, boolean)
1307     */
1308    public BufferedImage getRenderedImage(int t, int z, int c, boolean canvasView)
1309    {
1310        if (canvas == null)
1311            return null;
1312
1313        return canvas.getRenderedImage(t, z, c, canvasView);
1314    }
1315
1316    /**
1317     * @see icy.canvas.IcyCanvas#getRenderedSequence(boolean, icy.common.listener.ProgressListener)
1318     */
1319    public Sequence getRenderedSequence(boolean canvasView, ProgressListener progressListener)
1320    {
1321        if (canvas == null)
1322            return null;
1323
1324        return canvas.getRenderedSequence(canvasView, progressListener);
1325    }
1326
1327    /**
1328     * Returns the T navigation panel.
1329     */
1330    protected TNavigationPanel getTNavigationPanel()
1331    {
1332        if (canvas == null)
1333            return null;
1334
1335        return canvas.getTNavigationPanel();
1336    }
1337
1338    /**
1339     * Returns the frame rate (given in frame per second) for play command.
1340     */
1341    public int getFrameRate()
1342    {
1343        final TNavigationPanel tNav = getTNavigationPanel();
1344
1345        if (tNav != null)
1346            return tNav.getFrameRate();
1347
1348        return 0;
1349    }
1350
1351    /**
1352     * Sets the frame rate (given in frame per second) for play command.
1353     */
1354    public void setFrameRate(int fps)
1355    {
1356        final TNavigationPanel tNav = getTNavigationPanel();
1357
1358        if (tNav != null)
1359            tNav.setFrameRate(fps);
1360    }
1361
1362    /**
1363     * Returns true if <code>repeat</code> is enabled for play command.
1364     */
1365    public boolean isRepeat()
1366    {
1367        final TNavigationPanel tNav = getTNavigationPanel();
1368
1369        if (tNav != null)
1370            return tNav.isRepeat();
1371
1372        return false;
1373    }
1374
1375    /**
1376     * Set <code>repeat</code> mode for play command.
1377     */
1378    public void setRepeat(boolean value)
1379    {
1380        final TNavigationPanel tNav = getTNavigationPanel();
1381
1382        if (tNav != null)
1383            tNav.setRepeat(value);
1384    }
1385
1386    /**
1387     * Returns true if currently playing.
1388     */
1389    public boolean isPlaying()
1390    {
1391        final TNavigationPanel tNav = getTNavigationPanel();
1392
1393        if (tNav != null)
1394            return tNav.isPlaying();
1395
1396        return false;
1397    }
1398
1399    /**
1400     * Start sequence play.
1401     * 
1402     * @see #stopPlay()
1403     * @see #setRepeat(boolean)
1404     */
1405    public void startPlay()
1406    {
1407        final TNavigationPanel tNav = getTNavigationPanel();
1408
1409        if (tNav != null)
1410            tNav.startPlay();
1411    }
1412
1413    /**
1414     * Stop sequence play.
1415     * 
1416     * @see #startPlay()
1417     */
1418    public void stopPlay()
1419    {
1420        final TNavigationPanel tNav = getTNavigationPanel();
1421
1422        if (tNav != null)
1423            tNav.stopPlay();
1424    }
1425
1426    /**
1427     * Return true if only this viewer is currently displaying its attached sequence
1428     */
1429    public boolean isUnique()
1430    {
1431        return Icy.getMainInterface().isUniqueViewer(this);
1432    }
1433
1434    protected void lutChanged()
1435    {
1436        // can be called from external thread, replace it in AWT dispatch thread
1437        ThreadUtil.bgRunSingle(lutUpdater);
1438    }
1439
1440    protected void positionChanged(DimensionId dim)
1441    {
1442        fireViewerChanged(ViewerEventType.POSITION_CHANGED, dim);
1443    }
1444
1445    /**
1446     * Add a listener
1447     * 
1448     * @param listener
1449     */
1450    public void addListener(ViewerListener listener)
1451    {
1452        listeners.add(ViewerListener.class, listener);
1453    }
1454
1455    /**
1456     * Remove a listener
1457     * 
1458     * @param listener
1459     */
1460    public void removeListener(ViewerListener listener)
1461    {
1462        listeners.remove(ViewerListener.class, listener);
1463    }
1464
1465    void fireViewerChanged(ViewerEventType eventType, DimensionId dim)
1466    {
1467        final ViewerEvent event = new ViewerEvent(this, eventType, dim);
1468
1469        for (ViewerListener viewerListener : listeners.getListeners(ViewerListener.class))
1470            viewerListener.viewerChanged(event);
1471    }
1472
1473    void fireViewerChanged(ViewerEventType event)
1474    {
1475        fireViewerChanged(event, DimensionId.NULL);
1476    }
1477
1478    protected void fireViewerClosed()
1479    {
1480        for (ViewerListener viewerListener : listeners.getListeners(ViewerListener.class))
1481            viewerListener.viewerClosed(this);
1482    }
1483
1484    @Override
1485    public void keyPressed(KeyEvent e)
1486    {
1487        // forward to canvas
1488        if ((canvas != null) && (!e.isConsumed()))
1489            canvas.keyPressed(e);
1490    }
1491
1492    @Override
1493    public void keyReleased(KeyEvent e)
1494    {
1495        // forward to canvas
1496        if ((canvas != null) && (!e.isConsumed()))
1497            canvas.keyReleased(e);
1498    }
1499
1500    @Override
1501    public void keyTyped(KeyEvent e)
1502    {
1503        // forward to canvas
1504        if ((canvas != null) && (!e.isConsumed()))
1505            canvas.keyTyped(e);
1506    }
1507
1508    /**
1509     * Change the frame's title.
1510     */
1511    protected void refreshViewerTitle()
1512    {
1513        // have to test this as we release sequence reference on closed
1514        if (sequence != null)
1515            setTitle(sequence.getName());
1516    }
1517
1518    @Override
1519    public void sequenceChanged(SequenceEvent event)
1520    {
1521        switch (event.getSourceType())
1522        {
1523            case SEQUENCE_META:
1524                final String meta = (String) event.getSource();
1525
1526                if (StringUtil.isEmpty(meta) || StringUtil.equals(meta, Sequence.ID_NAME))
1527                    refreshViewerTitle();
1528                // update virtual state if needed
1529                if (initialized && StringUtil.equals(meta, Sequence.ID_VIRTUAL))
1530                    refreshToolBar();
1531                break;
1532
1533            case SEQUENCE_DATA:
1534                break;
1535
1536            case SEQUENCE_TYPE:
1537                // might need initialization
1538                if (!initialized && (sequence != null) && !sequence.isEmpty())
1539                {
1540                    adjustViewerToImageSize();
1541                    initialized = true;
1542                }
1543
1544                // we update LUT on type change directly on getLut() method
1545
1546                // // try to keep current LUT if possible
1547                if (!sequence.isLutCompatible(lut))
1548                    // need to update the lut according to the colormodel change
1549                    setLut(sequence.createCompatibleLUT());
1550                break;
1551
1552            case SEQUENCE_COLORMAP:
1553
1554                break;
1555
1556            case SEQUENCE_COMPONENTBOUNDS:
1557                // refresh lut scalers from sequence default lut
1558                final LUT sequenceLut = sequence.getDefaultLUT();
1559
1560                if (!sequenceLut.isCompatible(lut) || (lutViewer == null))
1561                    lut.setScalers(sequenceLut);
1562                break;
1563        }
1564    }
1565
1566    @Override
1567    public void sequenceClosed(Sequence sequence)
1568    {
1569
1570    }
1571
1572    @Override
1573    public void canvasChanged(IcyCanvasEvent event)
1574    {
1575        switch (event.getType())
1576        {
1577            case POSITION_CHANGED:
1578                // common process on position change
1579                positionChanged(event.getDim());
1580                break;
1581
1582            case SYNC_CHANGED:
1583                ThreadUtil.invokeLater(new Runnable()
1584                {
1585                    @Override
1586                    public void run()
1587                    {
1588                        refreshLockCombo();
1589                    }
1590                });
1591                break;
1592        }
1593    }
1594
1595    @Override
1596    public void propertyChange(PropertyChangeEvent evt)
1597    {
1598        super.propertyChange(evt);
1599
1600        // Canvas property "layer visible" changed ?
1601        if (StringUtil.equals(evt.getPropertyName(), IcyCanvas.PROPERTY_LAYERS_VISIBLE))
1602        {
1603            refreshToolBar();
1604            updateSystemMenu();
1605        }
1606    }
1607
1608    @Override
1609    public void pluginLoaderChanged(PluginLoaderEvent e)
1610    {
1611        ThreadUtil.invokeLater(new Runnable()
1612        {
1613            @Override
1614            public void run()
1615            {
1616                // refresh available canvas
1617                buildCanvasCombo();
1618                // and refresh components
1619                refreshCanvasCombo();
1620                updateToolbarComponents();
1621            }
1622        });
1623    }
1624}