/**
* Copyright (C) 2008-2011 Daniel Senff
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package de.danielsenff.imageflow.models.parameter;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Hashtable;
import java.util.Locale;
import javax.swing.BoundedRangeModel;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Creates form elements based on {@link Parameter}
* @author dahie
*
*/
public class ParameterWidgetFactory {
private static final class TextfieldParamChangeListener implements
ParamChangeListener {
private final JTextField component;
private TextfieldParamChangeListener(JTextField component) {
this.component = component;
}
public void parameterChanged(Parameter source) {
if (source.getValue() instanceof String) {
component.setText((String) source.getValue());
} else if (source.getValue() instanceof Integer) {
component.setText(Integer.toString((Integer) source.getValue()));
} else if (source.getValue() instanceof Double) {
Double value = (Double) source.getValue();
DecimalFormat decimal = new DecimalFormat("0.00");
decimal.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
component.setText(decimal.format(value));
}
}
}
private static class CheckBoxParamChangeListener implements
ParamChangeListener {
private JCheckBox component;
private CheckBoxParamChangeListener(JCheckBox component) {
this.component = component;
}
public void parameterChanged(Parameter source) {
if (source.getValue() instanceof Boolean) {
Boolean selected = (Boolean) source.getValue();
component.setSelected(selected);
}
}
}
private static class ComboBoxParamChangeListener implements
ParamChangeListener {
private JComboBox component;
public ComboBoxParamChangeListener(JComboBox component) {
this.component = component;
}
public void parameterChanged(Parameter source) {
if (source instanceof ChoiceParameter) {
this.component.setSelectedIndex(((ChoiceParameter) source).getChoiceIndex());
}
}
}
private static final class SliderParamChangeListener implements
ParamChangeListener {
private final JSlider component;
private SliderParamChangeListener(JSlider component) {
this.component = component;
}
public void parameterChanged(Parameter source) {
if (source.getValue() instanceof Integer) {
component.setValue(Integer.valueOf((Integer) source.getValue()));
} else if (source.getValue() instanceof Double) {
Integer value = (Integer) source.getValue()*100;
DecimalFormat decimal = new DecimalFormat("0.00");
decimal.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
component.setValue(value);
}
}
}
enum FileChooser { OPEN, SAVE}
public static JComponent createForm(final Parameter parameter) {
try {
if (parameter instanceof ChoiceParameter) {
// choice can be displayed as dropdown or radio buttons
if(optionsContainString(parameter, "as", "radio")) {
return createRadios((ChoiceParameter)parameter);
} else
return createComboBox((ChoiceParameter)parameter);
} else if (parameter instanceof IntegerParameter) {
// integer can be displayed as textfield or slider
// TODO add as spinner
if (optionsContainString(parameter, "as", "slider")) {
return createSlider((IntegerParameter)parameter);
} else {
return createTextField(parameter);
}
} else if (parameter instanceof DoubleParameter) {
// integer can be displayed as textfield or slider
// TODO add as spinner
if (optionsContainString(parameter, "as", "slider")) {
return createSlider((DoubleParameter)parameter);
} else {
return createTextField(parameter);
}
} else if (parameter instanceof StringParameter) {
// string can be displayed as textfield
// special treatment for paths
// TODO textarea
if (optionsContainString(parameter, "as", "openfilechooser")) {
return createFileChooser((StringParameter)parameter, FileChooser.OPEN);
} else if (optionsContainString(parameter, "as", "savefilechooser")) {
return createFileChooser((StringParameter)parameter, FileChooser.SAVE);
} else {
return createTextField(parameter);
}
} else if (parameter instanceof BooleanParameter) {
if (optionsContainString(parameter, "as", "openfilechooser")) {
// TODO return createToggleButton((BooleanParameter)parameter);
} else {
return createCheckBox((BooleanParameter)parameter);
}
}
} catch (final Exception e) {
System.err.println("caught error, skip form");
}
return null;
}
/**
* Returns true if the options hash contains the given key with the given value.
* @param parameter
* @param key String key that needs to be included in the options hash
* @param value value the key has to have
* @return
*/
public static boolean optionsContainString(final Parameter parameter,
final String key,
final String value) {
return parameter.getOptions() != null
&& parameter.getOptions().get(key) != null
&& parameter.getOptions().get(key) instanceof String
&& ((String)parameter.getOptions().get(key)).equalsIgnoreCase(value);
}
public static JComponent createSlider(final IntegerParameter parameter) throws IllegalArgumentException {
final JPanel panel = new JPanel();
panel.setOpaque(false);
final JTextField component = new JTextField(parameter.getValue().toString());
component.setEnabled(!parameter.isReadOnly());
component.setColumns(5);
component.setOpaque(false);
parameter.addParamChangeListener(new TextfieldParamChangeListener(component));
final int min = (Integer) parameter.getOptions().get("min");
final int max = (Integer) parameter.getOptions().get("max");
final int value = (Integer) parameter.getValue();
final Hashtable<Integer, JComponent> labels = new Hashtable<Integer, JComponent>();
labels.put(min, new JLabel(Integer.toString(min)));
labels.put(max, new JLabel(Integer.toString(max)));
final JSlider slider = new JSlider(min, max, value);
slider.setLabelTable(labels);
slider.setOpaque(false);
slider.setPaintTicks(true);
slider.setEnabled(!parameter.isReadOnly());
slider.addChangeListener(new ChangeListener() {
public void stateChanged(final ChangeEvent event) {
Object source = event.getSource();
if (source instanceof BoundedRangeModel) {
BoundedRangeModel aModel = (BoundedRangeModel) source;
if (!aModel.getValueIsAdjusting()) {
int newValue = aModel.getValue();
component.setText(Integer.toString(newValue));
parameter.setValue(newValue);
}
} else if (source instanceof JSlider) {
JSlider theJSlider = (JSlider) source;
if (!theJSlider.getValueIsAdjusting()) {
int newValue = theJSlider.getValue();
component.setText(Integer.toString(newValue));
parameter.setValue(newValue);
}
} else {
System.out.println("Something changed: " + source);
}
}
});
parameter.addParamChangeListener(new SliderParamChangeListener(slider));
component.addKeyListener(new KeyListener() {
public void keyTyped(final KeyEvent e) {}
public void keyReleased(final KeyEvent e) {}
public void keyPressed(final KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
final String string = ((JTextField)e.getSource()).getText();
// TODO strip non number characters
parameter.setValue(Integer.valueOf(string));
slider.setValue(Integer.valueOf(string));
}
}
});
panel.add(new JLabel(Integer.toString(min)));
panel.add(slider);
panel.add(new JLabel(Integer.toString(max)));
panel.add(component);
return panel;
}
/**
* @param parameter
* @return
* @throws IllegalArgumentException
*/
public static JComponent createSlider(final DoubleParameter parameter)
throws IllegalArgumentException {
final JPanel panel = new JPanel();
panel.setOpaque(false);
final JTextField component = new JTextField(parameter.getValue().toString());
component.setEnabled(!parameter.isReadOnly());
component.setColumns(5);
parameter.addParamChangeListener(new TextfieldParamChangeListener(component));
final double min = (Double) parameter.getOptions().get("min");
final double max = (Double) parameter.getOptions().get("max");
final double value = (Double) parameter.getValue();
final Hashtable<Double, JComponent> labels = new Hashtable<Double, JComponent>();
labels.put(min, new JLabel(Double.toString(min)));
labels.put(max, new JLabel(Double.toString(max)));
final JSlider slider = new JSlider((int)(min*100), (int)(max*100), (int)(value*100));
slider.setLabelTable(labels);
slider.setPaintTicks(true);
slider.setEnabled(!parameter.isReadOnly());
slider.addChangeListener(new ChangeListener() {
public void stateChanged(final ChangeEvent event) {
double newValue = ((JSlider) event.getSource()).getValue()*0.01;
DecimalFormat decimal = new DecimalFormat("0.00");
decimal.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
component.setText(decimal.format(newValue));
parameter.setValue(newValue);
}
});
parameter.addParamChangeListener(new SliderParamChangeListener(slider));
component.addKeyListener(new KeyListener() {
public void keyTyped(final KeyEvent e) {}
public void keyReleased(final KeyEvent e) {}
public void keyPressed(final KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
double oldValue = parameter.getValue();
try {
final String string = ((JTextField)e.getSource()).getText();
// TODO improve strip non number characters
double newValue = Double.valueOf(string);
slider.setValue((int)(newValue*100));
parameter.setValue(newValue);
} catch (NumberFormatException ex) {
ex.printStackTrace();
}
}
}
});
panel.add(new JLabel(Double.toString(min)));
panel.add(slider);
panel.add(new JLabel(Double.toString(max)));
panel.add(component);
return panel;
}
/**
*
* @param parameter
* @return
*/
public static JComponent createRadios(final ChoiceParameter parameter) {
final JPanel panel = new JPanel();
panel.setOpaque(false);
JRadioButton radioButton;
ButtonGroup group = new ButtonGroup();
for (final String choice : parameter.getChoices()) {
radioButton = new JRadioButton(choice);
radioButton.setActionCommand(choice);
radioButton.setOpaque(false);
radioButton.addActionListener(new RadioActionListener(parameter));
if (parameter.getValue().equals(choice)) {
radioButton.setSelected(true);
}
group.add(radioButton);
panel.add(radioButton);
}
return panel;
}
static class RadioActionListener implements ActionListener {
final private ChoiceParameter parameter;
public RadioActionListener(ChoiceParameter parameter) {
this.parameter = parameter;
}
public void actionPerformed(ActionEvent event) {
parameter.setValue(event.getActionCommand());
}
}
/**
* Creates a Textfield form element based on the {@link Parameter}
* @param parameter
* @return
*/
public static JComponent createTextField(final Parameter parameter) {
final JTextField component = new JTextField(parameter.getValue().toString());
component.setEnabled(!parameter.isReadOnly());
parameter.addParamChangeListener(new TextfieldParamChangeListener(component));
component.addKeyListener(new KeyListener() {
public void keyTyped(final KeyEvent e) {}
public void keyReleased(final KeyEvent e) {}
public void keyPressed(final KeyEvent e) {
try {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
final String string = ((JTextField)e.getSource()).getText();
// TODO strip non number characters
if(parameter instanceof StringParameter) {
((StringParameter)parameter).setValue(string);
} else if(parameter instanceof DoubleParameter) {
((DoubleParameter)parameter).setValue(Double.valueOf(string));
} else if(parameter instanceof IntegerParameter) {
((IntegerParameter)parameter).setValue(Integer.valueOf(string));
}
component.transferFocus();
}
} catch (NumberFormatException numEx) {
String message = "The value you entered not supported.";
System.err.println(message);
//showErrorDialog("XML parsing error", message);
}
}
});
return component;
}
/**
* Create a {@link JCheckBox} for {@link BooleanParameter}
* @param parameter
* @return
*/
public static JCheckBox createCheckBox(final BooleanParameter parameter) {
final JCheckBox chkBox = new JCheckBox();
//chkBox.setText(parameter.getDisplayName());
chkBox.setSelected(parameter.getValue());
chkBox.setOpaque(false);
parameter.addParamChangeListener(new CheckBoxParamChangeListener(chkBox));
chkBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent event) {
final boolean newValue = ((JCheckBox)event.getSource()).isSelected();
parameter.setValue(newValue);
}
});
return chkBox;
}
/**
* Create a {@link JComboBox} for the given {@link ChoiceParameter}.
* @param parameter
* @return
*/
public static JComboBox createComboBox(final ChoiceParameter parameter) {
JComboBox combo = new JComboBox();
final String[] choices = parameter.getChoicesArray();
combo = new JComboBox(choices);
combo.setSelectedItem(parameter.getValue());
parameter.addParamChangeListener(new ComboBoxParamChangeListener(combo));
combo.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
final int newindex = ((JComboBox)e.getSource()).getSelectedIndex();
parameter.setValue(newindex);
}
}
});
return combo;
}
/**
* @param parameter
* @param chooser
* @return
*/
public static JComponent createFileChooser(final StringParameter parameter, final FileChooser chooser) {
JPanel panel = new JPanel();
panel.setOpaque(false);
panel.setLayout(new BorderLayout());
JTextField textfield = new JTextField(parameter.getValue());
textfield.setEnabled(false);
JButton fileDialogButton = new JButton("Choose");
fileDialogButton.addActionListener(new FileChooserActionListener(parameter, textfield, chooser));
panel.add(textfield, BorderLayout.CENTER);
panel.add(fileDialogButton, BorderLayout.LINE_END);
return panel;
}
private static class FileChooserActionListener implements ActionListener {
private StringParameter parameter;
private JTextField textfield;
FileChooser chooser;
public FileChooserActionListener(StringParameter parameter, JTextField textfield, FileChooser chooser) {
this.parameter = parameter;
this.textfield = textfield;
this.chooser = chooser;
}
public void actionPerformed(ActionEvent e) {
final JFileChooser fc = new JFileChooser();
String filepath = parameter.getValue();
fc.setSelectedFile(new File(filepath));
final int option;
if(chooser == FileChooser.OPEN) {
option = fc.showOpenDialog(null);
} else {
option = fc.showSaveDialog(null);
}
if (option == JFileChooser.APPROVE_OPTION) {
filepath = fc.getSelectedFile().getAbsolutePath();
// backslashes need to be escaped
//filepath = filepath.replace("\\", "\\\\"); // \ to \\
parameter.setValue(filepath);
textfield.setText(filepath);
}
}
}
}