/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.canvas;

import icy.action.CanvasActions;
import icy.action.GeneralActions;
import icy.action.RoiActions;
import icy.action.WindowActions;
import icy.canvas.CanvasLayerEvent.LayersEventType;
import icy.canvas.IcyCanvasEvent.IcyCanvasEventType;
import icy.canvas.Layer.LayerListener;
import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.listener.ChangeListener;
import icy.common.listener.ProgressListener;
import icy.gui.util.GuiUtil;
import icy.gui.viewer.MouseImageInfosPanel;
import icy.gui.viewer.TNavigationPanel;
import icy.gui.viewer.Viewer;
import icy.gui.viewer.ViewerEvent;
import icy.gui.viewer.ViewerListener;
import icy.gui.viewer.ZNavigationPanel;
import icy.image.IcyBufferedImage;
import icy.image.colormodel.IcyColorModel;
import icy.image.lut.LUT;
import icy.image.lut.LUTEvent;
import icy.image.lut.LUTEvent.LUTEventType;
import icy.image.lut.LUTListener;
import icy.main.Icy;
import icy.painter.Overlay;
import icy.painter.OverlayWrapper;
import icy.painter.Painter;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginLoader;
import icy.plugin.interface_.PluginCanvas;
import icy.roi.ROI;
import icy.sequence.DimensionId;
import icy.sequence.Sequence;
import icy.sequence.SequenceEvent;
import icy.sequence.SequenceEvent.SequenceEventType;
import icy.sequence.SequenceListener;
import icy.system.IcyExceptionHandler;
import icy.system.thread.ThreadUtil;
import icy.type.point.Point5D;
import icy.util.ClassUtil;
import icy.util.EventUtil;
import icy.util.OMEUtil;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.event.ChangeEvent;

import plugins.kernel.canvas.Canvas2DPlugin;
import plugins.kernel.canvas.VtkCanvasPlugin;

/**
 * @author Fabrice de Chaumont & Stephane Dallongeville<br>
 * <br>
 *         An IcyCanvas is a basic Canvas used into the viewer. It contains a visual representation
 *         of the sequence and provides some facilities as basic transformation and view
 *         synchronization.<br>
 *         Also IcyCanvas receives key events from Viewer when they are not consumed.<br>
 * <br>
 *         By default transformations are applied in following order :<br>
 *         Rotation, Translation then Scaling.<br>
 *         The rotation transformation is relative to canvas center.<br>
 * <br>
 *         Free feel to implement and override this design or not. <br>
 * <br>
 *         (Canvas2D and Canvas3D derives from IcyCanvas)<br>
 */
