/*
 * org.openmicroscopy.shoola.agents.imviewer.view.ControlPane
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2014 University of Dundee. All rights reserved.
 *
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *  This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */

package org.openmicroscopy.shoola.agents.imviewer.view;

//Java imports
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

//Third-party libraries
import info.clearthought.layout.TableLayout;
import org.jdesktop.swingx.JXBusyLabel;

//Application-internal dependencies
import org.openmicroscopy.shoola.agents.imviewer.IconManager;
import org.openmicroscopy.shoola.agents.imviewer.actions.ColorModelAction;
import org.openmicroscopy.shoola.agents.imviewer.actions.ColorPickerAction;
import org.openmicroscopy.shoola.agents.imviewer.actions.ViewerAction;
import org.openmicroscopy.shoola.agents.imviewer.actions.ZoomAction;
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.agents.util.ui.ChannelButton;
import org.openmicroscopy.shoola.env.data.model.ProjectionParam;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.slider.OneKnobSlider;
import org.openmicroscopy.shoola.util.ui.slider.TwoKnobsSlider;
import pojos.ChannelData;

/**
 * Presents variable controls.
 *
 * @author Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 *         <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Andrea Falconi &nbsp;&nbsp;&nbsp;&nbsp;
 *         <a href="mailto:a.falconi@dundee.ac.uk">a.falconi@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since OME2.2
 */
