/* *------------------------------------------------------------------------------ * Copyright (C) 2006-2008 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.dataBrowser.view; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.Timer; import org.openmicroscopy.shoola.agents.dataBrowser.Colors; import org.openmicroscopy.shoola.agents.dataBrowser.IconManager; import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageDisplay; import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageNode; import org.openmicroscopy.shoola.agents.dataBrowser.browser.Thumbnail; import org.openmicroscopy.shoola.agents.util.ui.RollOverThumbnailManager; import org.openmicroscopy.shoola.util.ui.UIUtilities; /** * Dialog displaying the slideshow. * * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author Donald MacDonald      * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a> * @version 3.0 * <small> * (<b>Internal version:</b> $Revision: $Date: $) * </small> * @since OME3.0 */ class SlideShowView extends JDialog implements ActionListener, MouseListener, PropertyChangeListener { /** Bound property indicating to close the slide view. */ static final String CLOSE_SLIDE_VIEW_PROPERTY = "closeSlideView"; /** The delay between two images for medium speed. */ private static final int DELAY_MEDIUM = 2000; /** The delay between two images for slow speed. */ private static final int DELAY_SLOW = 3000; /** The delay between two images for fast speed. */ private static final int DELAY_FAST = 1000; /** ID indicating to play the show at a slow speed. */ private static final int SLOW_SPEED = 0; /** ID indicating to play the show at a medium speed. */ private static final int MEDIUM_SPEED = 1; /** ID indicating to play the show at a fast speed. */ private static final int FAST_SPEED = 2; /** ID indicating to modify the speed of play. */ private static final int SPEED = 10; /** ID indicating to pause the show. */ private static final int PAUSE = 11; /** ID indicating to play the show forward. */ private static final int PLAY_FORWARD = 12; /** ID indicating to play the show forward. */ private static final int PLAY_BACKWARD = 13; /** ID indicating to go to the next image. */ private static final int PREVIOUS = 14; /** ID indicating to go to the previous image. */ private static final int NEXT = 15; /** Indicates the <i>Start</i> state of the timer. */ public static final int START = 0; /** Indicates the <i>Stop</i> state of the timer. */ public static final int STOP = 1; /** The speed options. */ private static final String[] SPEEDS; static { SPEEDS = new String[3]; SPEEDS[SLOW_SPEED] = "slow"; SPEEDS[MEDIUM_SPEED] = "medium"; SPEEDS[FAST_SPEED] = "fast"; } /** The collection of nodes to show. */ private List<ImageNode> nodes; /** The button to go to the next image. */ private JButton next; /** The button to go to the previous image. */ private JButton previous; /** The button to pause the movie. */ private JButton pause; /** The button to show the images forward. */ private JToggleButton forwardPlay; /** The button to show the images backward. */ private JToggleButton backwardPlay; /** Label indicating to show images at a slow speed. */ private JComboBox speeds; /** The currently selected node. */ private int selectedNodeIndex; /** The timer controlling the display. */ private Timer timer; /** The state of the timer. */ private int state; /** The delay used by the timer. */ private int delay; /** Either {@link #PLAY_FORWARD} or {@link #PLAY_BACKWARD}. */ private int playingIndex; /** The Horizontal split pane. */ private JSplitPane pane; /** The scrollpane hosting the nodes. */ private JScrollPane nodePane; /** The component hosting the main image. */ private SlideShowUI uiDelegate; /** The map used to determine the index of the selected node. */ private Map<ImageNode, Integer> nodesMap; /** * Finds the first {@link ImageDisplay} in <code>x</code>'s containement * hierarchy. * * @param x A component. * @return The parent {@link ImageDisplay} or <code>null</code> if none * was found. */ private ImageDisplay findParentDisplay(Object x) { while (true) { if (x instanceof ImageDisplay) return (ImageDisplay) x; if (x instanceof JComponent) x = ((JComponent) x).getParent(); else break; } return null; } /** Initializes the components. */ private void initComponents() { nodesMap = new HashMap<ImageNode, Integer>(); IconManager icons = IconManager.getInstance(); previous = new JButton(icons.getIcon(IconManager.PREVIOUS)); previous.setActionCommand(""+PREVIOUS); previous.addActionListener(this); UIUtilities.unifiedButtonLookAndFeel(previous); next = new JButton(icons.getIcon(IconManager.NEXT)); next.setActionCommand(""+NEXT); next.addActionListener(this); UIUtilities.unifiedButtonLookAndFeel(next); pause = new JButton(icons.getIcon(IconManager.PAUSE)); pause.setActionCommand(""+PAUSE); pause.addActionListener(this); pause.setEnabled(false); forwardPlay = new JToggleButton(icons.getIcon(IconManager.FORWARD)); forwardPlay.setActionCommand(""+PLAY_FORWARD); forwardPlay.addActionListener(this); forwardPlay.setEnabled(false); backwardPlay = new JToggleButton(icons.getIcon(IconManager.BACKWARD)); backwardPlay.setActionCommand(""+PLAY_BACKWARD); backwardPlay.addActionListener(this); backwardPlay.setEnabled(false); speeds = new JComboBox(SPEEDS); speeds.setSelectedIndex(MEDIUM_SPEED); speeds.addActionListener(this); speeds.setActionCommand(""+SPEED); delay = DELAY_MEDIUM; timer = new Timer(delay, this); timer.setInitialDelay(delay/10); timer.setCoalesce(true); state = STOP; playingIndex = -1; selectedNodeIndex = 0; uiDelegate = new SlideShowUI(this); pane = new JSplitPane(); pane.setOrientation(JSplitPane.VERTICAL_SPLIT); pane.setOneTouchExpandable(true); pane.setContinuousLayout(true); pane.setTopComponent(uiDelegate); pane.setResizeWeight(1); pane.setBackground(UIUtilities.BACKGROUND); } /** * Stops the timer and fires a property indicating that the slide * show is closed. */ private void attachWindowListener() { setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { /** * Stops the timer when closing the dialog. * @see WindowAdapter#windowClosing(WindowEvent) */ public void windowClosing(WindowEvent e) { stop(); firePropertyChange(CLOSE_SLIDE_VIEW_PROPERTY, Boolean.FALSE, Boolean.TRUE); } }); } /** Sets the speed of play. */ private void setSpeed() { switch (speeds.getSelectedIndex()) { case SLOW_SPEED: delay = DELAY_SLOW; break; case MEDIUM_SPEED: delay = DELAY_MEDIUM; break; case FAST_SPEED: delay = DELAY_FAST; break; } timer.setDelay(delay); timer.setInitialDelay(delay); } /** * Plays the slide show. * * @param index The playing index, * either {@link #PLAY_FORWARD} or {@link #PLAY_BACKWARD}. */ private void play(int index) { if (index == playingIndex) return; if (state == START) stop(); playingIndex = index; forwardPlay.setSelected(playingIndex == PLAY_FORWARD); backwardPlay.setSelected(playingIndex == PLAY_BACKWARD); state = START; timer.start(); } /** Plays the slide show. */ private void stop() { if (timer == null) return; if (state == STOP) return; playingIndex = -1; state = STOP; forwardPlay.setSelected(false); backwardPlay.setSelected(false); timer.stop(); } /** * Sets the color of the specified node. * * @param node The selected node. */ private void setNodeColor(ImageNode node) { Iterator<ImageNode> i = nodes.iterator(); Colors colors = Colors.getInstance(); ImageNode n; while (i.hasNext()) { n = i.next(); if (n != node) { n.setHighlight(colors.getColor(Colors.TITLE_BAR)); n.repaint(); } } node.setHighlight(colors.getColor(Colors.TITLE_BAR_HIGHLIGHT)); node.repaint(); } /** Paints the selected images on the canvas. */ private void paintImage() { ImageNode node = nodes.get(selectedNodeIndex); if (node == null) return; setTitle("Slideshow "+node.toString()); uiDelegate.paintImage(node.getThumbnail().getFullSizeImage()); setNodeColor(node); Rectangle viewRect = nodePane.getViewport().getViewRect(); Rectangle bounds = node.getBounds(); if (!viewRect.contains(bounds)) { nodePane.getVerticalScrollBar().setValue(bounds.y); nodePane.getHorizontalScrollBar().setValue(bounds.x); } } /** * Builds the UI component hosting the controls. * * @return See above. */ private JPanel buildToolBar() { JPanel p = new JPanel(); p.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); JToolBar bar = new JToolBar(); bar.setFloatable(false); bar.setBorder(null); bar.add(backwardPlay); bar.add(pause); bar.add(forwardPlay); p.add(bar); JPanel speed = new JPanel(); speed.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0)); speed.add(new JLabel("Speed:")); speed.add(speeds); p.add(speed); return p; } /** * Builds the bottom tool bar. * * @return See above. */ private JComponent buildBottomBar() { JToolBar bar = new JToolBar(); bar.setFloatable(false); bar.setBorder(null); bar.add(previous); bar.add(Box.createHorizontalStrut(5)); bar.add(next); return UIUtilities.buildComponentPanelCenter(bar); } /** * Builds the component hosting the nodes. * * @return See above. */ private JComponent buildNodesPane() { JPanel p = new JPanel(); p.setBackground(UIUtilities.BACKGROUND); Iterator<ImageNode> i = nodes.iterator(); ImageNode node; int index = 0; while (i.hasNext()) { node = i.next(); node.setListenToBorder(false); node.addListenerToComponents(this); nodesMap.put(node, index); p.add(node); index++; } nodePane = new JScrollPane(p); return nodePane; } /** Builds and lays out the UI. */ private void buildGUI() { Container c = getContentPane(); pane.setBottomComponent(buildNodesPane()); c.add(pane, BorderLayout.CENTER); JPanel p = new JPanel(); p.setLayout(new BorderLayout()); JPanel bar = buildToolBar(); p.add(bar, BorderLayout.LINE_START); p.add(buildBottomBar(), BorderLayout.CENTER); Dimension d = bar.getPreferredSize(); p.add(Box.createHorizontalStrut(d.width), BorderLayout.LINE_END); c.add(p, BorderLayout.SOUTH); } /** * Creates a new instance. * * @param parent The parent of this dialog. * @param nodes The collection of nodes to display. */ SlideShowView(JFrame parent, List<ImageNode> nodes) { super(parent); setTitle("Slideshow"); this.nodes = nodes; initComponents(); buildGUI(); attachWindowListener(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int width = 6*(screenSize.width/10); int height = 6*(screenSize.height/10); setSize(width, height); } /** * Sets the value of the progress bar. * * @param hide Pass <code>true</code> to remove the progress bar, * and replace it by the canvas. * @param value The value to set. */ void setProgress(boolean hide, int value) { uiDelegate.setProgress(hide, value); if (hide) { pause.setEnabled(true); forwardPlay.setEnabled(true); backwardPlay.setEnabled(true); paintImage(); } pane.revalidate(); pane.repaint(); } /** Closes and disposes of the dialog. */ void close() { setVisible(false); dispose(); } /** * Plays the slide show. * @see ActionListener#actionPerformed(ActionEvent) */ public void actionPerformed(ActionEvent e) { if (e.getSource() == timer) { int size = nodes.size(); switch (playingIndex) { case PLAY_FORWARD: selectedNodeIndex++; if (selectedNodeIndex == size) selectedNodeIndex = 0; break; case PLAY_BACKWARD: if (selectedNodeIndex == 0) selectedNodeIndex = size; selectedNodeIndex--; break; } paintImage(); return; } int index = Integer.parseInt(e.getActionCommand()); switch (index) { case PAUSE: stop(); break; case PLAY_BACKWARD: play(PLAY_BACKWARD); break; case PLAY_FORWARD: play(PLAY_FORWARD); break; case SPEED: setSpeed(); break; case NEXT: selectedNodeIndex++; if (selectedNodeIndex == nodes.size()) selectedNodeIndex = 0; paintImage(); break; case PREVIOUS: if (selectedNodeIndex == 0) selectedNodeIndex = nodes.size(); selectedNodeIndex--; paintImage(); } } /** * Sets the selected node. * @see PropertyChangeListener#propertyChange(PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); if (SlideShowCanvas.SELECT_NEXT_PROPERTY.equals(name)) { selectedNodeIndex++; if (selectedNodeIndex == nodes.size()) selectedNodeIndex = 0; paintImage(); } } /** * Zooms the image or remove the zooming window from the display. * @see MouseListener#mouseEntered(MouseEvent) */ public void mouseEntered(MouseEvent e) { Object src = e.getSource(); ImageDisplay n = findParentDisplay(src); if (n != null && n instanceof ImageNode) { ImageNode node = (ImageNode) n; Thumbnail prv = node.getThumbnail(); BufferedImage full = prv.getFullScaleThumb(); if (prv.getScalingFactor() == Thumbnail.MAX_SCALING_FACTOR) full = prv.getZoomedFullScaleThumb(); RollOverThumbnailManager.rollOverDisplay(full, node.getBounds(), node.getLocationOnScreen(), node.toString()); } else RollOverThumbnailManager.stopOverDisplay(); } /** * Removes the zooming window from the display. * @see MouseListener#mouseExited(MouseEvent) */ public void mouseExited(MouseEvent e) { RollOverThumbnailManager.stopOverDisplay(); } /** * Sets the selected image. * @see MouseListener#mousePressed(MouseEvent) */ public void mousePressed(MouseEvent e) { ImageDisplay node = findParentDisplay(e.getSource()); if (node == null) return; Integer value = nodesMap.get(node); if (value != null) { selectedNodeIndex = value.intValue(); paintImage(); } } /** * Views the image if the user clicks twice on the thumbnail. * @see MouseListener#mouseReleased(MouseEvent) */ public void mouseReleased(MouseEvent e) { Object src = e.getSource(); /* ImageDisplay d = findParentDisplay(src); if (d instanceof ImageNode && !(d.getTitleBar() == src) && e.getClickCount() == 2) { EventBus bus = DataBrowserAgent.getRegistry().getEventBus(); bus.post(new ViewImage(new ViewImageObject( (ImageData) d.getHierarchyObject()), null)); }*/ } /** * Required by the {@link MouseListener} I/F but no-op implementation * in our case. * @see MouseListener#mouseClicked(MouseEvent) */ public void mouseClicked(MouseEvent me) {} }