public abstract class IcyCanvas extends JPanel implements KeyListener, ViewerListener, SequenceListener, LUTListener,
        ChangeListener, LayerListener
{
    protected class IcyCanvasImageOverlay extends Overlay
    {
        public IcyCanvasImageOverlay()
        {
            super((getSequence() == null) ? "Image" : getSequence().getName(), OverlayPriority.IMAGE_NORMAL);

            // we fix the image overlay
            canBeRemoved = false;
            readOnly = false;
        }

        @Override
        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
        {
            // default lazy implementation (very slow)
            if (g != null)
                g.drawImage(getCurrentImage(), null, 0, 0);
        }
    }

    /**
     * Returns all {@link PluginCanvas} plugins (kernel plugin are returned first).
     */
    public static List<PluginDescriptor> getCanvasPlugins()
    {
        // get all canvas plugins
        final List<PluginDescriptor> result = PluginLoader.getPlugins(PluginCanvas.class);

        // VTK is not loaded ?
        if (!Icy.isVtkLibraryLoaded())
        {
            // remove VtkCanvas
            final int ind = PluginDescriptor.getIndex(result, VtkCanvasPlugin.class.getName());
            if (ind != -1)
                result.remove(ind);
        }

        // sort plugins list
        Collections.sort(result, new Comparator<PluginDescriptor>()
        {
            @Override
            public int compare(PluginDescriptor o1, PluginDescriptor o2)
            {
                return Integer.valueOf(getOrder(o1)).compareTo(Integer.valueOf(getOrder(o2)));
            }

            int getOrder(PluginDescriptor p)
            {
                if (p.getClassName().equals(Canvas2DPlugin.class.getName()))
                    return 0;
                if (p.getClassName().equals(VtkCanvasPlugin.class.getName()))
                    return 1;

                return 10;
            }
        });

        return result;
    }

    /**
     * Returns all {@link PluginCanvas} plugins class name (kernel plugin are returned first).
     */
    public static List<String> getCanvasPluginNames()
    {
        // get all canvas plugins
        final List<PluginDescriptor> plugins = getCanvasPlugins();
        final List<String> result = new ArrayList<String>();

        for (PluginDescriptor plugin : plugins)
            result.add(plugin.getClassName());

        return result;
    }

    /**
     * Returns the plugin class name corresponding to the specified Canvas class name.<br>
     * Returns <code>null</code> if we can't find a corresponding plugin.
     */
    public static String getPluginClassName(String canvasClassName)
    {
        for (PluginDescriptor plugin : IcyCanvas.getCanvasPlugins())
        {
            final String className = getCanvasClassName(plugin);

            // we found the corresponding plugin
            if (canvasClassName.equals(className))
                // get plugin class name
                return plugin.getClassName();
        }

        return null;
    }

    /**
     * Returns the canvas class name corresponding to the specified {@link PluginCanvas} plugin.<br>
     * Returns <code>null</code> if we can't retrieve the corresponding canvas class name.
     */
    public static String getCanvasClassName(PluginDescriptor plugin)
    {
        try
        {
            if (plugin != null)
            {
                final PluginCanvas pluginCanvas = (PluginCanvas) plugin.getPluginClass().newInstance();
                // return canvas class name
                return pluginCanvas.getCanvasClassName();
            }
        }
        catch (Exception e)
        {
            IcyExceptionHandler.showErrorMessage(e, true);
        }

        return null;
    }

    /**
     * Returns the canvas class name corresponding to the specified {@link PluginCanvas} class name. <br>
     * Returns <code>null</code> if we can't find retrieve the corresponding canvas class name.
     */
    public static String getCanvasClassName(String pluginClassName)
    {
        return getCanvasClassName(PluginLoader.getPlugin(pluginClassName));
    }

    // /**
    // * Return the class name of all {@link PluginCanvas}.
    // */
    // public static List<String> getCanvasPlugins()
    // {
    // // get all canvas plugins
    // final List<PluginDescriptor> plugins = PluginLoader.getPlugins(PluginCanvas.class);
    // final List<String> result = new ArrayList<String>();
    //
    // // we want the default 2D and 3D canvas to be first
    // result.add(Canvas2DPlugin.class.getName());
    // if (Icy.isVtkLibraryLoaded())
    // result.add(VtkCanvasPlugin.class.getName());
    //
    // for (PluginDescriptor plugin : plugins)
    // {
    // final String className = plugin.getClassName();
    //
    // // ignore default canvas as they have been already added
    // if (Canvas2DPlugin.class.getName().equals(className))
    // continue;
    // if (VtkCanvasPlugin.class.getName().equals(className))
    // continue;
    //
    // CollectionUtil.addUniq(result, plugin.getClassName());
    // }
    //
    // return result;
    // }

    /**
     * Create a {@link IcyCanvas} object from its class name or {@link PluginCanvas} class name.<br>
     * Throws an exception if an error occurred (canvas class was not found or it could not be
     * creatd).
     * 
     * @param viewer
     *        {@link Viewer} to which to canvas is attached.
     * @throws ClassCastException
     *         if the specified class name is not a canvas plugin or canvas class name
     * @throws Exception
     *         if the specified canvas cannot be created for some reasons
     */
    public static IcyCanvas create(String className, Viewer viewer) throws ClassCastException, Exception
    {
        // search for the specified className
        final Class<?> clazz = ClassUtil.findClass(className);
        final Class<? extends PluginCanvas> pluginCanvasClazz;

        try
        {
            // we first check if we have a IcyCanvas Plugin class here
            pluginCanvasClazz = clazz.asSubclass(PluginCanvas.class);
        }
        catch (ClassCastException e0)
        {
            // check if this is a IcyCanvas class
            final Class<? extends IcyCanvas> canvasClazz = clazz.asSubclass(IcyCanvas.class);

            // get constructor (Viewer)
            final Constructor<? extends IcyCanvas> constructor = canvasClazz.getConstructor(new Class[] {Viewer.class});
            // build canvas
            return constructor.newInstance(new Object[] {viewer});
        }

        // create canvas from plugin
        return pluginCanvasClazz.newInstance().createCanvas(viewer);
    }

    public static void addVisibleLayerToList(final Layer layer, ArrayList<Layer> list)
    {
        if ((layer != null) && (layer.isVisible()))
            list.add(layer);
    }

    private static final long serialVersionUID = -8461229450296203011L;

    public static final String PROPERTY_LAYERS_VISIBLE = "layersVisible";

    /**
     * Navigations bar
     */
    final protected ZNavigationPanel zNav;
    final protected TNavigationPanel tNav;

    /**
     * The panel where mouse informations are displayed
     */
    protected final MouseImageInfosPanel mouseInfPanel;

    /**
     * The panel contains all settings and informations data such as<br>
     * scale factor, rendering mode...
     * Will be retrieved by the inspector to get information on the current canvas.
     */
    protected JPanel panel;

    /**
     * attached viewer
     */
    protected final Viewer viewer;
    /**
     * layers visible flag
     */
    protected boolean layersVisible;
    /**
     * synchronization group :<br>
     * 0 = unsynchronized
     * 1 = full synchronization group 1
     * 2 = full synchronization group 2
     * 3 = view synchronization group (T and Z navigation are not synchronized)
     * 4 = slice synchronization group (only T and Z navigation are synchronized)
     */
    protected int syncId;

    /**
     * Overlay/Layer used to display sequence image
     */
    protected final Overlay imageOverlay;
    protected final Layer imageLayer;

    /**
     * Layers attached to canvas<br>
     * There are representing sequence overlays with some visualization properties
     */
    protected final Map<Overlay, Layer> layers;
    /**
     * Priority ordered layers.
     */
    protected List<Layer> orderedLayers;

    /**
     * internal updater
     */
    protected final UpdateEventHandler updater;
    /**
     * listeners
     */
    protected final List<IcyCanvasListener> listeners;
    protected final List<CanvasLayerListener> layerListeners;

    /**
     * Current X position (should be -1 when canvas handle multi X dimension view).
     */
    protected int posX;
    /**
     * Current Y position (should be -1 when canvas handle multi Y dimension view).
     */
    protected int posY;
    /**
     * Current Z position (should be -1 when canvas handle multi Z dimension view).
     */
    protected int posZ;
    /**
     * Current T position (should be -1 when canvas handle multi T dimension view).
     */
    protected int posT;
    /**
     * Current C position (should be -1 when canvas handle multi C dimension view).
     */
    protected int posC;

    /**
     * Current mouse position (canvas coordinate space)
     */
    protected Point mousePos;

    /**
     * internals
     */
    protected LUT lut;
    protected boolean synchMaster;
    protected boolean orderedLayersOutdated;
    private Runnable guiUpdater;

    /**
     * Constructor
     * 
     * @param viewer
     */
    public IcyCanvas(Viewer viewer)
    {
        super();

        // default
        this.viewer = viewer;

        layersVisible = true;
        layers = new HashMap<Overlay, Layer>();
        orderedLayers = new ArrayList<Layer>();
        syncId = 0;
        synchMaster = false;
        orderedLayersOutdated = false;
        updater = new UpdateEventHandler(this, false);

        // default position
        mousePos = new Point(0, 0);
        posX = -1;
        posY = -1;
        posZ = -1;
        posT = -1;
        posC = -1;

        // GUI stuff
        panel = new JPanel();

        listeners = new ArrayList<IcyCanvasListener>();
        layerListeners = new ArrayList<CanvasLayerListener>();

        // Z navigation
        zNav = new ZNavigationPanel();
        zNav.addChangeListener(new javax.swing.event.ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                // set the new Z position
                setPositionZ(zNav.getValue());
            }
        });

        // T navigation
        tNav = new TNavigationPanel();
        tNav.addChangeListener(new javax.swing.event.ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                // set the new T position
                setPositionT(tNav.getValue());
            }
        });

        // mouse info panel
        mouseInfPanel = new MouseImageInfosPanel();

        // default canvas layout
        setLayout(new BorderLayout());

        add(zNav, BorderLayout.WEST);
        add(GuiUtil.createPageBoxPanel(tNav, mouseInfPanel), BorderLayout.SOUTH);

        // asynchronous updater for GUI
        guiUpdater = new Runnable()
        {
            @Override
            public void run()
            {
                ThreadUtil.invokeNow(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // update sliders bounds if needed
                        updateZNav();
                        updateTNav();

                        // adjust X position if needed
                        final int maxX = getMaxPositionX();
                        final int curX = getPositionX();
                        if ((curX != -1) && (curX > maxX))
                            setPositionX(maxX);

                        // adjust Y position if needed
                        final int maxY = getMaxPositionY();
                        final int curY = getPositionY();
                        if ((curY != -1) && (curY > maxY))
                            setPositionY(maxY);

                        // adjust C position if needed
                        final int maxC = getMaxPositionC();
                        final int curC = getPositionC();
                        if ((curC != -1) && (curC > maxC))
                            setPositionC(maxC);

                        // adjust Z position if needed
                        final int maxZ = getMaxPositionZ();
                        final int curZ = getPositionZ();
                        if ((curZ != -1) && (curZ > maxZ))
                            setPositionZ(maxZ);

                        // adjust T position if needed
                        final int maxT = getMaxPositionT();
                        final int curT = getPositionT();
                        if ((curT != -1) && (curT > maxT))
                            setPositionT(maxT);

                        // refresh mouse panel informations (data values can have changed)
                        mouseInfPanel.updateInfos(IcyCanvas.this);
                    }
                });
            }
        };

        // create image overlay
        imageOverlay = createImageOverlay();

        // create layers from overlays
        beginUpdate();
        try
        {
            // first add image layer
            imageLayer = addLayer(getImageOverlay());

            final Sequence sequence = getSequence();

            if (sequence != null)
            {
                // then add sequence overlays to layer list
                for (Overlay overlay : sequence.getOverlays())
                    addLayer(overlay);
            }
            else
                System.err.println("Sequence null when canvas created");
        }
        finally
        {
            endUpdate();
        }

        // add listeners
        viewer.addListener(this);
        final Sequence seq = getSequence();
        if (seq != null)
            seq.addListener(this);

        // set lut (no event wanted here)
        lut = null;
        setLut(viewer.getLut(), false);
    }

    /**
     * Called by the viewer when canvas is closed to release some resources.<br/>
     * Be careful to not restore previous state here (as the colormap) because generally <code>shutdown</code> is called
     * <b>after</b> the creation of the other canvas.
     */
    public void shutDown()
    {
        // remove navigation panel listener
        zNav.removeAllChangeListener();
        tNav.removeAllChangeListener();

        // remove listeners
        if (lut != null)
            lut.removeListener(this);
        final Sequence seq = getSequence();
        if (seq != null)
            seq.removeListener(this);
        viewer.removeListener(this);

        // remove all layers
        beginUpdate();
        try
        {
            for (Layer layer : getLayers())
                removeLayer(layer);
        }
        finally
        {
            endUpdate();
        }

        // release layers
        orderedLayers.clear();

        // remove all IcyCanvas & Layer listeners
        listeners.clear();
        layerListeners.clear();
    }

    /**
     * Force canvas refresh
     */
    public abstract void refresh();

    protected Overlay createImageOverlay()
    {
        // default image overlay
        return new IcyCanvasImageOverlay();
    }

    /**
     * Returns the {@link Overlay} used to display the current sequence image
     */
    public Overlay getImageOverlay()
    {
        return imageOverlay;
    }

    /**
     * Returns the {@link Layer} object used to display the current sequence image
     */
    public Layer getImageLayer()
    {
        return imageLayer;
    }

    /**
     * @deprecated Use {@link #isLayersVisible()} instead.
     */
    @Deprecated
    public boolean getDrawLayers()
    {
        return isLayersVisible();
    }

    /**
     * @deprecated Use {@link #setLayersVisible(boolean)} instead.
     */
    @Deprecated
    public void setDrawLayers(boolean value)
    {
        setLayersVisible(value);
    }

    /**
     * Return true if layers are visible on the canvas.
     */
    public boolean isLayersVisible()
    {
        return layersVisible;
    }

    /**
     * Make layers visible on this canvas (default = true).
     */
    public void setLayersVisible(boolean value)
    {
        if (layersVisible != value)
        {
            layersVisible = value;
            layersVisibleChanged();
            firePropertyChange(PROPERTY_LAYERS_VISIBLE, !value, value);
        }
    }

    /**
     * Global layers visibility changed
     */
    protected void layersVisibleChanged()
    {
        final Component comp = getViewComponent();

        if (comp != null)
            comp.repaint();
    }

    /**
     * @return the viewer
     */
    public Viewer getViewer()
    {
        return viewer;
    }

    /**
     * @return the sequence
     */
    public Sequence getSequence()
    {
        return viewer.getSequence();
    }

    /**
     * @return the main view component
     */
    public abstract Component getViewComponent();

    /**
     * @return the Z navigation bar panel
     */
    public ZNavigationPanel getZNavigationPanel()
    {
        return zNav;
    }

    /**
     * @return the T navigation bar panel
     */
    public TNavigationPanel getTNavigationPanel()
    {
        return tNav;
    }

    /**
     * @return the mouse image informations panel
     */
    public MouseImageInfosPanel getMouseImageInfosPanel()
    {
        return mouseInfPanel;
    }

    /**
     * @return the LUT
     */
    public LUT getLut()
    {
        // ensure we have the good lut
        setLut(viewer.getLut(), true);

        return lut;
    }

    /**
     * set canvas LUT
     */
    private void setLut(LUT lut, boolean event)
    {
        if (this.lut != lut)
        {
            if (this.lut != null)
                this.lut.removeListener(this);

            this.lut = lut;

            // add listener to the new lut
            if (lut != null)
                lut.addListener(this);

            // launch a lutChanged event if wanted
            if (event)
                lutChanged(new LUTEvent(lut, -1, LUTEventType.COLORMAP_CHANGED));
        }
    }

    /**
     * @deprecated Use {@link #customizeToolbar(JToolBar)} instead.
     */
    @SuppressWarnings("unused")
    @Deprecated
    public void addViewerToolbarComponents(JToolBar toolBar)
    {

    }

    /**
     * Called by the parent viewer when building the toolbar.<br>
     * This way the canvas can customize it by adding specific command for instance.<br>
     * 
     * @param toolBar
     *        the parent toolbar to customize
     */
    public void customizeToolbar(JToolBar toolBar)
    {
        addViewerToolbarComponents(toolBar);
    }

    /**
     * Returns the setting panel of this canvas.<br>
     * The setting panel is displayed in the inspector so user can change canvas parameters.
     */
    public JPanel getPanel()
    {
        return panel;
    }

    /**
     * Returns all layers attached to this canvas.<br/>
     * 
     * @param sorted
     *        If <code>true</code> the returned list is sorted on the layer priority.<br>
     *        Sort operation is cached so the method could take sometime when sort cache need to be
     *        rebuild.
     */
    public List<Layer> getLayers(boolean sorted)
    {
        if (sorted)
        {
            // need to rebuild sorted layer list ?
            if (orderedLayersOutdated)
            {
                // build and sort the list
                synchronized (layers)
                {
                    orderedLayers = new ArrayList<Layer>(layers.values());
                }
                Collections.sort(orderedLayers);

                orderedLayersOutdated = false;
            }

            return new ArrayList<Layer>(orderedLayers);
        }

        synchronized (layers)
        {
            return new ArrayList<Layer>(layers.values());
        }
    }

    /**
     * Returns all layers attached to this canvas.<br/>
     * The returned list is sorted on the layer priority.<br>
     * Sort operation is cached so the method could take sometime when cache need to be rebuild.
     */
    public List<Layer> getLayers()
    {
        return getLayers(true);
    }

    /**
     * Returns all visible layers (visible property set to <code>true</code>) attached to this
     * canvas.
     * 
     * @param sorted
     *        If <code>true</code> the returned list is sorted on the layer priority.<br>
     *        Sort operation is cached so the method could take sometime when sort cache need to be
     *        rebuild.
     */
    public List<Layer> getVisibleLayers(boolean sorted)
    {
        final List<Layer> olayers = getLayers(sorted);
        final List<Layer> result = new ArrayList<Layer>(olayers.size());

        for (Layer l : olayers)
            if (l.isVisible())
                result.add(l);

        return result;
    }

    /**
     * Returns all visible layers (visible property set to <code>true</code>) attached to this
     * canvas.<br/>
     * The list is sorted on the layer priority.
     */
    public ArrayList<Layer> getVisibleLayers()
    {
        return (ArrayList<Layer>) getVisibleLayers(true);
    }

    /**
     * @deprecated Use {@link #getLayers()} instead (sorted on Layer priority).
     */
    @Deprecated
    public List<Layer> getOrderedLayersForEvent()
    {
        return getLayers();
    }

    /**
     * @deprecated Use {@link #getVisibleLayers()} instead (sorted on Layer priority).
     */
    @Deprecated
    public List<Layer> getVisibleOrderedLayersForEvent()
    {
        return getVisibleLayers();
    }

    /**
     * @deprecated Use {@link #getOverlays()} instead.
     */
    @Deprecated
    public List<Painter> getLayersPainter()
    {
        final ArrayList<Painter> result = new ArrayList<Painter>();

        for (Overlay overlay : getOverlays())
        {
            if (overlay instanceof OverlayWrapper)
                result.add(((OverlayWrapper) overlay).getPainter());
            else
                result.add(overlay);
        }

        return result;
    }

    /**
     * Directly returns a {@link Set} of all Overlay displayed by this canvas.
     */
    public Set<Overlay> getOverlays()
    {
        synchronized (layers)
        {
            return new HashSet<Overlay>(layers.keySet());
        }
    }

    /**
     * @return the SyncId
     */
    public int getSyncId()
    {
        return syncId;
    }

    /**
     * Set the synchronization group id (0 means unsynchronized).<br>
     * 
     * @return <code>false</code> if the canvas do not support synchronization group.
     * @param id
     *        the syncId to set
     */
    public boolean setSyncId(int id)
    {
        if (!isSynchronizationSupported())
            return false;

        if (this.syncId != id)
        {
            this.syncId = id;

            // notify sync has changed
            updater.changed(new IcyCanvasEvent(this, IcyCanvasEventType.SYNC_CHANGED));
        }

        return true;
    }

    /**
     * Return true if this canvas support synchronization
     */
    public boolean isSynchronizationSupported()
    {
        // default (override it when supported)
        return false;
    }

    /**
     * Return true if this canvas is synchronized
     */
    public boolean isSynchronized()
    {
        return syncId > 0;
    }

    /**
     * Return true if current canvas is synchronized and is currently the synchronize leader.
     */
    public boolean isSynchMaster()
    {
        return synchMaster;
    }

    /**
     * @deprecated Use {@link #isSynchMaster()} instead.
     */
    @Deprecated
    public boolean isSynchHeader()
    {
        return isSynchMaster();
    }

    /**
     * Return true if current canvas is synchronized and it's not the synchronize master
     */
    public boolean isSynchSlave()
    {
        if (isSynchronized())
        {
            if (isSynchMaster())
                return false;

            // search for a master in synchronized canvas
            for (IcyCanvas cnv : getSynchronizedCanvas())
                if (cnv.isSynchMaster())
                    return true;
        }

        return false;
    }

    /**
     * Return true if this canvas is synchronized on view (offset, zoom and rotation).
     */
    public boolean isSynchOnView()
    {
        return (syncId == 1) || (syncId == 2) || (syncId == 3);
    }

    /**
     * Return true if this canvas is synchronized on slice (T and Z position)
     */
    public boolean isSynchOnSlice()
    {
        return (syncId == 1) || (syncId == 2) || (syncId == 4);
    }

    /**
     * Return true if this canvas is synchronized on cursor (mouse cursor)
     */
    public boolean isSynchOnCursor()
    {
        return (syncId > 0);
    }

    /**
     * Return true if we get the synchronizer master from synchronized canvas
     */
    protected boolean getSynchMaster()
    {
        return getSynchMaster(getSynchronizedCanvas());
    }

    /**
     * @deprecated Use {@link #getSynchMaster()} instead.
     */
    @Deprecated
    protected boolean getSynchHeader()
    {
        return getSynchMaster();
    }

    /**
     * Return true if we get the synchronizer master from specified canvas list.
     */
    protected boolean getSynchMaster(List<IcyCanvas> canvasList)
    {
        for (IcyCanvas canvas : canvasList)
            if (canvas.isSynchMaster())
                return canvas == this;

        // no master found so we are master
        synchMaster = true;

        return true;
    }

    /**
     * @deprecated Use {@link #getSynchMaster(List)} instead.
     */
    @Deprecated
    protected boolean getSynchHeader(List<IcyCanvas> canvasList)
    {
        return getSynchMaster(canvasList);
    }

    /**
     * Release synchronizer master
     */
    protected void releaseSynchMaster()
    {
        synchMaster = false;
    }

    /**
     * @deprecated Use {@link #releaseSynchMaster()} instead.
     */
    @Deprecated
    protected void releaseSynchHeader()
    {
        releaseSynchMaster();
    }

    /**
     * Return the list of canvas which are synchronized with the current one
     */
    private List<IcyCanvas> getSynchronizedCanvas()
    {
        final List<IcyCanvas> result = new ArrayList<IcyCanvas>();

        if (isSynchronized())
        {
            final List<Viewer> viewers = Icy.getMainInterface().getViewers();

            for (int i = viewers.size() - 1; i >= 0; i--)
            {
                final IcyCanvas cnv = viewers.get(i).getCanvas();

                if ((cnv == this) || (cnv.getSyncId() != syncId))
                    viewers.remove(i);
            }

            for (Viewer v : viewers)
            {
                final IcyCanvas cnv = v.getCanvas();

                // only permit same class
                if (cnv.getClass().isInstance(this))
                    result.add(cnv);
            }
        }

        return result;
    }

    /**
     * Synchronize views of specified list of canvas
     */
    protected void synchronizeCanvas(List<IcyCanvas> canvasList, IcyCanvasEvent event, boolean processAll)
    {
        final IcyCanvasEventType type = event.getType();
        final DimensionId dim = event.getDim();

        // position synchronization
        if (isSynchOnSlice())
        {
            if (processAll || (type == IcyCanvasEventType.POSITION_CHANGED))
            {
                // no information about dimension --> set all
                if (processAll || (dim == DimensionId.NULL))
                {
                    final int x = getPositionX();
                    final int y = getPositionY();
                    final int z = getPositionZ();
                    final int t = getPositionT();
                    final int c = getPositionC();

                    for (IcyCanvas cnv : canvasList)
                    {
                        if (x != -1)
                            cnv.setPositionX(x);
                        if (y != -1)
                            cnv.setPositionY(y);
                        if (z != -1)
                            cnv.setPositionZ(z);
                        if (t != -1)
                            cnv.setPositionT(t);
                        if (c != -1)
                            cnv.setPositionC(c);
                    }
                }
                else
                {
                    for (IcyCanvas cnv : canvasList)
                    {
                        final int pos = getPosition(dim);
                        if (pos != -1)
                            cnv.setPosition(dim, pos);
                    }
                }
            }
        }

        // view synchronization
        if (isSynchOnView())
        {
            if (processAll || (type == IcyCanvasEventType.SCALE_CHANGED))
            {
                // no information about dimension --> set all
                if (processAll || (dim == DimensionId.NULL))
                {
                    final double sX = getScaleX();
                    final double sY = getScaleY();
                    final double sZ = getScaleZ();
                    final double sT = getScaleT();
                    final double sC = getScaleC();

                    for (IcyCanvas cnv : canvasList)
                    {
                        cnv.setScaleX(sX);
                        cnv.setScaleY(sY);
                        cnv.setScaleZ(sZ);
                        cnv.setScaleT(sT);
                        cnv.setScaleC(sC);
                    }
                }
                else
                {
                    for (IcyCanvas cnv : canvasList)
                        cnv.setScale(dim, getScale(dim));
                }
            }

            if (processAll || (type == IcyCanvasEventType.ROTATION_CHANGED))
            {
                // no information about dimension --> set all
                if (processAll || (dim == DimensionId.NULL))
                {
                    final double rotX = getRotationX();
                    final double rotY = getRotationY();
                    final double rotZ = getRotationZ();
                    final double rotT = getRotationT();
                    final double rotC = getRotationC();

                    for (IcyCanvas cnv : canvasList)
                    {
                        cnv.setRotationX(rotX);
                        cnv.setRotationY(rotY);
                        cnv.setRotationZ(rotZ);
                        cnv.setRotationT(rotT);
                        cnv.setRotationC(rotC);
                    }
                }
                else
                {
                    for (IcyCanvas cnv : canvasList)
                        cnv.setRotation(dim, getRotation(dim));
                }
            }

            // process offset in last as it can be limited depending destination scale value
            if (processAll || (type == IcyCanvasEventType.OFFSET_CHANGED))
            {
                // no information about dimension --> set all
                if (processAll || (dim == DimensionId.NULL))
                {
                    final int offX = getOffsetX();
                    final int offY = getOffsetY();
                    final int offZ = getOffsetZ();
                    final int offT = getOffsetT();
                    final int offC = getOffsetC();

                    for (IcyCanvas cnv : canvasList)
                    {
                        cnv.setOffsetX(offX);
                        cnv.setOffsetY(offY);
                        cnv.setOffsetZ(offZ);
                        cnv.setOffsetT(offT);
                        cnv.setOffsetC(offC);
                    }
                }
                else
                {
                    for (IcyCanvas cnv : canvasList)
                        cnv.setOffset(dim, getOffset(dim));
                }
            }
        }

        // cursor synchronization
        if (isSynchOnCursor())
        {
            // mouse synchronization
            if (processAll || (type == IcyCanvasEventType.MOUSE_IMAGE_POSITION_CHANGED))
            {
                // no information about dimension --> set all
                if (processAll || (dim == DimensionId.NULL))
                {
                    final double mipX = getMouseImagePosX();
                    final double mipY = getMouseImagePosY();
                    final double mipZ = getMouseImagePosZ();
                    final double mipT = getMouseImagePosT();
                    final double mipC = getMouseImagePosC();

                    for (IcyCanvas cnv : canvasList)
                    {
                        cnv.setMouseImagePosX(mipX);
                        cnv.setMouseImagePosY(mipY);
                        cnv.setMouseImagePosZ(mipZ);
                        cnv.setMouseImagePosT(mipT);
                        cnv.setMouseImagePosC(mipC);
                    }
                }
                else
                {
                    for (IcyCanvas cnv : canvasList)
                        cnv.setMouseImagePos(dim, getMouseImagePos(dim));
                }
            }
        }
    }

    /**
     * Get position for specified dimension
     */
    public int getPosition(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getPositionX();
            case Y:
                return getPositionY();
            case Z:
                return getPositionZ();
            case T:
                return getPositionT();
            case C:
                return getPositionC();
        }

        return 0;
    }

    /**
     * @return current X (-1 if all selected)
     */
    public int getPositionX()
    {
        return -1;
    }

    /**
     * @return current Y (-1 if all selected)
     */
    public int getPositionY()
    {
        return -1;
    }

    /**
     * @return current Z (-1 if all selected)
     */
    public int getPositionZ()
    {
        return posZ;
    }

    /**
     * @return current T (-1 if all selected)
     */
    public int getPositionT()
    {
        return posT;
    }

    /**
     * @return current C (-1 if all selected)
     */
    public int getPositionC()
    {
        return posC;
    }

    /**
     * Returns the 5D canvas position (-1 mean that the complete dimension is selected)
     */
    public Point5D.Integer getPosition5D()
    {
        return new Point5D.Integer(getPositionX(), getPositionY(), getPositionZ(), getPositionT(), getPositionC());
    }

    /**
     * @return current Z (-1 if all selected)
     * @deprecated uses getPositionZ() instead
     */
    @Deprecated
    public int getZ()
    {
        return getPositionZ();
    }

    /**
     * @return current T (-1 if all selected)
     * @deprecated uses getPositionT() instead
     */
    @Deprecated
    public int getT()
    {
        return getPositionT();
    }

    /**
     * @return current C (-1 if all selected)
     * @deprecated uses getPositionC() instead
     */
    @Deprecated
    public int getC()
    {
        return getPositionC();
    }

    /**
     * Get maximum position for specified dimension
     */
    public double getMaxPosition(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getMaxPositionX();
            case Y:
                return getMaxPositionY();
            case Z:
                return getMaxPositionZ();
            case T:
                return getMaxPositionT();
            case C:
                return getMaxPositionC();
        }

        return 0;
    }

    /**
     * Get maximum X value
     */
    public int getMaxPositionX()
    {
        final Sequence sequence = getSequence();

        // have to test this as we release sequence reference on closed
        if (sequence == null)
            return 0;

        return Math.max(0, getImageSizeX() - 1);
    }

    /**
     * Get maximum Y value
     */
    public int getMaxPositionY()
    {
        final Sequence sequence = getSequence();

        // have to test this as we release sequence reference on closed
        if (sequence == null)
            return 0;

        return Math.max(0, getImageSizeY() - 1);
    }

    /**
     * Get maximum Z value
     */
    public int getMaxPositionZ()
    {
        final Sequence sequence = getSequence();

        // have to test this as we release sequence reference on closed
        if (sequence == null)
            return 0;

        return Math.max(0, getImageSizeZ() - 1);
    }

    /**
     * Get maximum T value
     */
    public int getMaxPositionT()
    {
        final Sequence sequence = getSequence();

        // have to test this as we release sequence reference on closed
        if (sequence == null)
            return 0;

        return Math.max(0, getImageSizeT() - 1);
    }

    /**
     * Get maximum C value
     */
    public int getMaxPositionC()
    {
        final Sequence sequence = getSequence();

        // have to test this as we release sequence reference on closed
        if (sequence == null)
            return 0;

        return Math.max(0, getImageSizeC() - 1);
    }

    /**
     * Get the maximum 5D position for the canvas.
     * 
     * @see #getPosition5D()
     */
    public Point5D.Integer getMaxPosition5D()
    {
        return new Point5D.Integer(getMaxPositionX(), getMaxPositionY(), getMaxPositionZ(), getMaxPositionT(),
                getMaxPositionC());
    }

    /**
     * @deprecated Use {@link #getMaxPosition(DimensionId)} instead
     */
    @Deprecated
    public double getMax(DimensionId dim)
    {
        return getMaxPosition(dim);
    }

    /**
     * @deprecated Use {@link #getMaxPositionX()} instead
     */
    @Deprecated
    public int getMaxX()
    {
        return getMaxPositionX();
    }

    /**
     * @deprecated Use {@link #getMaxPositionY()} instead
     */
    @Deprecated
    public int getMaxY()
    {
        return getMaxPositionY();
    }

    /**
     * @deprecated Use {@link #getMaxPositionZ()} instead
     */
    @Deprecated
    public int getMaxZ()
    {
        return getMaxPositionZ();
    }

    /**
     * @deprecated Use {@link #getMaxPositionT()} instead
     */
    @Deprecated
    public int getMaxT()
    {
        return getMaxPositionT();
    }

    /**
     * @deprecated Use {@link #getMaxPositionC()} instead
     */
    @Deprecated
    public int getMaxC()
    {
        return getMaxPositionC();
    }

    /**
     * Get canvas view size for specified Dimension
     */
    public int getCanvasSize(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getCanvasSizeX();
            case Y:
                return getCanvasSizeY();
            case Z:
                return getCanvasSizeZ();
            case T:
                return getCanvasSizeT();
            case C:
                return getCanvasSizeC();
        }

        // size not supported
        return -1;
    }

    /**
     * Returns the canvas view size X.
     */
    public int getCanvasSizeX()
    {
        final Component comp = getViewComponent();
        int res = 0;

        if (comp != null)
        {
            // by default we use view component width
            res = comp.getWidth();
            // preferred width if size not yet set
            if (res == 0)
                res = comp.getPreferredSize().width;
        }

        return res;
    }

    /**
     * Returns the canvas view size Y.
     */
    public int getCanvasSizeY()
    {
        final Component comp = getViewComponent();
        int res = 0;

        if (comp != null)
        {
            // by default we use view component width
            res = comp.getHeight();
            // preferred width if size not yet set
            if (res == 0)
                res = comp.getPreferredSize().height;
        }

        return res;
    }

    /**
     * Returns the canvas view size Z.
     */
    public int getCanvasSizeZ()
    {
        // by default : no Z dimension
        return 1;
    }

    /**
     * Returns the canvas view size T.
     */
    public int getCanvasSizeT()
    {
        // by default : no T dimension
        return 1;
    }

    /**
     * Returns the canvas view size C.
     */
    public int getCanvasSizeC()
    {
        // by default : no C dimension
        return 1;
    }

    /**
     * Returns the mouse position (in canvas coordinate space).
     */
    public Point getMousePos()
    {
        return (Point) mousePos.clone();
    }

    /**
     * Get mouse image position for specified Dimension
     */
    public double getMouseImagePos(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getMouseImagePosX();
            case Y:
                return getMouseImagePosY();
            case Z:
                return getMouseImagePosZ();
            case T:
                return getMouseImagePosT();
            case C:
                return getMouseImagePosC();
        }

        return 0;
    }

    /**
     * mouse X image position
     */
    public double getMouseImagePosX()
    {
        // default implementation
        return getPositionX();
    }

    /**
     * mouse Y image position
     */
    public double getMouseImagePosY()
    {
        // default implementation
        return getPositionY();
    }

    /**
     * mouse Z image position
     */
    public double getMouseImagePosZ()
    {
        // default implementation
        return getPositionZ();
    }

    /**
     * mouse T image position
     */
    public double getMouseImagePosT()
    {
        // default implementation
        return getPositionT();
    }

    /**
     * mouse C image position
     */
    public double getMouseImagePosC()
    {
        // default implementation
        return getPositionC();
    }

    /**
     * Returns the 5D mouse image position
     */
    public Point5D.Double getMouseImagePos5D()
    {
        return new Point5D.Double(getMouseImagePosX(), getMouseImagePosY(), getMouseImagePosZ(), getMouseImagePosT(),
                getMouseImagePosC());
    }

    /**
     * Get offset for specified Dimension
     */
    public int getOffset(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getOffsetX();
            case Y:
                return getOffsetY();
            case Z:
                return getOffsetZ();
            case T:
                return getOffsetT();
            case C:
                return getOffsetC();
        }

        return 0;
    }

    /**
     * X offset
     */
    public int getOffsetX()
    {
        return 0;
    }

    /**
     * Y offset
     */
    public int getOffsetY()
    {
        return 0;
    }

    /**
     * Z offset
     */
    public int getOffsetZ()
    {
        return 0;
    }

    /**
     * T offset
     */
    public int getOffsetT()
    {
        return 0;
    }

    /**
     * C offset
     */
    public int getOffsetC()
    {
        return 0;
    }

    /**
     * Returns the 5D offset.
     */
    public Point5D.Integer getOffset5D()
    {
        return new Point5D.Integer(getOffsetX(), getOffsetY(), getOffsetZ(), getOffsetT(), getOffsetC());
    }

    /**
     * X image offset
     * 
     * @deprecated use getOffsetX() instead
     */
    @Deprecated
    public int getImageOffsetX()
    {
        return 0;
    }

    /**
     * Y image offset
     * 
     * @deprecated use getOffsetY() instead
     */
    @Deprecated
    public int getImageOffsetY()
    {
        return 0;
    }

    /**
     * Z image offset
     * 
     * @deprecated use getOffsetZ() instead
     */
    @Deprecated
    public int getImageOffsetZ()
    {
        return 0;
    }

    /**
     * T image offset
     * 
     * @deprecated use getOffsetT() instead
     */
    @Deprecated
    public int getImageOffsetT()
    {
        return 0;
    }

    /**
     * C image offset
     * 
     * @deprecated use getOffsetC() instead
     */
    @Deprecated
    public int getImageOffsetC()
    {
        return 0;
    }

    /**
     * X canvas offset
     * 
     * @deprecated use getOffsetX() instead
     */
    @Deprecated
    public int getCanvasOffsetX()
    {
        return 0;
    }

    /**
     * Y canvas offset
     * 
     * @deprecated use getOffsetY() instead
     */
    @Deprecated
    public int getCanvasOffsetY()
    {
        return 0;
    }

    /**
     * Z canvas offset
     * 
     * @deprecated use getOffsetZ() instead
     */
    @Deprecated
    public int getCanvasOffsetZ()
    {
        return 0;
    }

    /**
     * T canvas offset
     * 
     * @deprecated use getOffsetT() instead
     */
    @Deprecated
    public int getCanvasOffsetT()
    {
        return 0;
    }

    /**
     * C canvas offset
     * 
     * @deprecated use getOffsetC() instead
     */
    @Deprecated
    public int getCanvasOffsetC()
    {
        return 0;
    }

    /**
     * X scale factor
     * 
     * @deprecated use getScaleX() instead
     */
    @Deprecated
    public double getScaleFactorX()
    {
        return getScaleX();
    }

    /**
     * Y scale factor
     * 
     * @deprecated use getScaleY() instead
     */
    @Deprecated
    public double getScaleFactorY()
    {
        return getScaleY();
    }

    /**
     * Z scale factor
     * 
     * @deprecated use getScaleZ() instead
     */
    @Deprecated
    public double getScaleFactorZ()
    {
        return getScaleZ();
    }

    /**
     * T scale factor
     * 
     * @deprecated use getScaleT() instead
     */
    @Deprecated
    public double getScaleFactorT()
    {
        return getScaleT();
    }

    /**
     * C scale factor
     * 
     * @deprecated use getScaleC() instead
     */
    @Deprecated
    public double getScaleFactorC()
    {
        return getScaleC();
    }

    /**
     * Get scale factor for specified Dimension
     */
    public double getScale(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getScaleX();
            case Y:
                return getScaleY();
            case Z:
                return getScaleZ();
            case T:
                return getScaleT();
            case C:
                return getScaleC();
        }

        return 1d;
    }

    /**
     * X scale factor
     */
    public double getScaleX()
    {
        return 1d;
    }

    /**
     * Y scale factor
     */
    public double getScaleY()
    {
        return 1d;
    }

    /**
     * Z scale factor
     */
    public double getScaleZ()
    {
        return 1d;
    }

    /**
     * T scale factor
     */
    public double getScaleT()
    {
        return 1d;
    }

    /**
     * C scale factor
     */
    public double getScaleC()
    {
        return 1d;
    }

    /**
     * Get rotation angle (radian) for specified Dimension
     */
    public double getRotation(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getRotationX();
            case Y:
                return getRotationY();
            case Z:
                return getRotationZ();
            case T:
                return getRotationT();
            case C:
                return getRotationC();
        }

        return 1d;
    }

    /**
     * X rotation angle (radian)
     */
    public double getRotationX()
    {
        return 0d;
    }

    /**
     * Y rotation angle (radian)
     */
    public double getRotationY()
    {
        return 0d;
    }

    /**
     * Z rotation angle (radian)
     */
    public double getRotationZ()
    {
        return 0d;
    }

    /**
     * T rotation angle (radian)
     */
    public double getRotationT()
    {
        return 0d;
    }

    /**
     * C rotation angle (radian)
     */
    public double getRotationC()
    {
        return 0d;
    }

    /**
     * Get image size for specified Dimension
     */
    public int getImageSize(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getImageSizeX();
            case Y:
                return getImageSizeY();
            case Z:
                return getImageSizeZ();
            case T:
                return getImageSizeT();
            case C:
                return getImageSizeC();
        }

        return 0;
    }

    /**
     * Get image size X
     */
    public int getImageSizeX()
    {
        final Sequence seq = getSequence();

        if (seq != null)
            return seq.getSizeX();

        return 0;
    }

    /**
     * Get image size Y
     */
    public int getImageSizeY()
    {
        final Sequence seq = getSequence();

        if (seq != null)
            return seq.getSizeY();

        return 0;
    }

    /**
     * Get image size Z
     */
    public int getImageSizeZ()
    {
        final Sequence seq = getSequence();

        if (seq != null)
            return seq.getSizeZ();

        return 0;
    }

    /**
     * Get image size T
     */
    public int getImageSizeT()
    {
        final Sequence seq = getSequence();

        if (seq != null)
            return seq.getSizeT();

        return 0;
    }

    /**
     * Get image size C
     */
    public int getImageSizeC()
    {
        final Sequence seq = getSequence();

        if (seq != null)
            return seq.getSizeC();

        return 0;
    }

    /**
     * Get image size in canvas pixel coordinate for specified Dimension
     * 
     * @deprecated doesn't take rotation transformation in account.<br>
     *             Use IcyCanvasXD.getImageCanvasSize(..) instead
     */
    @Deprecated
    public int getImageCanvasSize(DimensionId dim)
    {
        switch (dim)
        {
            case X:
                return getImageCanvasSizeX();
            case Y:
                return getImageCanvasSizeY();
            case Z:
                return getImageCanvasSizeZ();
            case T:
                return getImageCanvasSizeT();
            case C:
                return getImageCanvasSizeC();
        }

        return 0;
    }

    /**
     * Get image size X in canvas pixel coordinate
     * 
     * @deprecated doesn't take rotation transformation in account.<br>
     *             Use IcyCanvasXD.getImageCanvasSize(..) instead
     */
    @Deprecated
    public int getImageCanvasSizeX()
    {
        return imageToCanvasDeltaX(getImageSizeX());
    }

    /**
     * Get image size Y in canvas pixel coordinate
     * 
     * @deprecated doesn't take rotation transformation in account.<br>
     *             Use IcyCanvasXD.getImageCanvasSize(..) instead
     */
    @Deprecated
    public int getImageCanvasSizeY()
    {
        return imageToCanvasDeltaY(getImageSizeY());
    }

    /**
     * Get image size Z in canvas pixel coordinate
     * 
     * @deprecated doesn't take rotation transformation in account.<br>
     *             Use IcyCanvasXD.getImageCanvasSize(..) instead
     */
    @Deprecated
    public int getImageCanvasSizeZ()
    {
        return imageToCanvasDeltaZ(getImageSizeZ());
    }

    /**
     * Get image size T in canvas pixel coordinate
     * 
     * @deprecated doesn't take rotation transformation in account.<br>
     *             Use IcyCanvasXD.getImageCanvasSize(..) instead
     */
    @Deprecated
    public int getImageCanvasSizeT()
    {
        return imageToCanvasDeltaT(getImageSizeT());
    }

    /**
     * Get image size C in canvas pixel coordinate
     * 
     * @deprecated doesn't take rotation transformation in account.<br>
     *             Use IcyCanvasXD.getImageCanvasSize(..) instead
     */
    @Deprecated
    public int getImageCanvasSizeC()
    {
        return imageToCanvasDeltaC(getImageSizeC());
    }

    /**
     * Set position for specified dimension
     */
    public void setPosition(DimensionId dim, int value)
    {
        switch (dim)
        {
            case X:
                setPositionX(value);
                break;
            case Y:
                setPositionY(value);
                break;
            case Z:
                setPositionZ(value);
                break;
            case T:
                setPositionT(value);
                break;
            case C:
                setPositionC(value);
                break;
        }
    }

    /**
     * Set Z position
     * 
     * @deprecated uses setPositionZ(int) instead
     */
    @Deprecated
    public void setZ(int z)
    {
        setPositionZ(z);
    }

    /**
     * Set T position
     * 
     * @deprecated uses setPositionT(int) instead
     */
    @Deprecated
    public void setT(int t)
    {
        setPositionT(t);
    }

    /**
     * Set C position
     * 
     * @deprecated uses setPositionC(int) instead
     */
    @Deprecated
    public void setC(int c)
    {
        setPositionC(c);
    }

    /**
     * Set X position
     */
    public void setPositionX(int x)
    {
        final int adjX = Math.max(-1, Math.min(x, getMaxPositionX()));

        if (getPositionX() != adjX)
            setPositionXInternal(adjX);
    }

    /**
     * Set Y position
     */
    public void setPositionY(int y)
    {
        final int adjY = Math.max(-1, Math.min(y, getMaxPositionY()));

        if (getPositionY() != adjY)
            setPositionYInternal(adjY);
    }

    /**
     * Set Z position
     */
    public void setPositionZ(int z)
    {
        final int adjZ = Math.max(-1, Math.min(z, getMaxPositionZ()));

        if (getPositionZ() != adjZ)
            setPositionZInternal(adjZ);
    }

    /**
     * Set T position
     */
    public void setPositionT(int t)
    {
        final int adjT = Math.max(-1, Math.min(t, getMaxPositionT()));

        if (getPositionT() != adjT)
            setPositionTInternal(adjT);
    }

    /**
     * Set C position
     */
    public void setPositionC(int c)
    {
        final int adjC = Math.max(-1, Math.min(c, getMaxPositionC()));

        if (getPositionC() != adjC)
            setPositionCInternal(adjC);
    }

    /**
     * Set X position internal
     */
    protected void setPositionXInternal(int x)
    {
        posX = x;
        // common process on position change
        positionChanged(DimensionId.X);
    }

    /**
     * Set Y position internal
     */
    protected void setPositionYInternal(int y)
    {
        posY = y;
        // common process on position change
        positionChanged(DimensionId.Y);
    }

    /**
     * Set Z position internal
     */
    protected void setPositionZInternal(int z)
    {
        posZ = z;
        // common process on position change
        positionChanged(DimensionId.Z);
    }

    /**
     * Set T position internal
     */
    protected void setPositionTInternal(int t)
    {
        posT = t;
        // common process on position change
        positionChanged(DimensionId.T);
    }

    /**
     * Set C position internal
     */
    protected void setPositionCInternal(int c)
    {
        posC = c;
        // common process on position change
        positionChanged(DimensionId.C);
    }

    /**
     * Set mouse position (in canvas coordinate space).<br>
     * The method returns <code>true</code> if the mouse position actually changed.
     */
    public boolean setMousePos(int x, int y)
    {
        if ((mousePos.x != x) || (mousePos.y != y))
        {
            mousePos.x = x;
            mousePos.y = y;

            // mouse image position probably changed so this method should be overridden
            // to implement the correct calculation for the mouse image position change

            return true;
        }

        return false;
    }

    /**
     * Set mouse position (in canvas coordinate space)
     */
    public void setMousePos(Point point)
    {
        setMousePos(point.x, point.y);
    }

    /**
     * Set mouse image position for specified dimension (required for synchronization)
     */
    public void setMouseImagePos(DimensionId dim, double value)
    {
        switch (dim)
        {
            case X:
                setMouseImagePosX(value);
                break;
            case Y:
                setMouseImagePosY(value);
                break;
            case Z:
                setMouseImagePosZ(value);
                break;
            case T:
                setMouseImagePosT(value);
                break;
            case C:
                setMouseImagePosC(value);
                break;
        }
    }

    /**
     * Set mouse X image position
     */
    public void setMouseImagePosX(double value)
    {
        if (getMouseImagePosX() != value)
            // internal set
            setMouseImagePosXInternal(value);
    }

    /**
     * Set mouse Y image position
     */
    public void setMouseImagePosY(double value)
    {
        if (getMouseImagePosY() != value)
            // internal set
            setMouseImagePosYInternal(value);
    }

    /**
     * Set mouse Z image position
     */
    public void setMouseImagePosZ(double value)
    {
        if (getMouseImagePosZ() != value)
            // internal set
            setMouseImagePosZInternal(value);
    }

    /**
     * Set mouse T image position
     */
    public void setMouseImagePosT(double value)
    {
        if (getMouseImagePosT() != value)
            // internal set
            setMouseImagePosTInternal(value);
    }

    /**
     * Set mouse C image position
     */
    public void setMouseImagePosC(double value)
    {
        if (getMouseImagePosC() != value)
            // internal set
            setMouseImagePosCInternal(value);
    }

    /**
     * Set offset X internal
     */
    protected void setMouseImagePosXInternal(double value)
    {
        // notify change
        mouseImagePositionChanged(DimensionId.X);
    }

    /**
     * Set offset Y internal
     */
    protected void setMouseImagePosYInternal(double value)
    {
        // notify change
        mouseImagePositionChanged(DimensionId.Y);
    }

    /**
     * Set offset Z internal
     */
    protected void setMouseImagePosZInternal(double value)
    {
        // notify change
        mouseImagePositionChanged(DimensionId.Z);
    }

    /**
     * Set offset T internal
     */
    protected void setMouseImagePosTInternal(double value)
    {
        // notify change
        mouseImagePositionChanged(DimensionId.T);
    }

    /**
     * Set offset C internal
     */
    protected void setMouseImagePosCInternal(double value)
    {
        // notify change
        mouseImagePositionChanged(DimensionId.C);
    }

    /**
     * Set offset for specified dimension
     */
    public void setOffset(DimensionId dim, int value)
    {
        switch (dim)
        {
            case X:
                setOffsetX(value);
                break;
            case Y:
                setOffsetY(value);
                break;
            case Z:
                setOffsetZ(value);
                break;
            case T:
                setOffsetT(value);
                break;
            case C:
                setOffsetC(value);
                break;
        }
    }

    /**
     * Set offset X
     */
    public void setOffsetX(int value)
    {
        if (getOffsetX() != value)
            // internal set
            setOffsetXInternal(value);
    }

    /**
     * Set offset Y
     */
    public void setOffsetY(int value)
    {
        if (getOffsetY() != value)
            // internal set
            setOffsetYInternal(value);
    }

    /**
     * Set offset Z
     */
    public void setOffsetZ(int value)
    {
        if (getOffsetZ() != value)
            // internal set
            setOffsetZInternal(value);
    }

    /**
     * Set offset T
     */
    public void setOffsetT(int value)
    {
        if (getOffsetT() != value)
            // internal set
            setOffsetTInternal(value);
    }

    /**
     * Set offset C
     */
    public void setOffsetC(int value)
    {
        if (getOffsetC() != value)
            // internal set
            setOffsetCInternal(value);
    }

    /**
     * Set offset X internal
     */
    protected void setOffsetXInternal(int value)
    {
        // notify change
        offsetChanged(DimensionId.X);
    }

    /**
     * Set offset Y internal
     */
    protected void setOffsetYInternal(int value)
    {
        // notify change
        offsetChanged(DimensionId.Y);
    }

    /**
     * Set offset Z internal
     */
    protected void setOffsetZInternal(int value)
    {
        // notify change
        offsetChanged(DimensionId.Z);
    }

    /**
     * Set offset T internal
     */
    protected void setOffsetTInternal(int value)
    {
        // notify change
        offsetChanged(DimensionId.T);
    }

    /**
     * Set offset C internal
     */
    protected void setOffsetCInternal(int value)
    {
        // notify change
        offsetChanged(DimensionId.C);
    }

    /**
     * Set scale factor for specified dimension
     */
    public void setScale(DimensionId dim, double value)
    {
        switch (dim)
        {
            case X:
                setScaleX(value);
                break;
            case Y:
                setScaleY(value);
                break;
            case Z:
                setScaleZ(value);
                break;
            case T:
                setScaleT(value);
                break;
            case C:
                setScaleC(value);
                break;
        }
    }

    /**
     * Set scale factor X
     */
    public void setScaleX(double value)
    {
        if (getScaleX() != value)
            // internal set
            setScaleXInternal(value);
    }

    /**
     * Set scale factor Y
     */
    public void setScaleY(double value)
    {
        if (getScaleY() != value)
            // internal set
            setScaleYInternal(value);
    }

    /**
     * Set scale factor Z
     */
    public void setScaleZ(double value)
    {
        if (getScaleZ() != value)
            // internal set
            setScaleZInternal(value);
    }

    /**
     * Set scale factor T
     */
    public void setScaleT(double value)
    {
        if (getScaleT() != value)
            // internal set
            setScaleTInternal(value);
    }

    /**
     * Set scale factor C
     */
    public void setScaleC(double value)
    {
        if (getScaleC() != value)
            // internal set
            setScaleCInternal(value);
    }

    /**
     * Set scale factor X internal
     */
    protected void setScaleXInternal(double value)
    {
        // notify change
        scaleChanged(DimensionId.X);
    }

    /**
     * Set scale factor Y internal
     */
    protected void setScaleYInternal(double value)
    {
        // notify change
        scaleChanged(DimensionId.Y);
    }

    /**
     * Set scale factor Z internal
     */
    protected void setScaleZInternal(double value)
    {
        // notify change
        scaleChanged(DimensionId.Z);
    }

    /**
     * Set scale factor T internal
     */
    protected void setScaleTInternal(double value)
    {
        // notify change
        scaleChanged(DimensionId.T);
    }

    /**
     * Set scale factor C internal
     */
    protected void setScaleCInternal(double value)
    {
        // notify change
        scaleChanged(DimensionId.C);
    }

    /**
     * Set rotation angle (radian) for specified dimension
     */
    public void setRotation(DimensionId dim, double value)
    {
        switch (dim)
        {
            case X:
                setRotationX(value);
                break;
            case Y:
                setRotationY(value);
                break;
            case Z:
                setRotationZ(value);
                break;
            case T:
                setRotationT(value);
                break;
            case C:
                setRotationC(value);
                break;
        }
    }

    /**
     * Set X rotation angle (radian)
     */
    public void setRotationX(double value)
    {
        if (getRotationX() != value)
            // internal set
            setRotationXInternal(value);
    }

    /**
     * Set Y rotation angle (radian)
     */
    public void setRotationY(double value)
    {
        if (getRotationY() != value)
            // internal set
            setRotationYInternal(value);
    }

    /**
     * Set Z rotation angle (radian)
     */
    public void setRotationZ(double value)
    {
        if (getRotationZ() != value)
            // internal set
            setRotationZInternal(value);
    }

    /**
     * Set T rotation angle (radian)
     */
    public void setRotationT(double value)
    {
        if (getRotationT() != value)
            // internal set
            setRotationTInternal(value);
    }

    /**
     * Set C rotation angle (radian)
     */
    public void setRotationC(double value)
    {
        if (getRotationC() != value)
            // internal set
            setRotationCInternal(value);
    }

    /**
     * Set X rotation angle internal
     */
    protected void setRotationXInternal(double value)
    {
        // notify change
        rotationChanged(DimensionId.X);
    }

    /**
     * Set Y rotation angle internal
     */
    protected void setRotationYInternal(double value)
    {
        // notify change
        rotationChanged(DimensionId.Y);
    }

    /**
     * Set Z rotation angle internal
     */
    protected void setRotationZInternal(double value)
    {
        // notify change
        rotationChanged(DimensionId.Z);
    }

    /**
     * Set T rotation angle internal
     */
    protected void setRotationTInternal(double value)
    {
        // notify change
        rotationChanged(DimensionId.T);
    }

    /**
     * Set C rotation angle internal
     */
    protected void setRotationCInternal(double value)
    {
        // notify change
        rotationChanged(DimensionId.C);
    }

    /**
     * Called when mouse image position changed
     */
    public void mouseImagePositionChanged(DimensionId dim)
    {
        // handle with updater
        updater.changed(new IcyCanvasEvent(this, IcyCanvasEventType.MOUSE_IMAGE_POSITION_CHANGED, dim));
    }

    /**
     * Called when canvas offset changed
     */
    public void offsetChanged(DimensionId dim)
    {
        // handle with updater
        updater.changed(new IcyCanvasEvent(this, IcyCanvasEventType.OFFSET_CHANGED, dim));
    }

    /**
     * Called when scale factor changed
     */
    public void scaleChanged(DimensionId dim)
    {
        // handle with updater
        updater.changed(new IcyCanvasEvent(this, IcyCanvasEventType.SCALE_CHANGED, dim));
    }

    /**
     * Called when rotation angle changed
     */
    public void rotationChanged(DimensionId dim)
    {
        // handle with updater
        updater.changed(new IcyCanvasEvent(this, IcyCanvasEventType.ROTATION_CHANGED, dim));
    }

    /**
     * Convert specified canvas delta X to image delta X.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageDeltaX(int value)
    {
        return value / getScaleX();
    }

    /**
     * Convert specified canvas delta Y to image delta Y.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageDeltaY(int value)
    {
        return value / getScaleY();
    }

    /**
     * Convert specified canvas delta Z to image delta Z.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageDeltaZ(int value)
    {
        return value / getScaleZ();
    }

    /**
     * Convert specified canvas delta T to image delta T.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageDeltaT(int value)
    {
        return value / getScaleT();
    }

    /**
     * Convert specified canvas delta C to image delta C.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageDeltaC(int value)
    {
        return value / getScaleC();
    }

    /**
     * Convert specified canvas delta X to log image delta X.<br>
     * The conversion is still affected by zoom ratio but with specified logarithm form.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageLogDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageLogDeltaX(int value, double logFactor)
    {
        final double scaleFactor = getScaleX();
        // keep the zoom ratio but in a log perspective
        return value / (scaleFactor / Math.pow(10, Math.log10(scaleFactor) / logFactor));
    }

    /**
     * Convert specified canvas delta X to log image delta X.<br>
     * The conversion is still affected by zoom ratio but with logarithm form.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageLogDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageLogDeltaX(int value)
    {
        return canvasToImageLogDeltaX(value, 5d);
    }

    /**
     * Convert specified canvas delta Y to log image delta Y.<br>
     * The conversion is still affected by zoom ratio but with specified logarithm form.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageLogDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageLogDeltaY(int value, double logFactor)
    {
        final double scaleFactor = getScaleY();
        // keep the zoom ratio but in a log perspective
        return value / (scaleFactor / Math.pow(10, Math.log10(scaleFactor) / logFactor));
    }

    /**
     * Convert specified canvas delta Y to log image delta Y.<br>
     * The conversion is still affected by zoom ratio but with logarithm form.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageLogDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageLogDeltaY(int value)
    {
        return canvasToImageLogDeltaY(value, 5d);
    }

    /**
     * Convert specified canvas delta Z to log image delta Z.<br>
     * The conversion is still affected by zoom ratio but with specified logarithm form.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageLogDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageLogDeltaZ(int value, double logFactor)
    {
        final double scaleFactor = getScaleZ();
        // keep the zoom ratio but in a log perspective
        return value / (scaleFactor / Math.pow(10, Math.log10(scaleFactor) / logFactor));
    }

    /**
     * Convert specified canvas delta Z to log image delta Z.<br>
     * The conversion is still affected by zoom ratio but with logarithm form.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.canvasToImageLogDelta(...) method instead for rotation transformed delta.
     */
    public double canvasToImageLogDeltaZ(int value)
    {
        return canvasToImageLogDeltaZ(value, 5d);
    }

    /**
     * Convert specified canvas X coordinate to image X coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.canvasToImage(...) instead
     */
    @Deprecated
    public double canvasToImageX(int value)
    {
        return canvasToImageDeltaX(value - getOffsetX());
    }

    /**
     * Convert specified canvas Y coordinate to image Y coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.canvasToImage(...) instead
     */
    @Deprecated
    public double canvasToImageY(int value)
    {
        return canvasToImageDeltaY(value - getOffsetY());
    }

    /**
     * Convert specified canvas Z coordinate to image Z coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.canvasToImage(...) instead
     */
    @Deprecated
    public double canvasToImageZ(int value)
    {
        return canvasToImageDeltaZ(value - getOffsetZ());
    }

    /**
     * Convert specified canvas T coordinate to image T coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.canvasToImage(...) instead
     */
    @Deprecated
    public double canvasToImageT(int value)
    {
        return canvasToImageDeltaT(value - getOffsetT());
    }

    /**
     * Convert specified canvas C coordinate to image C coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.canvasToImage(...) instead
     */
    @Deprecated
    public double canvasToImageC(int value)
    {
        return canvasToImageDeltaC(value - getOffsetC());
    }

    /**
     * Convert specified image delta X to canvas delta X.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.imageToCanvasDelta(...) method instead for rotation transformed delta.
     */
    public int imageToCanvasDeltaX(double value)
    {
        return (int) (value * getScaleX());
    }

    /**
     * Convert specified image delta Y to canvas delta Y.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.imageToCanvasDelta(...) method instead for rotation transformed delta.
     */
    public int imageToCanvasDeltaY(double value)
    {
        return (int) (value * getScaleY());
    }

    /**
     * Convert specified image delta Z to canvas delta Z.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.imageToCanvasDelta(...) method instead for rotation transformed delta.
     */
    public int imageToCanvasDeltaZ(double value)
    {
        return (int) (value * getScaleZ());
    }

    /**
     * Convert specified image delta T to canvas delta T.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.imageToCanvasDelta(...) method instead for rotation transformed delta.
     */
    public int imageToCanvasDeltaT(double value)
    {
        return (int) (value * getScaleT());
    }

    /**
     * Convert specified image delta C to canvas delta C.<br>
     * WARNING: Does not take in account the rotation transformation.<br>
     * Use the IcyCanvasXD.imageToCanvasDelta(...) method instead for rotation transformed delta.
     */
    public int imageToCanvasDeltaC(double value)
    {
        return (int) (value * getScaleC());
    }

    /**
     * Convert specified image X coordinate to canvas X coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.imageToCanvas(...) instead
     */
    @Deprecated
    public int imageToCanvasX(double value)
    {
        return imageToCanvasDeltaX(value) + getOffsetX();
    }

    /**
     * Convert specified image Y coordinate to canvas Y coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.imageToCanvas(...) instead
     */
    @Deprecated
    public int imageToCanvasY(double value)
    {
        return imageToCanvasDeltaY(value) + getOffsetY();
    }

    /**
     * Convert specified image Z coordinate to canvas Z coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.imageToCanvas(...) instead
     */
    @Deprecated
    public int imageToCanvasZ(double value)
    {
        return imageToCanvasDeltaZ(value) + getOffsetZ();
    }

    /**
     * Convert specified image T coordinate to canvas T coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.imageToCanvas(...) instead
     */
    @Deprecated
    public int imageToCanvasT(double value)
    {
        return imageToCanvasDeltaT(value) + getOffsetT();
    }

    /**
     * Convert specified image C coordinate to canvas C coordinate
     * 
     * @deprecated Cannot give correct result if rotation is applied so use
     *             IcyCanvasXD.imageToCanvas(...) instead
     */
    @Deprecated
    public int imageToCanvasC(double value)
    {
        return imageToCanvasDeltaC(value) + getOffsetC();
    }

    /**
     * Helper to forward mouse press event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mousePressed(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mousePressed(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse press event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mousePressed(MouseEvent event)
    {
        mousePressed(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse release event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseReleased(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseReleased(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse release event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseReleased(MouseEvent event)
    {
        mouseReleased(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse click event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseClick(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseClick(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse click event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseClick(MouseEvent event)
    {
        mouseClick(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse move event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseMove(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseMove(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse mouse event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseMove(MouseEvent event)
    {
        mouseMove(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse drag event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseDrag(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseDrag(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse drag event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseDrag(MouseEvent event)
    {
        mouseDrag(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse enter event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseEntered(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseEntered(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse entered event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseEntered(MouseEvent event)
    {
        mouseEntered(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse exit event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseExited(MouseEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseExited(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse exited event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseExited(MouseEvent event)
    {
        mouseExited(event, getMouseImagePos5D());
    }

    /**
     * Helper to forward mouse wheel event to the overlays.
     * 
     * @param event
     *        original mouse event
     * @param pt
     *        mouse image position
     */
    public void mouseWheelMoved(MouseWheelEvent event, Point5D.Double pt)
    {
        final boolean globalVisible = isLayersVisible();

        // send mouse event to overlays after so mouse canvas position is ok
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveMouseEventOnHidden())
                layer.getOverlay().mouseWheelMoved(event, pt, this);
        }
    }

    /**
     * Helper to forward mouse wheel event to the overlays.
     * 
     * @param event
     *        original mouse event
     */
    public void mouseWheelMoved(MouseWheelEvent event)
    {
        mouseWheelMoved(event, getMouseImagePos5D());
    }

    @Override
    public void keyPressed(KeyEvent e)
    {
        final boolean globalVisible = isLayersVisible();
        final Point5D.Double pt = getMouseImagePos5D();

        // forward event to overlays
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveKeyEventOnHidden())
                layer.getOverlay().keyPressed(e, pt, this);
        }

        if (!e.isConsumed())
        {
            switch (e.getKeyCode())
            {
                case KeyEvent.VK_0:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (CanvasActions.globalDisableSyncAction.isEnabled())
                        {
                            CanvasActions.globalDisableSyncAction.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isNoModifier(e))
                    {
                        if (CanvasActions.disableSyncAction.isEnabled())
                        {
                            CanvasActions.disableSyncAction.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_1:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (CanvasActions.globalSyncGroup1Action.isEnabled())
                        {
                            CanvasActions.globalSyncGroup1Action.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isNoModifier(e))
                    {
                        if (CanvasActions.syncGroup1Action.isEnabled())
                        {
                            CanvasActions.syncGroup1Action.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_2:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (CanvasActions.globalSyncGroup2Action.isEnabled())
                        {
                            CanvasActions.globalSyncGroup2Action.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isNoModifier(e))
                    {
                        if (CanvasActions.syncGroup2Action.isEnabled())
                        {
                            CanvasActions.syncGroup2Action.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_3:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (CanvasActions.globalSyncGroup3Action.isEnabled())
                        {
                            CanvasActions.globalSyncGroup3Action.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isNoModifier(e))
                    {
                        if (CanvasActions.syncGroup3Action.isEnabled())
                        {
                            CanvasActions.syncGroup3Action.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_4:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (CanvasActions.globalSyncGroup4Action.isEnabled())
                        {
                            CanvasActions.globalSyncGroup4Action.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isNoModifier(e))
                    {
                        if (CanvasActions.syncGroup4Action.isEnabled())
                        {
                            CanvasActions.syncGroup4Action.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_G:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (WindowActions.gridTileAction.isEnabled())
                        {
                            WindowActions.gridTileAction.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_H:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (WindowActions.horizontalTileAction.isEnabled())
                        {
                            WindowActions.horizontalTileAction.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_A:
                    if (EventUtil.isMenuControlDown(e, true))
                    {
                        if (RoiActions.selectAllAction.isEnabled())
                        {
                            RoiActions.selectAllAction.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_V:
                    if (EventUtil.isShiftDown(e, true))
                    {
                        if (WindowActions.verticalTileAction.isEnabled())
                        {
                            WindowActions.verticalTileAction.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isMenuControlDown(e, true))
                    {
                        if (GeneralActions.pasteImageAction.isEnabled())
                        {
                            GeneralActions.pasteImageAction.execute();
                            e.consume();
                        }
                        else if (RoiActions.pasteAction.isEnabled())
                        {
                            RoiActions.pasteAction.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isAltDown(e, true))
                    {
                        if (RoiActions.pasteLinkAction.isEnabled())
                        {
                            RoiActions.pasteLinkAction.execute();
                            e.consume();
                        }
                    }
                    break;

                case KeyEvent.VK_C:
                    if (EventUtil.isMenuControlDown(e, true))
                    {
                        // do this one first else copyImage hide it
                        if (RoiActions.copyAction.isEnabled())
                        {
                            // copy them to icy clipboard
                            RoiActions.copyAction.execute();
                            e.consume();
                        }
                        else if (GeneralActions.copyImageAction.isEnabled())
                        {
                            // copy image to system clipboard
                            GeneralActions.copyImageAction.execute();
                            e.consume();
                        }
                    }
                    else if (EventUtil.isAltDown(e, true))
                    {
                        if (RoiActions.copyLinkAction.isEnabled())
                        {
                            // copy link of selected ROI to clipboard
                            RoiActions.copyLinkAction.execute();
                            e.consume();
                        }
                    }
                    break;
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e)
    {
        final boolean globalVisible = isLayersVisible();
        final Point5D.Double pt = getMouseImagePos5D();

        // forward event to overlays
        for (Layer layer : getLayers(true))
        {
            if ((globalVisible && layer.isVisible()) || layer.getReceiveKeyEventOnHidden())
                layer.getOverlay().keyReleased(e, pt, this);
        }
    }

    @Override
    public void keyTyped(KeyEvent e)
    {

    }

    /**
     * Gets the image at position (t, z, c).
     */
    public IcyBufferedImage getImage(int t, int z, int c)
    {
        if ((t == -1) || (z == -1))
            return null;

        final Sequence sequence = getSequence();

        // have to test this as sequence reference can be release in viewer
        if (sequence != null)
            return sequence.getImage(t, z, c);

        return null;
    }

    /**
     * @deprecated Use {@link #getImage(int, int, int)} with C = -1 instead.
     */
    @Deprecated
    public IcyBufferedImage getImage(int t, int z)
    {
        return getImage(t, z, -1);
    }

    /**
     * Get the current image.
     */
    public IcyBufferedImage getCurrentImage()
    {
        return getImage(getPositionT(), getPositionZ(), getPositionC());
    }

    /**
     * @deprecated use {@link #getRenderedImage(int, int, int, boolean)} instead
     */
    @Deprecated
    public final BufferedImage getRenderedImage(int t, int z, int c, int imageType, boolean canvasView)
    {
        return getRenderedImage(t, z, c, canvasView);
    }

    /**
     * @deprecated use {@link #getRenderedSequence(boolean)} instead
     */
    @Deprecated
    public final Sequence getRenderedSequence(int imageType, boolean canvasView)
    {
        return getRenderedSequence(canvasView);
    }

    /**
     * Returns a RGB or ARGB (depending support) BufferedImage representing the canvas view for
     * image at position (t, z, c).<br>
     * Free feel to the canvas to handle or not a specific dimension.
     * 
     * @param t
     *        T position of wanted image (-1 for complete sequence)
     * @param z
     *        Z position of wanted image (-1 for complete stack)
     * @param c
     *        C position of wanted image (-1 for all channels)
     * @param canvasView
     *        render with canvas view if true else use default sequence dimension
     */
    public abstract BufferedImage getRenderedImage(int t, int z, int c, boolean canvasView);

    /**
     * @deprecated Use {@link #getRenderedImage(int, int, int, boolean)} instead.
     */
    @Deprecated
    public BufferedImage getRenderedImage(int t, int z, int c)
    {
        return getRenderedImage(t, z, c, true);
    }

    /**
     * Return a sequence which contains rendered images.<br>
     * Default implementation, override it if needed in your canvas.
     * 
     * @param canvasView
     *        render with canvas view if true else use default sequence dimension
     * @param progressListener
     *        progress listener which receive notifications about progression
     */
    public Sequence getRenderedSequence(boolean canvasView, ProgressListener progressListener)
    {
        final Sequence seqIn = getSequence();
        // create output sequence
        final Sequence result = new Sequence();

        if (seqIn != null)
        {
            // derive original metadata
            result.setMetaData(OMEUtil.createOMEMetadata(seqIn.getMetadata()));

            int t = getPositionT();
            int z = getPositionZ();
            int c = getPositionC();
            final int sizeT = getImageSizeT();
            final int sizeZ = getImageSizeZ();
            final int sizeC = getImageSizeC();

            int pos = 0;
            int len = 1;
            if (t != -1)
                len *= sizeT;
            if (z != -1)
                len *= sizeZ;
            if (c != -1)
                len *= sizeC;

            result.beginUpdate();
            // This cause position changed event to not be sent during rendering.
            // Painters have to take care of that, they should check the canvas position
            // in the paint() method
            beginUpdate();
            try
            {
                if (t != -1)
                {
                    for (t = 0; t < sizeT; t++)
                    {
                        if (z != -1)
                        {
                            for (z = 0; z < sizeZ; z++)
                            {
                                if (c != -1)
                                {
                                    final List<BufferedImage> images = new ArrayList<BufferedImage>();

                                    for (c = 0; c < sizeC; c++)
                                    {
                                        images.add(getRenderedImage(t, z, c, canvasView));
                                        pos++;
                                        if (progressListener != null)
                                            progressListener.notifyProgress(pos, len);
                                    }

                                    result.setImage(t, z, IcyBufferedImage.createFrom(images));
                                }
                                else
                                {
                                    result.setImage(t, z, getRenderedImage(t, z, -1, canvasView));
                                    pos++;
                                    if (progressListener != null)
                                        progressListener.notifyProgress(pos, len);
                                }
                            }
                        }
                        else
                        {
                            result.setImage(t, 0, getRenderedImage(t, -1, -1, canvasView));
                            pos++;
                            if (progressListener != null)
                                progressListener.notifyProgress(pos, len);
                        }
                    }
                }
                else
                {
                    if (z != -1)
                    {
                        for (z = 0; z < sizeZ; z++)
                        {
                            if (c != -1)
                            {
                                final ArrayList<BufferedImage> images = new ArrayList<BufferedImage>();

                                for (c = 0; c < sizeC; c++)
                                {
                                    images.add(getRenderedImage(-1, z, c, canvasView));
                                    pos++;
                                    if (progressListener != null)
                                        progressListener.notifyProgress(pos, len);
                                }

                                result.setImage(0, z, IcyBufferedImage.createFrom(images));
                            }
                            else
                            {
                                result.setImage(0, z, getRenderedImage(-1, z, -1, canvasView));
                                pos++;
                                if (progressListener != null)
                                    progressListener.notifyProgress(pos, len);
                            }
                        }
                    }
                    else
                    {
                        if (c != -1)
                        {
                            final ArrayList<BufferedImage> images = new ArrayList<BufferedImage>();

                            for (c = 0; c < sizeC; c++)
                            {
                                images.add(getRenderedImage(-1, -1, c, canvasView));
                                pos++;
                                if (progressListener != null)
                                    progressListener.notifyProgress(pos, len);
                            }

                            result.setImage(0, 0, IcyBufferedImage.createFrom(images));
                        }
                        else
                        {
                            result.setImage(0, 0, getRenderedImage(-1, -1, -1, canvasView));
                            pos++;
                            if (progressListener != null)
                                progressListener.notifyProgress(pos, len);
                        }
                    }
                }
            }
            finally
            {
                endUpdate();
                result.endUpdate();
            }
        }

        return result;
    }

    /**
     * @deprecated Use {@link #getRenderedSequence(boolean, ProgressListener)} instead.
     */
    @Deprecated
    public Sequence getRenderedSequence(boolean canvasView)
    {
        return getRenderedSequence(canvasView, null);
    }

    /**
     * @deprecated Use {@link #getRenderedSequence(boolean, ProgressListener)} instead.
     */
    @Deprecated
    public Sequence getRenderedSequence()
    {
        return getRenderedSequence(true, null);
    }

    /**
     * Return the number of "selected" samples
     */
    public int getNumSelectedSamples()
    {
        final Sequence sequence = getSequence();

        // have to test this as we release sequence reference on closed
        if (sequence == null)
            return 0;

        final int base_len = getImageSizeX() * getImageSizeY() * getImageSizeC();

        if (getPositionT() == -1)
        {
            if (getPositionZ() == -1)
                return base_len * getImageSizeZ() * getImageSizeT();

            return base_len * getImageSizeT();
        }

        if (getPositionZ() == -1)
            return base_len * getImageSizeZ();

        return base_len;
    }

    /**
     * Returns the frame rate (given in frame per second) for play command (T navigation panel).
     */
    public int getFrameRate()
    {
        return tNav.getFrameRate();
    }

    /**
     * Sets the frame rate (given in frame per second) for play command (T navigation panel).
     */
    public void setFrameRate(int fps)
    {
        tNav.setFrameRate(fps);
    }

    /**
     * update Z slider state
     */
    protected void updateZNav()
    {
        final int maxZ = getMaxPositionZ();
        final int z = getPositionZ();

        zNav.setMaximum(maxZ);
        if (z != -1)
        {
            zNav.setValue(z);
            zNav.setVisible(maxZ > 0);
        }
        else
            zNav.setVisible(false);
    }

    /**
     * update T slider state
     */
    protected void updateTNav()
    {
        final int maxT = getMaxPositionT();
        final int t = getPositionT();

        tNav.setMaximum(maxT);
        if (t != -1)
        {
            tNav.setValue(t);
            tNav.setVisible(maxT > 0);
        }
        else
            tNav.setVisible(false);
    }

    /**
     * @deprecated Use {@link #getLayer(Overlay)} instead.
     */
    @Deprecated
    public Layer getLayer(Painter painter)
    {
        for (Layer layer : getLayers(false))
            if (layer.getPainter() == painter)
                return layer;

        return null;
    }

    /**
     * Find the layer corresponding to the specified Overlay
     */
    public Layer getLayer(Overlay overlay)
    {
        return layers.get(overlay);
    }

    /**
     * Find the layer corresponding to the specified ROI (use the ROI overlay internally).
     */
    public Layer getLayer(ROI roi)
    {
        return getLayer(roi.getOverlay());
    }

    /**
     * @deprecated Use {@link #hasLayer(Overlay)} instead.
     */
    @Deprecated
    public boolean hasLayer(Painter painter)
    {
        return getLayer(painter) != null;
    }

    /**
     * Returns true if the canvas contains a layer for the specified {@link Overlay}.
     */
    public boolean hasLayer(Overlay overlay)
    {
        synchronized (layers)
        {
            return layers.containsKey(overlay);
        }
    }

    public boolean hasLayer(Layer layer)
    {
        final Overlay overlay = layer.getOverlay();

        // faster to test from overlay
        if (overlay != null)
            return hasLayer(overlay);

        synchronized (layers)
        {
            return layers.containsValue(layer);
        }
    }

    /**
     * @deprecated Use {@link #addLayer(Overlay)} instead.
     */
    @Deprecated
    public void addLayer(Painter painter)
    {
        if (!hasLayer(painter))
            addLayer(new Layer(painter));
    }

    public Layer addLayer(Overlay overlay)
    {
        if (!hasLayer(overlay))
            return addLayer(new Layer(overlay));

        return null;
    }

    protected Layer addLayer(Layer layer)
    {
        if (layer != null)
        {
            // listen layer
            layer.addListener(this);

            // add to list
            synchronized (layers)
            {
                layers.put(layer.getOverlay(), layer);
                if (Layer.DEFAULT_NAME.equals(layer))
                    layer.setName("layer " + layers.size());
            }

            // added
            layerAdded(layer);
        }

        return layer;
    }

    /**
     * @deprecated Use {@link #removeLayer(Overlay)} instead.
     */
    @Deprecated
    public void removeLayer(Painter painter)
    {
        removeLayer(getLayer(painter));
    }

    /**
     * Remove the layer for the specified {@link Overlay} from the canvas.<br/>
     * Returns <code>true</code> if the method succeed.
     */
    public boolean removeLayer(Overlay overlay)
    {
        final Layer layer;

        // remove from list
        synchronized (layers)
        {
            layer = layers.remove(overlay);
        }

        if (layer != null)
        {
            // stop listening layer
            layer.removeListener(this);
            // notify remove
            layerRemoved(layer);

            return true;
        }

        return false;
    }

    /**
     * Remove the specified layer from the canvas.
     */
    public void removeLayer(Layer layer)
    {
        removeLayer(layer.getOverlay());
    }

    /**
     * Returns <code>true</code> if the specified overlay is visible in the canvas.<br>
     */
    public boolean isVisible(Overlay overlay)
    {
        final Layer layer = getLayer(overlay);

        if (layer != null)
            return layer.isVisible();

        return false;
    }

    /**
     * @deprecated Use {@link #addLayerListener(CanvasLayerListener)} instead.
     */
    @Deprecated
    public void addLayersListener(CanvasLayerListener listener)
    {
        addLayerListener(listener);
    }

    /**
     * @deprecated Use {@link #removeLayerListener(CanvasLayerListener)} instead.
     */
    @Deprecated
    public void removeLayersListener(CanvasLayerListener listener)
    {
        removeLayerListener(listener);
    }

    /**
     * Add a layer listener
     * 
     * @param listener
     */
    public void addLayerListener(CanvasLayerListener listener)
    {
        if (listener != null)
            layerListeners.add(listener);
    }

    /**
     * Remove a layer listener
     * 
     * @param listener
     */
    public void removeLayerListener(CanvasLayerListener listener)
    {
        layerListeners.remove(listener);
    }

    protected void fireLayerChangedEvent(CanvasLayerEvent event)
    {
        for (CanvasLayerListener listener : new ArrayList<CanvasLayerListener>(layerListeners))
            listener.canvasLayerChanged(event);
    }

    /**
     * Add a IcyCanvas listener
     * 
     * @param listener
     */
    public void addCanvasListener(IcyCanvasListener listener)
    {
        listeners.add(listener);
    }

    /**
     * Remove a IcyCanvas listener
     * 
     * @param listener
     */
    public void removeCanvasListener(IcyCanvasListener listener)
    {
        listeners.remove(listener);
    }

    protected void fireCanvasChangedEvent(IcyCanvasEvent event)
    {
        for (IcyCanvasListener listener : new ArrayList<IcyCanvasListener>(listeners))
            listener.canvasChanged(event);
    }

    public void beginUpdate()
    {
        updater.beginUpdate();
    }

    public void endUpdate()
    {
        updater.endUpdate();
    }

    public boolean isUpdating()
    {
        return updater.isUpdating();
    }

    /**
     * layer added
     * 
     * @param layer
     */
    protected void layerAdded(Layer layer)
    {
        // handle with updater
        updater.changed(new CanvasLayerEvent(layer, LayersEventType.ADDED));
    }

    /**
     * layer removed
     * 
     * @param layer
     */
    protected void layerRemoved(Layer layer)
    {
        // handle with updater
        updater.changed(new CanvasLayerEvent(layer, LayersEventType.REMOVED));
    }

    /**
     * layer has changed
     */
    @Override
    public void layerChanged(Layer layer, String propertyName)
    {
        // handle with updater
        updater.changed(new CanvasLayerEvent(layer, LayersEventType.CHANGED, propertyName));
    }

    /**
     * canvas changed (packed event).<br>
     * do global changes processing here
     */
    public void changed(IcyCanvasEvent event)
    {
        final IcyCanvasEventType eventType = event.getType();

        // handle synchronized canvas
        if (isSynchronized())
        {
            final List<IcyCanvas> synchCanvasList = getSynchronizedCanvas();

            // this is the synchronizer master so dispatch view changes to others canvas
            if (getSynchMaster(synchCanvasList))
            {
                try
                {
                    // synchronize all events when the view has just been synchronized
                    final boolean synchAll = (eventType == IcyCanvasEventType.SYNC_CHANGED);
                    synchronizeCanvas(synchCanvasList, event, synchAll);
                }
                finally
                {
                    releaseSynchMaster();
                }
            }
        }

        switch (eventType)
        {
            case POSITION_CHANGED:
                final int curZ = getPositionZ();
                final int curT = getPositionT();
                final int curC = getPositionC();

                switch (event.getDim())
                {
                    case Z:
                        // ensure Z slider position
                        if (curZ != -1)
                            zNav.setValue(curZ);
                        break;

                    case T:
                        // ensure T slider position
                        if (curT != -1)
                            tNav.setValue(curT);
                        break;

                    case C:
                        // single channel mode
                        final int maxC = getMaxPositionC();

                        // disabled others channels
                        for (int c = 0; c <= maxC; c++)
                            getLut().getLutChannel(c).setEnabled((curC == -1) || (curC == c));
                        break;

                    case NULL:
                        // ensure Z slider position
                        if (curZ != -1)
                            zNav.setValue(curZ);
                        // ensure T slider position
                        if (curT != -1)
                            tNav.setValue(curT);
                        break;
                }
                // refresh mouse panel informations
                mouseInfPanel.updateInfos(this);
                break;

            case MOUSE_IMAGE_POSITION_CHANGED:
                // refresh mouse panel informations
                mouseInfPanel.updateInfos(this);
                break;
        }

        // notify listeners that canvas have changed
        fireCanvasChangedEvent(event);
    }

    /**
     * layer property has changed (packed event)
     */
    protected void layerChanged(CanvasLayerEvent event)
    {
        final String property = event.getProperty();

        // we need to rebuild sorted layer list
        if ((event.getType() != LayersEventType.CHANGED) || (property == null) || (property == Layer.PROPERTY_PRIORITY))
            orderedLayersOutdated = true;

        // notify listeners that layers have changed
        fireLayerChangedEvent(event);
    }

    /**
     * position has changed<br>
     * 
     * @param dim
     *        define the position which has changed
     */
    protected void positionChanged(DimensionId dim)
    {
        // handle with updater
        updater.changed(new IcyCanvasEvent(this, IcyCanvasEventType.POSITION_CHANGED, dim));
    }

    @Override
    public void lutChanged(LUTEvent event)
    {
        final int curC = getPositionC();

        // single channel mode ?
        if (curC != -1)
        {
            final int channel = event.getComponent();

            // channel is enabled --> change C position
            if ((channel != -1) && getLut().getLutChannel(channel).isEnabled())
                setPositionC(channel);
            else
                // ensure we have 1 channel enable
                getLut().getLutChannel(curC).setEnabled(true);
        }

        lutChanged(event.getComponent());
    }

    /**
     * lut changed
     * 
     * @param component
     */
    protected void lutChanged(int component)
    {

    }

    /**
     * sequence meta data has changed
     */
    protected void sequenceMetaChanged(String metadataName)
    {

    }

    /**
     * sequence type has changed
     */
    protected void sequenceTypeChanged()
    {

    }

    /**
     * sequence component bounds has changed
     * 
     * @param colorModel
     * @param component
     */
    protected void sequenceComponentBoundsChanged(IcyColorModel colorModel, int component)
    {

    }

    /**
     * sequence component bounds has changed
     * 
     * @param colorModel
     * @param component
     */
    protected void sequenceColorMapChanged(IcyColorModel colorModel, int component)
    {

    }

    /**
     * sequence data has changed
     * 
     * @param image
     *        image which has changed (null if global data changed)
     * @param type
     *        event type
     */
    protected void sequenceDataChanged(IcyBufferedImage image, SequenceEventType type)
    {
        ThreadUtil.runSingle(guiUpdater);
    }

    /**
     * @deprecated Use {@link #sequenceOverlayChanged(Overlay, SequenceEventType)} instead.
     */
    @SuppressWarnings("unused")
    @Deprecated
    protected void sequencePainterChanged(Painter painter, SequenceEventType type)
    {
        // no more stuff here
    }

    /**
     * Sequence overlay has changed
     * 
     * @param overlay
     *        overlay which has changed
     * @param type
     *        event type
     */
    protected void sequenceOverlayChanged(Overlay overlay, SequenceEventType type)
    {
        switch (type)
        {
            case ADDED:
                addLayer(overlay);
                break;

            case REMOVED:
                removeLayer(overlay);
                break;

            case CHANGED:
                // nothing to do here
                break;
        }
    }

    /**
     * sequence roi has changed
     * 
     * @param roi
     *        roi which has changed (null if global roi changed)
     * @param type
     *        event type
     */
    protected void sequenceROIChanged(ROI roi, SequenceEventType type)
    {
        // nothing here

    }

    @Override
    public void viewerChanged(ViewerEvent event)
    {
        switch (event.getType())
        {
            case POSITION_CHANGED:
                // ignore this event as we are launching it
                break;

            case LUT_CHANGED:
                // set new lut
                setLut(viewer.getLut(), true);
                break;

            case CANVAS_CHANGED:
                // nothing to do
                break;
        }
    }

    @Override
    public void viewerClosed(Viewer viewer)
    {
        // nothing to do here
    }

    @Override
    public final void sequenceChanged(SequenceEvent event)
    {
        switch (event.getSourceType())
        {
            case SEQUENCE_META:
                sequenceMetaChanged((String) event.getSource());
                break;

            case SEQUENCE_TYPE:
                sequenceTypeChanged();
                break;

            case SEQUENCE_COMPONENTBOUNDS:
                sequenceComponentBoundsChanged((IcyColorModel) event.getSource(), event.getParam());
                break;

            case SEQUENCE_COLORMAP:
                sequenceColorMapChanged((IcyColorModel) event.getSource(), event.getParam());
                break;

            case SEQUENCE_DATA:
                sequenceDataChanged((IcyBufferedImage) event.getSource(), event.getType());
                break;

            case SEQUENCE_OVERLAY:
                final Overlay overlay = (Overlay) event.getSource();

                sequenceOverlayChanged(overlay, event.getType());

                // backward compatibility
                @SuppressWarnings("deprecation")
                final Painter painter;

                if (overlay instanceof OverlayWrapper)
                    painter = ((OverlayWrapper) overlay).getPainter();
                else
                    painter = overlay;

                sequencePainterChanged(painter, event.getType());
                break;

            case SEQUENCE_ROI:
                sequenceROIChanged((ROI) event.getSource(), event.getType());
                break;
        }
    }

    @Override
    public void sequenceClosed(Sequence sequence)
    {
        // nothing to do here
    }

    @Override
    public void onChanged(CollapsibleEvent event)
    {
        if (event instanceof CanvasLayerEvent)
            layerChanged((CanvasLayerEvent) event);

        if (event instanceof IcyCanvasEvent)
            changed((IcyCanvasEvent) event);
    }
}