/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2015 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.util.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
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.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.env.data.model.MovieExportParam;
import org.openmicroscopy.shoola.util.ui.ColorListRenderer;
import org.openmicroscopy.shoola.util.ui.IconManager;
import org.openmicroscopy.shoola.util.ui.NumericalTextField;
import org.openmicroscopy.shoola.util.ui.TitlePanel;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.slider.TextualTwoKnobsSlider;
import omero.gateway.model.ChannelData;
/**
* Modal dialog displaying option to export the rendered image.
*
* @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
* @since 3.0-Beta4
*/
public class MovieExportDialog
extends JDialog
implements ActionListener, DocumentListener
{
/** Bound property indicating to create a movie. */
public static final String CREATE_MOVIE_PROPERTY = "createMovie";
/** Action id indicating to close the dialog. */
public static final int CLOSE = 0;
/** Action id indicating to create a movie. */
public static final int SAVE = 1;
/** Action id indicating to allow the modification of the scale bar. */
private static final int SCALE_BAR = 2;
/** Action id indicating to allow the modification of the scale bar. */
private static final int Z_INTERVAL = 3;
/** Action id indicating to allow the modification of the scale bar. */
private static final int T_INTERVAL = 4;
/** Action id indicating to view the script used. */
public static final int VIEW = 5;
/** The title of the dialog. */
private static final String TITLE = "Movie Creation";
/** The default value for the scale bar. */
private static final int DEFAULT_SCALE = 5;
/** Button to close the dialog. */
private JButton closeButton;
/** Button to close the dialog. */
private JButton saveButton;
/** The name of the file. */
private JTextField nameField;
/** The supported movie formats. */
private JComboBox formats;
/** The supported value of the scale bar. */
private NumericalTextField scaleBar;
/** Add a scale bar if selected. */
private JCheckBox showScaleBar;
/** Creates a movie across time. */
private JCheckBox timeInterval;
/** Creates a movie across z-section. */
private JCheckBox zInterval;
/** Displays or not the real time. */
private JCheckBox labelVisible;
/** The selected color for scale bar. */
private JComboBox colorBox;
/** To specify the movie play-back rate in frames per second. */
private JSpinner fps;
/** Component to select the time interval. */
private TextualTwoKnobsSlider timeRange;
/** Component to select the z-section interval. */
private TextualTwoKnobsSlider zRange;
/** Option chosen by the user. */
private int option;
/** The parameters to set. */
private MovieExportParam param;
/** Component used to set the default z-section. */
private JSpinner zSpinner;
/** Component used to set the default z-section. */
private JSpinner tSpinner;
/** The number of z-sections. */
private int maxZ;
/** The number of time-points. */
private int maxT;
/** The collection of channels.*/
private Map<Object, JComponent> buttons;
/** Button to view the script.*/
private JButton viewScript;
/**
* Creates the components composing the display.
*
* @param name The default name of the file.
* @param defaultZ The default z-section.
* @param defaultT The default time-point.
* @param channels The collection of channels.
*/
private void initComponents(String name, int defaultZ, int defaultT, Object
channels)
{
buttons = new LinkedHashMap<Object, JComponent>();
if (channels instanceof Map) {
Entry entry;
Iterator i = ((Map) channels).entrySet().iterator();
ChannelData data;
ChannelButton button;
while (i.hasNext()) {
entry = (Entry) i.next();
data = (ChannelData) entry.getKey();
button = new ChannelButton(data.getChannelLabeling(),
(Color) entry.getValue(), data.getIndex());
button.setSelected(true);
button.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
String n = evt.getPropertyName();
if (ChannelButton.CHANNEL_SELECTED_PROPERTY.equals(n)) {
Map m = (Map) evt.getNewValue();
ChannelButton button =
(ChannelButton) evt.getSource();
Boolean b = (Boolean) m.get(
button.getChannelIndex());
if (b != null)
button.setSelected(b.booleanValue());
}
}
});
buttons.put(data, button);
}
} else {
JCheckBox box;
int maxC = (Integer) channels;
for (int i = 0; i < maxC; i++) {
box = new JCheckBox(""+i);
box.setSelected(true);
buttons.put(i, box);
}
}
viewScript = new JButton("View script");
viewScript.setActionCommand(""+VIEW);
viewScript.addActionListener(this);
closeButton = new JButton("Cancel");
closeButton.setToolTipText(UIUtilities.formatToolTipText(
"Close the window."));
closeButton.setActionCommand(""+CLOSE);
closeButton.addActionListener(this);
saveButton = new JButton("Create");
saveButton.setEnabled(false);
saveButton.setToolTipText(UIUtilities.formatToolTipText(
"Create a movie."));
saveButton.setActionCommand(""+SAVE);
saveButton.addActionListener(this);
nameField = new JTextField();
String s = UIUtilities.removeFileExtension(name);
if (s != null) {
nameField.setText(s);
saveButton.setEnabled(true);
}
nameField.getDocument().addDocumentListener(this);
Map<Integer, String> map = MovieExportParam.FORMATS;
String[] f = new String[map.size()];
Entry entry;
Iterator i = map.entrySet().iterator();
int index = 0;
int key;
String value;
boolean isMac = UIUtilities.isMacOS();
while (i.hasNext()) {
entry = (Entry) i.next();
key = (Integer) entry.getKey();
f[key] = (String) entry.getValue();
if (isMac) {
if (key == MovieExportParam.QT) index = key;
}
}
formats = new JComboBox(f);
formats.setSelectedIndex(index);
timeRange = new TextualTwoKnobsSlider(1, maxT, 1, maxT);
timeRange.getSlider().setOverlap(true);
timeRange.layoutComponents();
timeRange.setEnabled(maxT > 1);
zRange = new TextualTwoKnobsSlider(1, maxZ, 1, maxZ);
zRange.getSlider().setOverlap(true);
zRange.layoutComponents();
zRange.setEnabled(false);
timeInterval = new JCheckBox("Time Interval");
timeInterval.setFont(timeInterval.getFont().deriveFont(Font.BOLD));
zInterval = new JCheckBox("Z-section Interval");
zInterval.setFont(zInterval.getFont().deriveFont(Font.BOLD));
if (maxT > 1) timeInterval.setSelected(true);
else timeInterval.setEnabled(false);
if (maxZ > 1 && maxT <= 1) {
zInterval.setEnabled(false);
zInterval.setSelected(true);
zRange.setEnabled(true);
}
SpinnerModel sp = new SpinnerNumberModel(defaultZ, 1, maxZ, 1);
zSpinner = new JSpinner(sp);
sp = new SpinnerNumberModel(defaultT, 1, maxT, 1);
tSpinner = new JSpinner(sp);
if (maxT <= 1) tSpinner.setEnabled(false);
if (maxZ <= 1 || zInterval.isSelected()) zSpinner.setEnabled(false);
zInterval.addActionListener(this);
zInterval.setActionCommand(""+Z_INTERVAL);
timeInterval.addActionListener(this);
timeInterval.setActionCommand(""+T_INTERVAL);
showScaleBar = new JCheckBox("Scale Bar");
showScaleBar.setFont(showScaleBar.getFont().deriveFont(Font.BOLD));
showScaleBar.setSelected(true);
showScaleBar.setActionCommand(""+SCALE_BAR);
showScaleBar.addActionListener(this);
scaleBar = new NumericalTextField();
scaleBar.setText(""+DEFAULT_SCALE);
fps = new JSpinner();
fps.setValue(MovieExportParam.DEFAULT_FPS);
labelVisible = new JCheckBox("Show Labels");
getRootPane().setDefaultButton(saveButton);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) { close(); }
});
colorBox = new JComboBox();
Map<Color, String> colors = EditorUtil.COLORS_BAR;
Object[][] cols = new Object[colors.size()][2];
index = 0;
i = colors.entrySet().iterator();
while (i.hasNext()) {
entry = (Entry) i.next();
cols[index] = new Object[]{entry.getKey(), entry.getValue()};
index++;
}
colorBox.setModel(new DefaultComboBoxModel(cols));
colorBox.setRenderer(new ColorListRenderer());
colorBox.setSelectedIndex(cols.length-1);
}
/** Enables or not the controls. */
private void enabledControls()
{
saveButton.setEnabled(true);
if (zInterval.isSelected()) {
zRange.setEnabled(maxZ > 1);
zSpinner.setEnabled(false);
} else {
zRange.setEnabled(false);
zSpinner.setEnabled(maxZ > 1);
}
if (timeInterval.isSelected()) {
timeRange.setEnabled(maxT > 1);
tSpinner.setEnabled(false);
} else {
timeRange.setEnabled(false);
tSpinner.setEnabled(maxT > 1);
}
}
/**
* Builds and lays out the control.
*
* @return See above.
*/
private JPanel buildToolBar()
{
JPanel bar = new JPanel();
bar.add(closeButton);
bar.add(Box.createHorizontalStrut(5));
bar.add(saveButton);
bar.add(Box.createHorizontalStrut(20));
JPanel p = new JPanel();
p.add(viewScript);
JPanel all = new JPanel();
all.setLayout(new BoxLayout(all, BoxLayout.X_AXIS));
all.add(UIUtilities.buildComponentPanel(p));
all.add(UIUtilities.buildComponentPanelRight(bar));
all.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
return all;
}
/** Builds and lays out the UI. */
private void buildGUI()
{
IconManager icons = IconManager.getInstance();
TitlePanel tp = new TitlePanel(TITLE, "Create a movie.",
"The movie will be saved to the server and " +
"attached to the image.", icons.getIcon(IconManager.MOVIE_48));
Container c = getContentPane();
c.setLayout(new BorderLayout(5, 5));
c.add(tp, BorderLayout.NORTH);
c.add(buildBody(), BorderLayout.CENTER);
c.add(buildToolBar(), BorderLayout.SOUTH);
}
/**
* Builds the main component.
*
* @return See above.
*/
private JPanel buildBody()
{
JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
JPanel p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT));
p.add(UIUtilities.setTextFont("Name"));
p.add(nameField);
content.add(p);
p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT));
p.add(UIUtilities.setTextFont("Format"));
p.add(formats);
p.add(UIUtilities.setTextFont("Frame Rate"));
p.add(fps);
content.add(p);
p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT));
p.add(UIUtilities.setTextFont("Channels selection"));
Iterator<JComponent> k = buttons.values().iterator();
while (k.hasNext()) {
p.add(k.next());
}
content.add(p);
p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT));
p.add(timeInterval);
p.add(UIUtilities.buildComponentPanel(timeRange));
content.add(p);
p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT));
p.add(zInterval);
p.add(zRange);
content.add(p);
p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT));
p.add(showScaleBar);
p.add(scaleBar);
p.add(new JLabel("microns"));
p.add(colorBox);
content.add(p);
content.add(UIUtilities.buildComponentPanel(labelVisible));
content.add(Box.createVerticalStrut(5));
return content;
}
/** Closes the dialog. */
private void close()
{
option = CLOSE;
setVisible(false);
dispose();
}
/** Collects the parameters to create a movie. */
private void save()
{
String name = nameField.getText();
int format = formats.getSelectedIndex();
int scale = -1;
int f = (Integer) fps.getValue();
if (showScaleBar.isSelected()) {
Number n = scaleBar.getValueAsNumber();
if (n != null) scale = n.intValue();
}
int type = MovieExportParam.ZT_MOVIE;;
if (!timeInterval.isSelected() && zInterval.isSelected())
type = MovieExportParam.Z_MOVIE;
else if (timeInterval.isSelected() && !zInterval.isSelected())
type = MovieExportParam.T_MOVIE;
param = new MovieExportParam(name, f, format, scale, type);
if (type == MovieExportParam.T_MOVIE ||
type == MovieExportParam.ZT_MOVIE)
param.setTimeInterval((int) timeRange.getStartValue()-1,
(int) timeRange.getEndValue()-1);
if (type == MovieExportParam.Z_MOVIE ||
type == MovieExportParam.ZT_MOVIE)
param.setZsectionInterval((int) zRange.getStartValue()-1,
(int) zRange.getEndValue()-1);
param.setLabelVisible(labelVisible.isSelected());
if (!timeInterval.isSelected()) {
int t = (Integer) tSpinner.getValue()-1;
param.setTimeInterval(t, t);
}
if (!zInterval.isSelected()) {
int z = (Integer) zSpinner.getValue()-1;
param.setZsectionInterval(z, z);
}
int index = colorBox.getSelectedIndex();
Entry entry;
Map<Color, String> m = EditorUtil.COLORS_BAR;
Iterator i = m.entrySet().iterator();
int j = 0;
String c = null;
while (i.hasNext()) {
entry = (Entry) i.next();
if (j == index) c = (String) entry.getValue();
j++;
}
param.setColor(c);
//Channels
List<Integer> channels = new ArrayList<Integer>();
i = buttons.entrySet().iterator();
JComponent comp;
ChannelButton cb;
while (i.hasNext()) {
entry = (Entry) i.next();
comp = (JComponent) entry.getValue();
if (comp instanceof JCheckBox) {
if (((JCheckBox) comp).isSelected()) {
channels.add((Integer) entry.getKey());
}
} else if (comp instanceof ChannelButton) {
cb = (ChannelButton) comp;
if (cb.isSelected()) {
channels.add(cb.getChannelIndex());
}
}
}
param.setChannels(channels);
option = SAVE;
close();
firePropertyChange(CREATE_MOVIE_PROPERTY, null, param);
}
/**
* Sets the enabled flag of the {@link #saveButton} depending on
* the value to the name field.
*/
private void handleText()
{
String text = nameField.getText();
saveButton.setEnabled(!(text == null || text.trim().length() == 0));
}
/**
* Returns <code>true</code> if the colors are the same, <code>false</code>
* otherwise.
*
* @param c1 The color to handle.
* @param c2 The color to handle.
* @return See above.
*/
private boolean isSameColor(Color c1, Color c2)
{
if (c1.getRed() != c2.getRed()) return false;
if (c1.getGreen() != c2.getGreen()) return false;
if (c1.getBlue() != c2.getBlue()) return false;
return true;
}
/**
* Creates a new instance.
*
* @param owner The owner of the frame.
* @param name The name of the movie.
* @param maxT The maximum number of time points.
* @param maxZ The maximum number of z-sections.
* @param defaultZ The default z-section.
* @param defaultT The default timepoint.
* @param channels The channels information or the number of channels.
*/
public MovieExportDialog(JFrame owner, String name, int maxT, int maxZ,
int defaultZ, int defaultT, Object channels)
{
super(owner);
setModal(true);
param = null;
this.maxT = maxT;
this.maxZ = maxZ;
initComponents(name, defaultZ, defaultT, channels);
buildGUI();
pack();
}
/**
* Turns off controls if the binary data are not available.
*
* @param available Pass <code>false</code> to turn off the control.
*/
public void setBinaryAvailable(boolean available)
{
saveButton.setEnabled(available);
}
/**
* Sets the default value of the scale bar.
*
* @param value The numerical value.
* @param color The selected color.
*/
public void setScaleBarDefault(int value, Color color)
{
if (color != null) {
Map<Color, String> m = EditorUtil.COLORS_BAR;
int index = 0;
Entry entry;
Iterator i = m.entrySet().iterator();
Color c;
while (i.hasNext()) {
entry = (Entry) i.next();
c = (Color) entry.getKey();
if (isSameColor(c, color))
break;
index++;
}
colorBox.setSelectedIndex(index);
}
if (value > 0)
scaleBar.setText(""+value);
}
/**
* Shows the dialog and returns the option selected by the user.
*
* @return The option selected by the user.
*/
public int showDialog()
{
setLocation(getParent().getLocation());
setVisible(true);
return option;
}
/**
* Centers and shows the dialog. Returns the option selected by the user.
*
* @return The option selected by the user.
*/
public int centerDialog()
{
UIUtilities.centerAndShow(this);
return option;
}
/**
* Returns the parameters used to create the movie or <code>null</code>
* if none set.
*
* @return See above.
*/
public MovieExportParam getParameters() { return param; }
/**
* Closes or creates a movie.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent e)
{
int index = Integer.parseInt(e.getActionCommand());
switch (index) {
case CLOSE:
close();
break;
case SAVE:
save();
break;
case SCALE_BAR:
scaleBar.setEnabled(showScaleBar.isSelected());
break;
case T_INTERVAL:
case Z_INTERVAL:
enabledControls();
break;
case VIEW:
firePropertyChange(
ScriptingDialog.VIEW_SELECTED_SCRIPT_PROPERTY, null,
MovieExportParam.MOVIE_SCRIPT);
}
}
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#insertUpdate(DocumentEvent)
*/
public void insertUpdate(DocumentEvent e) { handleText(); }
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#removeUpdate(DocumentEvent)
*/
public void removeUpdate(DocumentEvent e) { handleText(); }
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#changedUpdate(DocumentEvent)
*/
public void changedUpdate(DocumentEvent e) {}
}