class ControlPane
    extends JPanel
    implements ActionListener, ChangeListener, MouseWheelListener,
    PropertyChangeListener
{

    /** Identifies the T sliders.*/
    private static final int T = 0;

    /** Identifies the Z sliders.*/
    private static final int Z = 1;

    /** Identifies the lifetime sliders.*/
    private static final int BIN = 2;
    
	/** The default size of the busy label. */
	private static final Dimension	DIMENSION = new Dimension(16, 16);

    /** The description of a z-sections selection slider. */
    private static final String Z_SLIDER_DESCRIPTION = "Select a z-section.";

    /** The description of a timepoint selection slider. */
    private static final String T_SLIDER_DESCRIPTION = "Select a timepoint.";

    /** The description of a bin selection slider. */
    private static final String LITEIME_SLIDER_DESCRIPTION = "Select a bin.";

    /** The description of a magnification selection slider. */
    private static final String RATIO_SLIDER_DESCRIPTION = "Select the " +
                            "magnification factor of an image composing " +
                            "the grid.";

    /** The description of a magnification selection slider. */
    private static final String ZOOM_SLIDER_DESCRIPTION = "Select the " +
                            "magnification factor of the image.";

    /** The description of a z-sections selection slider. */
    private static final String PROJECTION_SLIDER_DESCRIPTION =
                            "Select the interval of z-sections to project.";

    /** The tipString of the {@link #zSlider}. */
    private static final String Z_SLIDER_TIPSTRING = "Z";

    /** The tipString of the {@link #tSlider}. */
    private static final String T_SLIDER_TIPSTRING = "T";

    /** The maximum height of a magnification slider. */
    private static final int SLIDER_HEIGHT = 100;

    /** Action command id. */
	private static final int FREQUENCY = 0;

	/** Action command id. */
	private static final int TYPE = 1;

    /** Default text describing the compression check box.  */
    private static final String PROJECTION_DESCRIPTION =
            "Select the type of projection.";

    /** The value after which no ticks are displayed. */
    private static final int MAX_NO_TICKS = 10;

    /** Dimension of the box between the channel buttons. */
    private static final Dimension VBOX = new Dimension(1, 10);

    /** Reference to the Control. */
    private ImViewerControl controller;

    /** Reference to the Model. */
    private ImViewerModel model;

    /** Reference to the View. */
    private ImViewerUI view;

    /** Slider to select the lifetime bin. */
    private OneKnobSlider lifetimeSlider;

    /** Slider to select the lifetime bin. */
    private OneKnobSlider lifetimeSliderGrid;
    
    /** Slider to select the z-section. */
    private OneKnobSlider zSlider;

    /** Slider to select the time-point. */
    private OneKnobSlider tSlider;

    /** Slider to select the z-section. */
    private OneKnobSlider zSliderGrid;

    /** Slider to select the time-point. */
    private OneKnobSlider tSliderGrid;

    /** Slider to select the time-point. */
    private OneKnobSlider tSliderProjection;

    /** Slider to select the z-sections interval to project. */
    private TwoKnobsSlider projectionRange;

    /** Slider to set the magnification factor of an image of the grid. */
    private OneKnobSlider gridRatioSlider;

    /** Slider to set the magnification factor of the image. */
    private OneKnobSlider ratioSlider;

    /** Slider to set the magnification factor of the image. */
    private OneKnobSlider projectionRatioSlider;

    /** One {@link ChannelButton} per channel. */
    private List<ChannelButton> channelButtons;

    /** One {@link ChannelButton} per channel. */
    private List<ChannelButton> channelButtonsGrid;

    /** One {@link ChannelButton} per channel. */
    private List<ChannelButton> channelButtonsProjection;

    /** One {@link ChannelButton} per channel. */
    private List<ChannelButton> overlayButtons;

    /** Button to play movie across channel. */
    private JButton channelMovieButton;

    /** Button to select the color model. */
    private JButton colorModelButton;

    /** Button to select the color model. */
    private JButton colorModelButtonGrid;

    /** Button to select the color model. */
    private JButton colorModelButtonProjection;

    /** Button to bring up the color picker. */
    private JButton colorPickerButton;

    /** Button to paint some textual information on top of the grid image. */
    private JToggleButton textVisibleButton;

    /** Button to play movie across T. */
    private JButton playTMovie;

    /** Button to play movie across lifetime bin. */
    private JButton playLifetimeMovie;

    /** Button to play movie across lifetime bin. */
    private JButton playLifetimeMovieGrid;
    
    /** Button to play movie across T displayed in the split view. */
    private JButton playTMovieGrid;

    /** Button to play movie across T. */
    private JButton playZMovie;

    /** Button to play movie across T displayed in the split view. */
    private JButton playZMovieGrid;

    /** Button to project the displayed image. */
    private JButton projectionProject;

    /** The type of supported projections. */
    private JComboBox projectionTypesBox;

	/** The type of projection. */
	private Map<Integer, Integer> projectionTypes;

    /** Sets the stepping for the mapping. */
    private JSpinner projectionFrequency;

    /** Busy indicating that the images composing the grid are retrieved. */
    private JXBusyLabel gridImageLabel;

    /** Helper reference. */
    private IconManager icons;

    /** The controls. */
    private  JPanel controls;

    /** The Box to turn on or off the overlays.*/
    private JCheckBox overlays;

    /** The listener attached to the overlays. */
    private ActionListener overlaysListener;

    /** Button to reset the zoom level to <code>1</code>.*/
    private JButton resetZoom;

    /**
     * Sets the selected plane.
     * 
     * @param z The selected z-section.
     * @param t The selected time-point.
     */
    private void setSelectedXYPlane(int z, int t)
    {
    	int bin = -1;
    	if (model.isLifetimeImage()) bin = lifetimeSlider.getValue();
    	controller.setSelectedXYPlane(z, t, bin);
    }

    /**
     * Handles the event when the wheel is moved over the {@link #zSlider}
     * or {@link #zSliderGrid}.
     *
     * @param e The event to handle.
     */
    private void mouseWheelMovedZ(MouseWheelEvent e)
    {
        boolean up = true;
        if (e.getWheelRotation() > 0) up = false;
        if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
            int v = model.getDefaultZ()-e.getWheelRotation();
            if (up) {
                if (v <= model.getMaxZ())
                    setSelectedXYPlane(v, model.getRealSelectedT());
            } else { //moving down
                if (v >= 0)
                    setSelectedXYPlane(v,  model.getRealSelectedT());
            }
        }
    }

    /**
     * Handles the event when the wheel is moved over the {@link #tSlider}
     * or {@link #tSliderGrid}.
     *
     * @param e The event to handle.
     */
    private void mouseWheelMovedT(MouseWheelEvent e)
    {
        boolean up = true;
        if (e.getWheelRotation() > 0) up = false;
        if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
            int v = model.getRealSelectedT()-e.getWheelRotation();
            if (up) {
                if (v <= model.getRealT())
                    setSelectedXYPlane(model.getDefaultZ(), v);
            } else { //moving down
                if (v >= 0)
                    setSelectedXYPlane(model.getDefaultZ(), v);
            }
        }
    }

    /**
     * Handles the event when the wheel is moved over the 
     * {@link #lifetimeSlider}.
     *
     * @param e The event to handle.
     */
    private void mouseWheelMovedLifetime(MouseWheelEvent e)
    {
        boolean up = true;
        if (e.getWheelRotation() > 0) up = false;
        if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
            int v = model.getRealSelectedT()-e.getWheelRotation();
            if (up) {
                if (v <= model.getMaxLifetimeBin())
                    setSelectedXYPlane(model.getDefaultZ(),
                    		model.getDefaultT());
            } else { //moving down
                if (v >= 0)
                    setSelectedXYPlane(model.getDefaultZ(),
                    		model.getDefaultT());
            }
        }
    }

    /**
     * Returns the description of the current color model.
     *
     * @param model The color model.
     * @return See above.
     */
    private String getColorModelDescription(String model)
    {
    	if (ImViewer.GREY_SCALE_MODEL.equals(model))
            return ColorModelAction.DESCRIPTION_GREY_SCALE;
        else if (ImViewer.RGB_MODEL.equals(model))
        	return ColorModelAction.DESCRIPTION_RGB;
        return null;
    }

    /**
     * Returns the icon corresponding to the current color model.
     * 
     * @param model The color model.
     * @return See above.
     */
    private Icon getColorModelIcon(String model)
    {
        if (ImViewer.GREY_SCALE_MODEL.equals(model))
            return icons.getIcon(IconManager.GRAYSCALE);
        else if (ImViewer.RGB_MODEL.equals(model))
            return icons.getIcon(IconManager.RGB);
        return null;
    }

    /**
     * Returns the color model corresponding to the icon hosted by the
     * passed button.
     *
     * @param button The button to handle.
     * @return
     */
    private String getColorModelFromIcon(JButton button)
    {
    	if (button == null) return null;
    	Icon icon = button.getIcon();
    	if (icons.getIcon(IconManager.GRAYSCALE).equals(icon))
    		return ImViewer.GREY_SCALE_MODEL;
    	return ImViewer.RGB_MODEL;
    }

    /** Initializes the components composing the display. */
    private void initComponents()
    {
        channelButtons = new ArrayList<ChannelButton>();
        channelButtonsGrid = new ArrayList<ChannelButton>();
        channelButtonsProjection = new ArrayList<ChannelButton>();
        projectionRange = new TwoKnobsSlider(0, 1, 0, 1);
        projectionRange.setOrientation(TwoKnobsSlider.VERTICAL);
        projectionRange.setEnabled(false);
        projectionRange.setToolTipText(PROJECTION_SLIDER_DESCRIPTION);
        zSlider = new OneKnobSlider(OneKnobSlider.VERTICAL, 0, 1, 0);
        zSlider.setEnabled(false);
        tSlider = new OneKnobSlider(OneKnobSlider.HORIZONTAL, 0, 1, 0);
        tSlider.setEnabled(false);
        zSliderGrid = new OneKnobSlider(OneKnobSlider.VERTICAL, 0, 1, 0);
        zSliderGrid.setEnabled(false);
        tSliderGrid = new OneKnobSlider(OneKnobSlider.HORIZONTAL, 0, 1, 0);
        tSliderGrid.setEnabled(false);

        tSliderProjection = new OneKnobSlider(OneKnobSlider.HORIZONTAL, 0, 1,
        		0);
        tSliderProjection.setEnabled(false);
        lifetimeSlider = new OneKnobSlider(OneKnobSlider.HORIZONTAL, 0, 1, 0);
        lifetimeSlider.setEnabled(false);
        lifetimeSliderGrid = new OneKnobSlider(OneKnobSlider.HORIZONTAL, 0, 1,
                0);
        lifetimeSliderGrid.setEnabled(false);

        IconManager icons = IconManager.getInstance();
        gridRatioSlider = new OneKnobSlider(OneKnobSlider.VERTICAL, 1, 10, 5);
        gridRatioSlider.setEnabled(true);
        gridRatioSlider.setShowArrows(true);
        gridRatioSlider.setToolTipText(RATIO_SLIDER_DESCRIPTION);
        gridRatioSlider.setArrowsImageIcon(
        				icons.getImageIcon(IconManager.RATIO_MAX),
        				icons.getImageIcon(IconManager.RATIO_MIN));
        ratioSlider = new OneKnobSlider(OneKnobSlider.VERTICAL,
        					ZoomAction.MIN_ZOOM_INDEX,
        					ZoomAction.MAX_ZOOM_INDEX,
        					ZoomAction.DEFAULT_ZOOM_INDEX);
        ratioSlider.setEnabled(true);
        ratioSlider.setShowArrows(true);
        ratioSlider.setToolTipText(ZOOM_SLIDER_DESCRIPTION);

        ratioSlider.setArrowsImageIcon(
        		icons.getImageIcon(IconManager.RATIO_MAX),
        		icons.getImageIcon(IconManager.RATIO_MIN),
        		icons.getImageIcon(IconManager.RATIO_MAX_DISABLED),
        		icons.getImageIcon(IconManager.RATIO_MIN_DISABLED));
        resetZoom = new JButton(icons.getImageIcon(IconManager.ZOOM_FIT));
        resetZoom.setVisible(false);
        UIUtilities.unifiedButtonLookAndFeel(resetZoom);

        projectionRatioSlider = new OneKnobSlider(OneKnobSlider.VERTICAL,
				ZoomAction.MIN_ZOOM_INDEX,
				ZoomAction.MAX_ZOOM_INDEX,
				ZoomAction.DEFAULT_ZOOM_INDEX);
        projectionRatioSlider.setEnabled(true);
        projectionRatioSlider.setShowArrows(true);
        projectionRatioSlider.setToolTipText(ZOOM_SLIDER_DESCRIPTION);

        projectionRatioSlider.setArrowsImageIcon(
        		icons.getImageIcon(IconManager.RATIO_MAX),
        		icons.getImageIcon(IconManager.RATIO_MIN));

        channelMovieButton = new JButton(
                controller.getAction(ImViewerControl.CHANNEL_MOVIE));
        UIUtilities.unifiedButtonLookAndFeel(channelMovieButton);
        colorModelButton = new JButton();
        UIUtilities.unifiedButtonLookAndFeel(colorModelButton);
        colorModelButton.addActionListener(controller);
        colorModelButtonGrid = new JButton();
        UIUtilities.unifiedButtonLookAndFeel(colorModelButtonGrid);
        colorModelButtonGrid.addActionListener(controller);

        colorModelButtonProjection = new JButton();
        UIUtilities.unifiedButtonLookAndFeel(colorModelButtonProjection);
        colorModelButtonProjection.addActionListener(controller);

        ViewerAction a = controller.getAction(ImViewerControl.COLOR_PICKER);
        colorPickerButton = new JButton(a);
        colorPickerButton.addMouseListener((ColorPickerAction) a);
        UIUtilities.unifiedButtonLookAndFeel(colorPickerButton);
        textVisibleButton = new JToggleButton();
        textVisibleButton.setSelected(model.isTextVisible());
        textVisibleButton.setAction(
                controller.getAction(ImViewerControl.TEXT_VISIBLE));
        playTMovie = new JButton(
                controller.getAction(ImViewerControl.PLAY_MOVIE_T));
        UIUtilities.unifiedButtonLookAndFeel(playTMovie);
        playTMovieGrid = new JButton(
                controller.getAction(ImViewerControl.PLAY_MOVIE_T));
        UIUtilities.unifiedButtonLookAndFeel(playTMovieGrid);
        playZMovie = new JButton(
                controller.getAction(ImViewerControl.PLAY_MOVIE_Z));
        UIUtilities.unifiedButtonLookAndFeel(playZMovie);

        playZMovieGrid = new JButton(
                controller.getAction(ImViewerControl.PLAY_MOVIE_Z));
        UIUtilities.unifiedButtonLookAndFeel(playZMovieGrid);

        projectionProject = new JButton(
                controller.getAction(ImViewerControl.PROJECTION_PROJECT));

        projectionFrequency = new JSpinner(new SpinnerNumberModel(1, 1, 1, 1));
        JComponent comp = projectionFrequency.getEditor();
        if (comp instanceof JSpinner.NumberEditor) {
            JFormattedTextField field =
                    ((JSpinner.NumberEditor) comp).getTextField();
            field.addActionListener(this);
            field.setActionCommand(""+FREQUENCY);
        }
        String[] names = new String[ProjectionParam.PROJECTIONS.size()];
        int index = 0;
        Entry<Integer, String> entry;
        Iterator<Entry<Integer, String>>
        i = ProjectionParam.PROJECTIONS.entrySet().iterator();
        projectionTypes = new HashMap<Integer, Integer>();
        int j;
        while (i.hasNext()) {
            entry = i.next();
            j = entry.getKey();
            projectionTypes.put(index, j);
            names[index] = entry.getValue();
            index++;
        }
        projectionTypesBox = EditorUtil.createComboBox(names, 0,
                getBackground());
        projectionTypesBox.setBackground(getBackground());
        projectionTypesBox.setToolTipText(PROJECTION_DESCRIPTION);
        projectionTypesBox.setActionCommand(""+TYPE);
        projectionTypesBox.addActionListener(this);

        playLifetimeMovie = new JButton(
                controller.getAction(ImViewerControl.PLAY_LIFETIME_MOVIE));
        UIUtilities.unifiedButtonLookAndFeel(playLifetimeMovie);

        playLifetimeMovieGrid = new JButton(
                controller.getAction(ImViewerControl.PLAY_LIFETIME_MOVIE));
        UIUtilities.unifiedButtonLookAndFeel(playLifetimeMovieGrid);
        Icon icon = textVisibleButton.getIcon();
        Dimension d = DIMENSION;
        if (icon != null) {
            d = new Dimension(icon.getIconWidth(), icon.getIconHeight());
        }
        gridImageLabel = new JXBusyLabel(d);
        gridImageLabel.setVisible(false);
        controls = new JPanel();
        double size[][] = {{TableLayout.PREFERRED},
                {TableLayout.PREFERRED, TableLayout.PREFERRED,
            TableLayout.PREFERRED, TableLayout.PREFERRED,
            SLIDER_HEIGHT, TableLayout.PREFERRED, TableLayout.PREFERRED}};
        controls.setLayout(new TableLayout(size));
    }

    /**
     * Attaches listener to the passed slider and sets the default values.
     *
     * @param slider The slider to handle.
     * @param max The maximum value.
     * @param v The default value.
     * @param toolTip The text displayed in the tool tip.
     * @param endLabel The text displayed in the tool tip when
     * 					slider changes value, as well as the label shown at
     * 					the end of the text.
     */
    private void initSlider(OneKnobSlider slider, int max, int v,
            String toolTip, String endLabel)
    {
        slider.setVisible(max != 0);
        slider.setMaximum(max);
        slider.setValue(v);
        slider.addChangeListener(this);
        slider.addMouseWheelListener(this);
        slider.setToolTipText(toolTip);
        slider.setEndLabel(endLabel);
        slider.setShowEndLabel(true);
        slider.setShowTipLabel(true);
        if (max > 1  && max <= MAX_NO_TICKS) {
            slider.setPaintTicks(true);
            slider.setMajorTickSpacing(1);
        }
    }

    /**
     * Sets the tool tip of the specified slider.
     *
     * @param v The selected z-section, timepoint, bin, etc.
     * @param slider The slider to handle.
     * @
     */
    private void setSliderToolTip(int v, OneKnobSlider slider, int index)
    {
    	String tip = "";
    	switch (index) {
            case T:
                tip = "T="+(v+1)+"/"+model.getRealT();
                break;
            case Z:
                tip = "Z="+(v+1)+"/"+(model.getMaxZ()+1);
                break;
            case BIN:
                tip = "t="+(v+1)+"/"+model.getMaxLifetimeBin();
        }
    	slider.setToolTipText(tip);
    }

    /**
     * Initializes the value of the components displaying the currently selected
     * z-section and time-point.
     */
    private void initializeValues()
    {
        int maxZ = model.getMaxZ();
        int maxT = model.getRealT();
        projectionRange.setValues(maxZ+1, 1, maxZ+1, 1, 1, maxZ+1);
        projectionRange.addPropertyChangeListener(this);
        projectionRange.addMouseWheelListener(this);
        projectionRange.setToolTipText(PROJECTION_SLIDER_DESCRIPTION);
        setRangeSliderToolTip(0, maxZ);

        initSlider(tSliderProjection, maxT-1, model.getRealSelectedT(),
                T_SLIDER_DESCRIPTION, T_SLIDER_TIPSTRING);
        setSliderToolTip(model.getRealSelectedT(), tSliderProjection, T);

        initSlider(zSlider, maxZ, model.getDefaultZ(),
                Z_SLIDER_DESCRIPTION, Z_SLIDER_TIPSTRING);
        initSlider(zSliderGrid, maxZ, model.getDefaultZ(),
                Z_SLIDER_DESCRIPTION, Z_SLIDER_TIPSTRING);

        setSliderToolTip(model.getDefaultZ(), zSlider, Z);
        setSliderToolTip(model.getDefaultZ(), zSliderGrid, Z);
        initSlider(tSlider, maxT-1, model.getRealSelectedT(),
                T_SLIDER_DESCRIPTION, T_SLIDER_TIPSTRING);
        initSlider(tSliderGrid, maxT-1, model.getRealSelectedT(),
                T_SLIDER_DESCRIPTION, T_SLIDER_TIPSTRING);

        setSliderToolTip(model.getRealSelectedT(), tSlider, T);
        setSliderToolTip(model.getRealSelectedT(), tSliderGrid, T);
        if (model.isBigImage()) {
            ratioSlider.addPropertyChangeListener(this);
            ratioSlider.setMaximum(model.getResolutionLevels()-1);
            ratioSlider.setValue(model.getSelectedResolutionLevel());
            resetZoom.addActionListener(
                    controller.getZoomActionFromLevels(
                            model.getResolutionLevels()-1));
            resetZoom.setToolTipText("Reset to full size.");
            resetZoom.setText("");
        } 
        ratioSlider.addChangeListener(this);
        gridRatioSlider.addChangeListener(this);
        projectionRatioSlider.addChangeListener(this);

        playTMovie.setVisible(maxT > 1);
        playTMovieGrid.setVisible(maxT > 1);
        playZMovie.setVisible(maxZ != 0);
        playZMovieGrid.setVisible(maxZ != 0);
        colorModelButton.setIcon(getColorModelIcon(model.getColorModel()));
        colorModelButton.setToolTipText(
                getColorModelDescription(model.getColorModel()));
        colorModelButtonGrid.setIcon(getColorModelIcon(model.getColorModel()));
        colorModelButtonGrid.setToolTipText(
                getColorModelDescription(model.getColorModel()));
        colorModelButtonProjection.setIcon(
                getColorModelIcon(model.getColorModel()));
        colorModelButtonProjection.setToolTipText(
                getColorModelDescription(model.getColorModel()));
        SpinnerNumberModel m =
                (SpinnerNumberModel) projectionFrequency.getModel();
        m.setMaximum(view.getMaxZ()+1);
        projectionFrequency.addChangeListener(this);

        //Lifetime for now
        int maxBin = model.getMaxLifetimeBin();
        initSlider(lifetimeSlider, maxBin-1, model.getSelectedBin(),
                LITEIME_SLIDER_DESCRIPTION, EditorUtil.SMALL_T_VARIABLE);
        setSliderToolTip(model.getSelectedBin(), lifetimeSlider, BIN);
        lifetimeSlider.setPaintTicks(false);
        initSlider(lifetimeSliderGrid, maxBin-1, model.getSelectedBin(),
                LITEIME_SLIDER_DESCRIPTION, EditorUtil.SMALL_T_VARIABLE);
        setSliderToolTip(model.getSelectedBin(), lifetimeSliderGrid, BIN);
        playLifetimeMovie.setVisible(maxBin > 1);
        playLifetimeMovie.setEnabled(maxBin > 1);
        playLifetimeMovieGrid.setVisible(maxBin > 1);
        playLifetimeMovieGrid.setEnabled(maxBin > 1);
        if (model.isBigImage()) resetZoom.setVisible(true);
    }

    /**
     * Helper method to create a panel hosting the passed slider.
     *
     * @param slider The slider to host.
     * @return See above.
     */
    private JPanel layoutSlider(JComponent slider)
    {
        JPanel pane = new JPanel();
        pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
        pane.add(slider);
        return pane;
    }

    /**
     * Builds a tool bar hosting the passed button.
     *
     * @param button The button to add.
     * @return See above
     */
    private JToolBar createButtonToolBar(JComponent button)
    {
        JToolBar bar = new JToolBar();
        bar.setFloatable(false);
        bar.setRollover(true);
        bar.setBorder(null);
        bar.add(button);
        return bar;
    }

    /**
     * Helper method to create a panel hosting the passed slider.
     *
     * @return See above.
     */
    private JPanel createZSliderPane()
    {
    	JPanel pane = new JPanel();
    	double[][] tl = {{TableLayout.FILL}, 
				{TableLayout.FILL, TableLayout.PREFERRED}};
    	pane.setLayout(new TableLayout(tl));
    	pane.add(zSlider, "0, 0");
    	pane.add(createButtonToolBar(playZMovie), "0, 1");
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(pane);
        return p;
    }

    /**
     * Helper method to create a panel hosting the passed slider.
     *
     * @return See above.
     */
    private JPanel createZGridSliderPane()
    {
        JPanel pane = new JPanel();
        double[][] tl = {{TableLayout.FILL},
                {TableLayout.FILL, TableLayout.PREFERRED}};
        pane.setLayout(new TableLayout(tl));
        pane.add(zSliderGrid, "0, 0");
        pane.add(createButtonToolBar(playZMovieGrid), "0, 1");
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(pane);
        return p;
    }

    /** 
     * Builds the tool bar displayed on the left side of the image.
     *
     * @return See above.
     */
    private JComponent buildToolBar()
    {
        if (!model.isLifetimeImage() || model.getModuloT() != null) {
        	JToolBar bar = createBar();
            bar.add(colorModelButton);
            bar.add(Box.createRigidArea(VBOX));
            bar.add(colorPickerButton);
            return bar;
        }
        return colorModelButton;
    }

    /** 
     * Builds the tool bar displayed on the left side of the  grid view.
     *
     * @return See above.
     */
    private JToolBar buildGridBar()
    {
    	JToolBar bar = createBar();
        bar.add(gridImageLabel);
        bar.add(Box.createRigidArea(VBOX));
        bar.add(colorModelButtonGrid);
        return bar;
    }

    /**
     * Creates a tool bar.
     *
     * @return See above.
     */
    private JToolBar createBar()
    {
    	JToolBar bar = new JToolBar(JToolBar.VERTICAL);
        bar.setFloatable(false);
        bar.setRollover(true);
        bar.setBorder(null);
        return bar;
    }

    /** 
     * Builds the tool bar displayed on the left side of the  projection view.
     *
     * @return See above.
     */
    private JToolBar buildProjectionBar()
    {
        JToolBar bar = new JToolBar(JToolBar.VERTICAL);
        bar.setFloatable(false);
        bar.setRollover(true);
        bar.setBorder(null);
        bar.add(colorModelButtonProjection);
        return bar;
    }

    /**
     * Creates a UI component hosting the {@link ChannelButton}s.
     * 
     * @return See above.
     */
    private JPanel createChannelsPane()
    {
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
        ChannelButton button;
        Dimension d;
        int w = 0, h = 0;
        if (!model.isLifetimeImage() || model.getModuloT() != null) {
            p.add(Box.createRigidArea(VBOX));
            channelButtons = createChannelButtons();
            Iterator<ChannelButton> i = channelButtons.iterator();
            while (i.hasNext()) {
                button = i.next();
                d = button.getPreferredSize();
                if (d.width > w) w = d.width;
                if (d.height > h) h = d.height;
                button.addPropertyChangeListener(controller);
                p.add(button);
                p.add(Box.createRigidArea(VBOX));
            }
        }

        controls.add(Box.createVerticalStrut(20), "0, 0");
        int k = 1;
        controls.add(buildToolBar(), "0, "+k+", CENTER, CENTER");
        k++;
        if (channelButtons.size() > ImViewer.MAX_CHANNELS) {
            JScrollPane sp = new JScrollPane(p);
            d = new Dimension(2*w, h*ImViewer.MAX_CHANNELS);
            sp.setPreferredSize(d);
            controls.add(sp, "0, "+k+", RIGHT, CENTER");
        } else controls.add(p, "0, "+k);
        k++;
        if (!model.isLifetimeImage() || model.getModuloT() != null) {
            controls.add(createButtonToolBar(channelMovieButton),
                    "0, "+k+", CENTER, CENTER");
        }
        k++;
        controls.add(ratioSlider, "0, "+k+", CENTER, CENTER");
        k++;
        controls.add(resetZoom, "0, "+k+", CENTER, CENTER");
        return UIUtilities.buildComponentPanel(controls);
    }

    /** Builds and lays out the UI. */
    private void buildGUI()
    {
        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
        add(createChannelsPane());
        add(createZSliderPane());
    }

    /**
     * Sets the value of the specified slider when a propertyChange has been
     * fired.
     *
     * @param slider The slider to update.
     * @param v The selected value.
     */
    private void updateSlider(JSlider slider, int v)
    {
        slider.removeChangeListener(this);
        slider.setValue(v);
        slider.addChangeListener(this);
    }

    /**
     * Sets the maximum value of the slider.
     *
     * @param slider The slider to handle.
     * @param max The maximum value to set.
     */
    private void setSliderMax(JSlider slider, int max)
    {
        slider.removeChangeListener(this);
        slider.setMaximum(max);
        slider.addChangeListener(this);
    }

    /**
     * Creates a new instance.
     *
     * @param controller Reference to the Control. Mustn't be <code>null</code>.
     * @param model Reference to the Model. Mustn't be <code>null</code>.
     * @param view Reference to the View. Mustn't be <code>null</code>.
     */
    ControlPane(ImViewerControl controller, ImViewerModel model,
                ImViewerUI view)
    {
        if (controller == null) throw new NullPointerException("No control.");
        if (model == null) throw new NullPointerException("No model.");
        if (view == null) throw new NullPointerException("No view.");
        this.controller = controller;
        this.model = model;
        this.view = view;
        icons = IconManager.getInstance();
        initComponents();
    }

    /**
     * Creates a collection of <code>ChannelButton</code>s.
     *
     * @return See above.
     */
    List<ChannelButton> createChannelButtons()
    {
        List<ChannelButton> channelButtons = new ArrayList<ChannelButton>();
        List<ChannelData> data = model.getChannelData();
        boolean gs = model.getColorModel().equals(ImViewer.GREY_SCALE_MODEL);
        ChannelButton button;
        ChannelData d;
        Dimension dim;
        Dimension dimMax = ChannelButton.DEFAULT_MIN_SIZE;
        Iterator<ChannelData> i = data.iterator();
        int k;
        while (i.hasNext()) {
            d = i.next();
            k = d.getIndex();
            button = new ChannelButton(""+d.getChannelLabeling(),
                    model.getChannelColor(k), k, model.isChannelActive(k));
            if (gs) button.setGrayedOut(gs);
            channelButtons.add(button);
            dim = button.getPreferredSize();
            if (dim.width > dimMax.width) 
                dimMax = new Dimension(dim.width, dimMax.height);
        }

        Iterator<ChannelButton> j = channelButtons.iterator();
        while (j.hasNext())
            j.next().setPreferredSize(dimMax);
        return channelButtons;
    }

    /** 
     * This method should be called straight after the metadata and the
     * rendering settings are loaded.
     */
    void buildComponent()
    {
        initializeValues();
        buildGUI();
    }

    /**
     * Builds the control panel displayed in the grid view.
     *
     * @return See above.
     */
    JPanel buildGridComponent()
    {
        if (model.getModuloT() == null &&
           model.isLifetimeImage()) return new JPanel();
        JPanel p = createZGridSliderPane();
        JPanel buttons = new JPanel();
        buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS));
        ChannelButton button;
        buttons.add(Box.createRigidArea(VBOX));
        channelButtonsGrid = createChannelButtons();
        Iterator<ChannelButton> i = channelButtonsGrid.iterator();
        Dimension d;
        int w = 0, h = 0;
        while (i.hasNext()) {
            button = i.next();
            d = button.getPreferredSize();
            if (d.width > w) w = d.width;
            if (d.height > h) h = d.height;
            buttons.add(button);
            buttons.add(Box.createRigidArea(VBOX));
            button.addPropertyChangeListener(controller);
        }
        JPanel controls = new JPanel();
        double size[][] = {{TableLayout.PREFERRED},
                {TableLayout.PREFERRED, TableLayout.PREFERRED,
            TableLayout.PREFERRED, TableLayout.PREFERRED, SLIDER_HEIGHT}};

        controls.setLayout(new TableLayout(size));
        controls.add(Box.createVerticalStrut(10), "0, 0");
        JToolBar bar = buildGridBar();
        bar.add(Box.createRigidArea(VBOX));
        controls.add(bar, "0, 1, CENTER, CENTER");
        controls.add(createButtonToolBar(textVisibleButton), "0, 2, " +
                "CENTER, CENTER");
        if (channelButtonsGrid.size() > ImViewer.MAX_CHANNELS) {
            JScrollPane sp = new JScrollPane(buttons);
            d = new Dimension(2*w, h*ImViewer.MAX_CHANNELS);
            sp.setPreferredSize(d);
            controls.add(sp, "0, 3, RIGHT, CENTER");
        } else controls.add(buttons, "0, 3");
        controls.add(gridRatioSlider, "0, 4, CENTER, CENTER");

        JPanel content = new JPanel();
        content.setLayout(new BoxLayout(content, BoxLayout.X_AXIS));
        content.add(UIUtilities.buildComponentPanel(controls));
        content.add(p);
        return content;
    }

    /** 
     * Builds the component hosting controls for projection.
     *
     * @return See above
     */
    JPanel buildProjectionToolBar()
    {
        JPanel bar = new JPanel();
        bar.setBorder(null);
        bar.add(new JLabel("Intensity: "));
        bar.add(projectionTypesBox);
        bar.add(new JLabel(" Every n-th slice: "));
        bar.add(projectionFrequency);
        bar.add(Box.createRigidArea(VBOX));
        bar.add(projectionProject);
        JPanel projectionBar = new JPanel();
        projectionBar.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
        projectionBar.add(bar);
        return projectionBar;
    }

    /**
     * Builds the component hosting the controls to manage the projected image.
     *
     * @return See above.
     */
    JPanel buildProjectionComponent()
    {
        if (model.getModuloT() == null &&
           model.isLifetimeImage()) return new JPanel();
        JPanel p = layoutSlider(projectionRange);
        JPanel buttons = new JPanel();
        buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS));
        ChannelButton button;
        buttons.add(Box.createRigidArea(VBOX));
        channelButtonsProjection = createChannelButtons();
        Iterator<ChannelButton> i = channelButtonsProjection.iterator();
        int w = 0, h = 0;
        Dimension d;
        while (i.hasNext()) {
            button = i.next();
            d = button.getPreferredSize();
            if (d.width > w) w = d.width;
            if (d.height > h) h = d.height;
            buttons.add(button);
            buttons.add(Box.createRigidArea(VBOX));
            button.addPropertyChangeListener(controller);
        }
        JPanel controls = new JPanel();
        double size[][] = {{TableLayout.PREFERRED}, 
                {TableLayout.PREFERRED, TableLayout.PREFERRED,
            TableLayout.PREFERRED, SLIDER_HEIGHT}};

        controls.setLayout(new TableLayout(size));
        controls.add(Box.createVerticalStrut(20), "0, 0");
        int k = 1;
        controls.add(buildProjectionBar(), "0, "+k+", CENTER, CENTER");
        k++;
        if (channelButtonsProjection.size() > ImViewer.MAX_CHANNELS) {
            JScrollPane sp = new JScrollPane(buttons);
            d = new Dimension(2*w, h*ImViewer.MAX_CHANNELS);
            sp.setPreferredSize(d);
            controls.add(sp, "0, "+k+", RIGHT, CENTER");
        } else controls.add(buttons, "0, "+k+", CENTER, CENTER");
        k++;
        controls.add(projectionRatioSlider, "0, "+k+", CENTER, CENTER");

        JPanel content = new JPanel();
        content.setLayout(new BoxLayout(content, BoxLayout.X_AXIS));
        content.add(UIUtilities.buildComponentPanel(controls));
        content.add(p);
        return content;
    }

    /**
     * Updates UI components when a new timepoint is selected.
     *
     * @param t The selected timepoint.
     */
    void setTimepoint(int t)
    {
        setSliderToolTip(t, tSlider, T);
        setSliderToolTip(t, tSliderGrid, T);
        updateSlider(tSlider, t);
        updateSlider(tSliderGrid, t);
        updateSlider(tSliderProjection, t);
    }

    /**
     * Updates UI components when a new bin is selected.
     *
     * @param v The selected bin.
     */
    void setBin(int v)
    {
        if (lifetimeSlider == null) return;
        setSliderToolTip(v, lifetimeSlider, BIN);
        updateSlider(lifetimeSlider, v);
        setSliderToolTip(v, lifetimeSliderGrid, BIN);
        updateSlider(lifetimeSliderGrid, v);
    }

    /**
     * Updates UI components when a new z-section is selected.
     * 
     * @param z The selected z-section.
     */
    void setZSection(int z)
    {
        setSliderToolTip(z, zSlider, Z);
        setSliderToolTip(z, zSliderGrid, Z);
        updateSlider(zSlider, z);
        updateSlider(zSliderGrid, z);
    }

    /** Updates UI components when a new color model is selected. */
    void setColorModel()
    {
        boolean gs = (model.getColorModel().equals(ImViewer.GREY_SCALE_MODEL));
        Iterator<ChannelButton> i = channelButtons.iterator();
        ChannelButton button;
        while (i.hasNext()) {
            button = i.next();
            button.setSelected(
                    model.isChannelActive(button.getChannelIndex()));
            button.setGrayedOut(gs);
        }
        i = channelButtonsGrid.iterator();
        while (i.hasNext()) {
            button = i.next();
            if (!gs) 
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            button.setGrayedOut(gs);
        }
        i = channelButtonsProjection.iterator();
        while (i.hasNext()) {
            button = i.next();
            button.setSelected(
                    model.isChannelActive(button.getChannelIndex()));
            button.setGrayedOut(gs);
        }
        colorModelButton.setIcon(getColorModelIcon(model.getColorModel()));
        colorModelButton.setToolTipText(getColorModelDescription(
                model.getColorModel()));
        colorModelButtonGrid.setIcon(getColorModelIcon(model.getColorModel()));
        colorModelButtonGrid.setToolTipText(getColorModelDescription(
                model.getColorModel()));
        colorModelButtonProjection.setIcon(
                getColorModelIcon(model.getColorModel()));
        colorModelButtonProjection.setToolTipText(getColorModelDescription(
                model.getColorModel()));
    }

    /**
     * Sets the selected channels in the grid view.
     * 
     * @param channels Collection of channels to set.
     */
    void setChannelsSelection(List channels)
    {
        Iterator<ChannelButton> i = channelButtonsGrid.iterator();
        ChannelButton button;
        int index;
        while (i.hasNext()) {
            button = i.next();
            index = button.getChannelIndex();
            button.setSelected(channels.contains(index));
        }
    }

    /** 
     * Updates the {@link ChannelButton}s when a new one is selected or 
     * deselected.
     * 
     * @param index One of the following constants {@link ImViewerUI#GRID_ONLY},
	 * 				{@link ImViewerUI#VIEW_ONLY} and 
	 * 				{@link ImViewerUI#ALL_VIEW}.
     */
    void setChannelsSelection(int index)
    {
        Iterator<ChannelButton> i;
        ChannelButton button;
        switch (index) {
        case ImViewerUI.GRID_ONLY:
            i = channelButtonsGrid.iterator();
            while (i.hasNext()) {
                button = i.next();
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            }
            break;
        case ImViewerUI.VIEW_ONLY:
            i = channelButtons.iterator();
            while (i.hasNext()) {
                button = i.next();
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            }
            break;
        case ImViewerUI.PROJECTION_ONLY:
            i = channelButtonsProjection.iterator();
            while (i.hasNext()) {
                button = i.next();
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            }
            break;
        case ImViewerUI.ALL_VIEW:
            i = channelButtons.iterator();
            while (i.hasNext()) {
                button = i.next();
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            }
            i = channelButtonsGrid.iterator();
            while (i.hasNext()) {
                button = i.next();
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            }
            i = channelButtonsProjection.iterator();
            while (i.hasNext()) {
                button = i.next();
                button.setSelected(
                        model.isChannelActive(button.getChannelIndex()));
            }
        }
    }

    /**
     * Sets the color of selected channel.
     * 
     * @param index The channel index.
     * @param c The color to set.
     */
    void setChannelColor(int index, Color c)
    {
        Iterator<ChannelButton> i = channelButtons.iterator();
        ChannelButton button;
        while (i.hasNext()) {
            button = i.next();
            if (index == button.getChannelIndex())
                button.setColor(c);
        }
        i = channelButtonsGrid.iterator();
        while (i.hasNext()) {
            button = i.next();
            if (index == button.getChannelIndex())
                button.setColor(c);
        }
        i = channelButtonsProjection.iterator();
        while (i.hasNext()) {
            button = i.next();
            if (index == button.getChannelIndex())
                button.setColor(c);
        }
    }

    /** Resets the rendering settings. */
    void resetRndSettings()
    {
        boolean gs = (model.getColorModel().equals(ImViewer.GREY_SCALE_MODEL));
        Iterator<ChannelButton> i = channelButtons.iterator();
        ChannelButton button;
        int index;
        while (i.hasNext()) {
            button = i.next();
            index = button.getChannelIndex();
            button.setSelected(model.isChannelActive(index));
            button.setColor(model.getChannelColor(index));
            button.setGrayedOut(gs);
        }
        i = channelButtonsGrid.iterator();
        while (i.hasNext()) {
            button = i.next();
            index = button.getChannelIndex();
            button.setSelected(model.isChannelActive(index));
            button.setColor(model.getChannelColor(index));
            button.setGrayedOut(gs);
        }
        i = channelButtonsProjection.iterator();
        while (i.hasNext()) {
            button = i.next();
            index = button.getChannelIndex();
            button.setSelected(model.isChannelActive(index));
            button.setColor(model.getChannelColor(index));
            button.setGrayedOut(gs);
        }
        Icon icon = getColorModelIcon(model.getColorModel());
        String tip = getColorModelDescription(model.getColorModel());
        colorModelButton.setIcon(icon);
        colorModelButton.setToolTipText(tip);
        colorModelButtonGrid.setIcon(icon);
        colorModelButtonGrid.setToolTipText(tip);
        colorModelButtonProjection.setIcon(icon);
        colorModelButtonProjection.setToolTipText(tip);
        setZSection(model.getDefaultZ());
        setTimepoint(model.getDefaultT());
        setBin(model.getSelectedBin());
        ProjectionParam ref = model.getLastProjRef();
        if (ref != null)
            projectionRange.setInterval(ref.getStartZ(), ref.getEndZ());
    }

    /** Resets the UI when the user switches to a new rendering control. */
    void switchRndControl()
    {
        setSliderMax(zSlider, model.getMaxZ());
        setSliderMax(zSliderGrid, model.getMaxZ());
        setSliderMax(tSliderGrid, model.getRealT());
        setSliderMax(tSlider, model.getRealT());
        if (lifetimeSlider != null) {
            setSliderMax(lifetimeSlider, model.getMaxLifetimeBin());
            setSliderMax(lifetimeSliderGrid, model.getMaxLifetimeBin());
        }
        resetRndSettings();
    }

    /** 
     * Reacts to {@link ImViewer} change events.
     * 
     * @param b Pass <code>true</code> to enable the UI components,
     *          <code>false</code> otherwise.
     */
    void onStateChange(boolean b)
    {
        Iterator<ChannelButton> i = channelButtons.iterator();
        while (i.hasNext())
            i.next().setEnabled(b);

        i = channelButtonsGrid.iterator();
        while (i.hasNext())
            i.next().setEnabled(b);

        i = channelButtonsProjection.iterator();
        while (i.hasNext())
            i.next().setEnabled(b);

        colorModelButton.setEnabled(b);
        colorModelButtonGrid.setEnabled(b);
        colorModelButtonProjection.setEnabled(b);

        //projection stuff
        if (projectionTypesBox != null) projectionTypesBox.setEnabled(b);
        if (projectionFrequency != null) projectionFrequency.setEnabled(b);
        if (b) onColorModelChanged();
        else {
            if (overlays != null) overlays.setEnabled(false);
            if (overlayButtons != null) {
                i = overlayButtons.iterator();
                while (i.hasNext())
                    i.next().setEnabled(false);
            }
        }
        //big images.
        if (model.isBigImage()) {
            boolean loading = model.getState() != ImViewer.LOADING_TILES;
            ratioSlider.setEnabled(loading);
            gridRatioSlider.setEnabled(loading);
            projectionRatioSlider.setEnabled(loading);
            if (resetZoom != null) resetZoom.setEnabled(loading);
        }
    }

    /**
     * Builds and returns a UI component hosting the time slider
     * corresponding to the passed index.
     * 
     * @param index The index used to identify the slider.
     * @return See above.
     */
    JPanel getTimeSliderPane(int index)
    {
        switch (index) {
        case ImViewer.GRID_INDEX:
            JPanel p = new JPanel();
            p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
            p.add(createButtonToolBar(playTMovieGrid));
            p.add(tSliderGrid);
            return p;
        case ImViewer.PROJECTION_INDEX:
            return layoutSlider(tSliderProjection);
        case ImViewer.VIEW_INDEX:
        default:
            JPanel pane = new JPanel();
            pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
            pane.add(createButtonToolBar(playTMovie));
            pane.add(tSlider);
            return pane;
        }
    }

    /**
     * Builds and returns a UI component hosting the time slider
     * corresponding to the passed index.
     * 
     * @param index The index used to identify the slider.
     * @return See above.
     */
    JPanel getLifetimeSliderPane(int index)
    {
        JPanel pane = new JPanel();
        pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
        switch (index) {
        case ImViewer.VIEW_INDEX:
        default:
            pane.add(createButtonToolBar(playLifetimeMovie));
            pane.add(lifetimeSlider);
            return pane;
        case ImViewer.GRID_INDEX:
            pane.add(createButtonToolBar(playLifetimeMovieGrid));
            pane.add(lifetimeSliderGrid);
            return pane;
        }
    }


    /**
     * Sets the <code>enable</code> flag of the slider used to select
     * the current z-section and timepoint.
     * 
     * @param b Pass <code>true</code> to enable the sliders,
     * 			<code>false</code> otherwise.
     */
    void enableSliders(boolean b)
    {
        enableZSliders(b);
        enableTSliders(b);
        if (b) {
            boolean v = model.getMaxLifetimeBin() > 1;
            lifetimeSlider.setEnabled(v);
            lifetimeSliderGrid.setEnabled(v);
        } else {
            lifetimeSlider.setEnabled(b);
            lifetimeSliderGrid.setEnabled(b);
        }
    }

    /**
     * Sets the <code>enable</code> flag of the slider used to select
     * the current z-section.
     * 
     * @param b Pass <code>true</code> to enable the sliders,
     * 			<code>false</code> otherwise.
     */
    void enableZSliders(boolean b)
    {
        if (b) {
            zSlider.setEnabled(model.getMaxZ() != 0);
            zSliderGrid.setEnabled(model.getMaxZ() != 0);
            projectionRange.setEnabled(model.getMaxZ() != 0);
        } else {
            zSlider.setEnabled(b);
            zSliderGrid.setEnabled(b);
            projectionRange.setEnabled(b);
        }
    }

    /**
     * Sets the <code>enable</code> flag of the slider used to select
     * the current timepoint.
     * 
     * @param b Pass <code>true</code> to enable the sliders,
     * 			<code>false</code> otherwise.
     */
    void enableTSliders(boolean b)
    {
        if (b) {
            tSlider.setEnabled(model.getRealT() > 1);
            tSliderGrid.setEnabled(model.getRealT() > 1);
            tSliderProjection.setEnabled(model.getRealT() > 1);
        } else {
            tSlider.setEnabled(b);
            tSliderGrid.setEnabled(b);
            tSliderProjection.setEnabled(b);
        }
    }

    /**
     * Sets the specified channel to active.
     *
     * @param index The channel's index.
     * @param uiIndex One of the following constants
     * 				  {@link ImViewerUI#GRID_ONLY} and
	 * 				  {@link ImViewerUI#ALL_VIEW}.
     */
    void setChannelActive(int index, int uiIndex)
    {
        Iterator<ChannelButton> i;
        ChannelButton button;
        switch (uiIndex) {
        case ImViewerUI.GRID_ONLY:
            i = channelButtonsGrid.iterator();
            while (i.hasNext()) {
                button = i.next();
                if (index == button.getChannelIndex())
                    button.setSelected(true);
            }
            break;
        case ImViewerUI.ALL_VIEW:
            i = channelButtons.iterator();
            while (i.hasNext()) {
                button = i.next();
                if (index == button.getChannelIndex())
                    button.setSelected(true);
            }
            i = channelButtonsGrid.iterator();
            while (i.hasNext()) {
                button = i.next();
                if (index == button.getChannelIndex())
                    button.setSelected(true);
            }
            i = channelButtonsProjection.iterator();
            while (i.hasNext()) {
                button = i.next();
                if (index == button.getChannelIndex())
                    button.setSelected(true);
            }
        }
    }

    /**
     * Returns the collection of active channels in the grid view.
     *
     * @return See above.
     */
    List getActiveChannelsInGrid()
    {
        List<Integer> active = new ArrayList<Integer>();
        Iterator<ChannelButton> i = channelButtonsGrid.iterator();
        ChannelButton button;
        while (i.hasNext()) {
            button = i.next();
            if (button.isSelected()) active.add(button.getChannelIndex());
        }
        return active;
    }

    /**
     * Returns the collection of active channels in the projection view.
     *
     * @return See above.
     */
    List getActiveChannelsInProjection()
    {
        List<Integer> active = new ArrayList<Integer>();
        Iterator<ChannelButton> i = channelButtonsProjection.iterator();
        ChannelButton button;
        while (i.hasNext()) {
            button = i.next();
            if (button.isSelected()) active.add(button.getChannelIndex());
        }
        return active;
    }

    /**
     * Updates UI components when a zooming factor is selected.
     *
     * @param zoomIndex The index of the selected zoomFactor.
     */
    void setZoomFactor(int zoomIndex)
    {
        if (ratioSlider.getMinimum() > zoomIndex ||
                ratioSlider.getMaximum() < zoomIndex)
            return;
        ratioSlider.removeChangeListener(this);
        ratioSlider.setValue(zoomIndex);
        ratioSlider.addChangeListener(this);
        projectionRatioSlider.removeChangeListener(this);
        projectionRatioSlider.setValue(zoomIndex);
        projectionRatioSlider.addChangeListener(this);
    }

    /**
     * Returns the lower bound of the z-section to project.
     *
     * @return See above.
     */
    int getProjectionStartZ() { return projectionRange.getStartValue()-1; }

    /**
     * Returns the lower bound of the z-section to project.
     *
     * @return See above.
     */
    int getProjectionEndZ() { return projectionRange.getEndValue()-1; }

    /**
     * Updates UI components when a zooming factor for the grid
     * is selected.
     *
     * @param zoomIndex The index of the selected zoomFactor.
     */
    void setGridMagnificationFactor(int zoomIndex)
    {
        if (gridRatioSlider.getMinimum() > zoomIndex ||
                gridRatioSlider.getMaximum() < zoomIndex)
            return;
        gridRatioSlider.removeChangeListener(this);
        gridRatioSlider.setValue(zoomIndex);
        gridRatioSlider.addChangeListener(this);
    }

    /**
     * Returns the stepping used for the projection.
     * 
     * @return See above.
     */
    int getProjectionStepping()
    {
        return (Integer) projectionFrequency.getValue();
    }

    /**
     * Returns the type of projection.
     * 
     * @return See above.
     */
    int getProjectionType()
    {
        int index = projectionTypesBox.getSelectedIndex();
        return projectionTypes.get(index);
    }

    /** Resets the zoom values when the image is large. */
    void resetZoomValues()
    {
        //ratioSlider.setMaximum(ZoomAction.ZOOM_100);
    }

    /**
     * Returns a textual version of the type of projection.
     * 
     * @return See above.
     */
    String getProjectionTypeName()
    {
        int index = projectionTypesBox.getSelectedIndex();
        return ProjectionParam.PROJECTIONS.get(index);
    }

    /**
     * Shows or hides a busy label indicating the on-going creation of the
     * grid image.
     * 
     * @param busy  Pass <code>true</code> to indicate the on-going creation,
     * 				<code>false</code> when it is finished.
     */
    void createGridImage(boolean busy)
    {
        gridImageLabel.setVisible(busy);
        gridImageLabel.setBusy(busy);
    }

    /**
     * Returns <code>true</code> if the passed object is one of the
     * channel buttons, <code>false</code> otherwise.
     * 
     * @param source The object to handle.
     * @return See above.
     */
    boolean isSourceDisplayed(Object source)
    {
        Iterator<ChannelButton> i = channelButtons.iterator();
        while (i.hasNext()) {
            if (i.next() == source) return true;
        }
        i = channelButtonsGrid.iterator();
        while (i.hasNext()) {
            if (i.next() == source) return true;
        }
        i = channelButtonsProjection.iterator();
        while (i.hasNext()) {
            if (i.next() == source) return true;
        }
        return false;
    }

    /** Removes the overlays. */
    void onColorModelChanged() 
    {
        String colorModel = model.getColorModel();
        if (overlays == null) return;
        if (ImViewer.GREY_SCALE_MODEL.equals(colorModel)) {
            overlays.removeActionListener(overlaysListener);
            overlays.setSelected(false);
            overlays.addActionListener(overlaysListener);
            overlays.setEnabled(false);
            Iterator<ChannelButton> i = overlayButtons.iterator();
            while (i.hasNext()) 
                i.next().setEnabled(false);
        } else {
            boolean ready = model.getState() == ImViewer.READY;
            overlays.setEnabled(ready);
            Iterator<ChannelButton> i = overlayButtons.iterator();
            while (i.hasNext()) 
                i.next().setEnabled(ready);
        }
    }

    /** Builds the overlays. */
    void buildOverlays()
    {
        Map m = model.getOverLays();
        if (m == null || m.size() == 0) return;
        overlays = new JCheckBox("Overlays");
        overlaysListener = new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                controller.renderOverlays(overlays.isSelected());
            }
        };
        overlays.addActionListener(overlaysListener);
        TableLayout layout = (TableLayout) controls.getLayout();
        int row = layout.getNumRow()-1;
        overlayButtons = new ArrayList<ChannelButton>();
        Iterator i = m.entrySet().iterator();
        Entry entry;
        int index;
        ChannelButton button;
        Color c;
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
        int j = 0;
        int w = 0;
        int h = 0;
        while (i.hasNext()) {
            entry = (Entry) i.next();
            index = (Integer) entry.getKey();
            c = new Color((Integer) entry.getValue());
            button = new ChannelButton(""+j, c, index, true);
            button.setOverlay(true);
            button.addPropertyChangeListener(controller);
            overlayButtons.add(button);
            p.add(button);
            p.add(Box.createRigidArea(VBOX));
            w = button.getPreferredSize().width;
            h = button.getPreferredSize().height;
            j++;
        }

        JPanel pane = new JPanel();
        double size[][] = {{TableLayout.PREFERRED}, 
                {TableLayout.PREFERRED, TableLayout.PREFERRED}};
        pane.setLayout(new TableLayout(size));
        pane.add(overlays, "0, 0, CENTER, CENTER");
        if (overlayButtons.size() > ImViewer.MAX_OVERLAYS) {
            JScrollPane sp = new JScrollPane(p);
            Dimension d = new Dimension(2*w, h*ImViewer.MAX_OVERLAYS);
            sp.setPreferredSize(d);
            pane.add(sp, "0, 1, CENTER, CENTER");
        } else pane.add(p, "0, 1, CENTER, CENTER");

        controls.add(pane, "0, "+row+", LEFT, CENTER");
    }

    /**
     * Turns on or off the selected overlay.
     * 
     * @param index     The index of the overlay.
     * @param selected  Pass <code>true</code> to turn the overlay on,
     * 					<code>false</code> to turn it off.
     */
    void renderOverlays(int index, boolean selected)
    {
        if (index == -1) return;
        Iterator<ChannelButton> i = overlayButtons.iterator();
        ChannelButton b;
        while (i.hasNext()) {
            b = i.next();
            if (b.getChannelIndex() == index) {
                b.setSelected(selected);
                break;
            }
        }
    }

    /**
     * Returns the selected overlays if displayed otherwise returns 
     * <code>null</code>.
     * 
     * @return See above.
     */
    Map<Long, Integer> getSelectedOverlays()
    {
        Map<Long, Integer> m = new HashMap<Long, Integer>();
        Iterator<ChannelButton> i = overlayButtons.iterator();
        ChannelButton b;
        Color c;
        while (i.hasNext()) {
            b = i.next();
            c = b.getColor();
            if (b.isSelected() && c != null) {
                m.put((long) b.getChannelIndex(), c.getRGB() & 0x00ffffff);
            }
        }
        return m;
    }

    /**
     * Returns <code>true</code> if the overlays are turned on,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isOverlayActive() { return overlays.isSelected(); }

    /**
     * Returns the color model of the pane currently selected.
     * 
     * @return See above.
     */
    String getSelectedPaneColorModel()
    {
        switch (view.getTabbedIndex()) {
        case ImViewer.GRID_INDEX:
            return getColorModelFromIcon(colorModelButtonGrid);
        case ImViewer.PROJECTION_INDEX:
            return getColorModelFromIcon(colorModelButtonProjection);
        case ImViewer.VIEW_INDEX:
        default:
            return getColorModelFromIcon(colorModelButton);
        }
    }

    /**
     * Sets the tool tip of the specified slider.
     * 
     * @param start The start z-section.
     * @param end The end z-section.
     */
    void setRangeSliderToolTip(int start, int end)
    {
        String tip = "Selected Range Z="+(start+1)+"-"+(end+1)+
                "/"+(model.getMaxZ()+1);
        projectionRange.setToolTipText(tip);
    }

    /**
     * Updates the component displaying the channels' details after update.
     */
    void onChannelUpdated()
    {
        Dimension dimMax = updateText(channelButtons);
        updateText(channelButtonsGrid);
        updateText(channelButtonsProjection);
        resizeChannelButton(dimMax, channelButtons);
        resizeChannelButton(dimMax, channelButtonsGrid);
        resizeChannelButton(dimMax, channelButtonsProjection);
        repaint();
    }

    /**
     * Updates the text of the specified components. Returns the maximum
     * size of the components.
     *
     * @param buttons The components to update.
     * @return See above
     */
    private Dimension updateText(List<ChannelButton> buttons)
    {
        Iterator<ChannelButton> i = buttons.iterator();
        ChannelData data;
        ChannelButton cb;
        Dimension d;
        Dimension dimMax = new Dimension(0, 0);
        while (i.hasNext()) {
            cb = i.next();
            data = model.getChannelData(cb.getChannelIndex());
            cb.setText(data.getChannelLabeling());
            d = cb.getPreferredSize();
            if (d.width > dimMax.width) 
                dimMax = new Dimension(d.width, d.height);
        }
        return dimMax;
    }

    /**
     * Resizes the buttons after changing the text.
     * 
     * @param d The dimension to set.
     * @param buttons The buttons to handle.
     */
    private void resizeChannelButton(Dimension d, List<ChannelButton> buttons)
    {
        Iterator<ChannelButton> j = buttons.iterator();
        while (j.hasNext())
            j.next().setPreferredSize(d);
        repaint();
    }
    
    /**
     * Reacts to the selection of an item in the projection box
     * @see ActionListener#actionPerformed(ActionEvent)
     */
    public void actionPerformed(ActionEvent e)
    {
        int index = Integer.parseInt(e.getActionCommand());
        if (index == FREQUENCY) {
            JComponent comp = projectionFrequency.getEditor();
            if (comp instanceof JSpinner.NumberEditor) {
                JFormattedTextField field =
                        ((JSpinner.NumberEditor) comp).getTextField();
                String value = field.getText();
                int v = -1;
                try {
                    v = Integer.parseInt(value);
                } catch (Exception ex) {}
                if (v == -1 || v > model.getMaxZ() || v < 1) return;
                projectionFrequency.setValue(v);
            }
        }
        controller.setProjectionRange(true);
    }

    /**
     * Reacts to selection of a new plane.
     * @see ChangeListener#stateChanged(ChangeEvent)
     */
    public void stateChanged(ChangeEvent e)
    {
        Object object = e.getSource();
        if (object instanceof JSlider) {
            if (object.equals(gridRatioSlider)) {
                double r = (double) gridRatioSlider.getValue()/10;
                controller.setGridMagnificationFactor(r);
                return;
            } else if (object.equals(ratioSlider)) {
                if (model.isBigImage()) {
                    if (!ratioSlider.isDragging())
                        controller.setZoomFactor(ratioSlider.getValue());
                } else {
                    controller.setZoomFactor(ratioSlider.getValue());
                }
            } else if (object.equals(projectionRatioSlider)) {
                controller.setZoomFactor(projectionRatioSlider.getValue());
            }
            if (object.equals(zSlider) || object.equals(tSlider))
                setSelectedXYPlane(zSlider.getValue(), tSlider.getValue());
            else if (object.equals(lifetimeSlider)) {
                controller.setSelectedXYPlane(model.getDefaultZ(),
                        model.getRealSelectedT(), lifetimeSlider.getValue());
            } else if (object.equals(lifetimeSliderGrid)) {
                    controller.setSelectedXYPlane(model.getDefaultZ(),
                            model.getRealSelectedT(),
                            lifetimeSliderGrid.getValue());
            } else if (object.equals(zSliderGrid) || object.equals(tSliderGrid))
                setSelectedXYPlane(zSliderGrid.getValue(),
                        tSliderGrid.getValue());
            else if (object.equals(tSliderProjection)) {
                //Only if knob is released.
                if (!tSliderProjection.getValueIsAdjusting()) {
                    try {
                        setSelectedXYPlane(-1, tSliderProjection.getValue());
                        controller.setProjectionRange(true);
                    } catch (Exception ex) {}
                }
            } 
        } else if (object.equals(projectionFrequency))
            controller.setProjectionRange(true);
    }

    /**
     * Reacts to wheels moved event related to the {@link #zSlider},
     * {@link #tSlider}, {@link #zSliderGrid} and {@link #zSliderGrid}.
     * @see MouseWheelListener#mouseWheelMoved(MouseWheelEvent)
     */
    public void mouseWheelMoved(MouseWheelEvent e)
    {
        Object source = e.getSource();
        if (source == zSlider && zSlider.isEnabled()) mouseWheelMovedZ(e);
        else if (source == tSlider && tSlider.isEnabled())
            mouseWheelMovedT(e);
        else if (source == zSliderGrid && zSliderGrid.isEnabled())
            mouseWheelMovedZ(e);
        else if (source == tSliderGrid && tSliderGrid.isEnabled())
            mouseWheelMovedT(e);
        else if (source == lifetimeSlider && lifetimeSlider.isEnabled())
            mouseWheelMovedLifetime(e);
        else if (source == lifetimeSliderGrid && lifetimeSliderGrid.isEnabled())
            mouseWheelMovedLifetime(e);
    }

    /**
     * Notifies that the projection range has been modified.
     * @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
        String name = evt.getPropertyName();
        if (TwoKnobsSlider.RIGHT_MOVED_PROPERTY.equals(name) ||
                TwoKnobsSlider.LEFT_MOVED_PROPERTY.equals(name))
            controller.setProjectionRange(false);
        else if (TwoKnobsSlider.KNOB_RELEASED_PROPERTY.equals(name))
            controller.setProjectionRange(true);
        else if (OneKnobSlider.ONE_KNOB_RELEASED_PROPERTY.equals(name)) {
            controller.setZoomFactor(ratioSlider.getValue());
        }
    }

}
