/*
* Copyright (C) 2012 Jason Gedge <http://www.gedge.ca>
*
* This file is part of the OpGraph project.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package ca.gedge.opgraph.app.components;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import ca.gedge.opgraph.InputField;
import ca.gedge.opgraph.OpNode;
import ca.gedge.opgraph.app.extensions.NodeMetadata;
import ca.gedge.opgraph.validators.ClassValidator;
/**
* A panel for displaying and editing default values for a node's input fields.
*
* TODO undoable edits for defaults
*/
public class NodeDefaultsPanel extends JPanel {
/** Logger */
private final Logger LOGGER = Logger.getLogger(NodeDefaultsPanel.class.getName());
/** The node currently being viewed */
private OpNode node;
/**
* Default constructor.
*/
public NodeDefaultsPanel() {
super(new GridBagLayout());
setNode(null);
}
/**
* Gets the node this info panel is currently viewing.
*
* @return the node
*/
public OpNode getNode() {
return node;
}
/**
* Sets the node this info panel is currently viewing.
*
* @param node the node to display
*/
public void setNode(OpNode node) {
if(this.node != node || getComponentCount() == 0) {
this.node = node;
// Clear all current components and add in new ones
removeAll();
if(node == null) {
final GridBagConstraints gbc = new GridBagConstraints();
final JLabel label = new JLabel("No node selected", SwingConstants.CENTER);
label.setFont(label.getFont().deriveFont(Font.ITALIC));
add(label, gbc);
} else {
final GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0;
final NodeMetadata meta = node.getExtension(NodeMetadata.class);
if(meta != null) {
gbc.insets.set(2, 5, 2, 2);
gbc.gridwidth = 1;
for(InputField field : node.getInputFields()) {
final Object value = meta.getDefault(field);
final JComponent editComp = getEditComponentForField(field, value);
if(editComp != null) {
final JLabel fieldNameLabel = new JLabel(field.getKey() + ":");
fieldNameLabel.setFont(fieldNameLabel.getFont().deriveFont(Font.BOLD));
fieldNameLabel.setToolTipText(field.getDescription());
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.EAST;
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = 0;
add(fieldNameLabel, gbc);
editComp.setToolTipText(field.getDescription());
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
add(editComp, gbc);
++gbc.gridy;
}
}
if(getComponentCount() > 0) {
gbc.weighty = 1;
gbc.gridwidth = 2;
add(new JComponent() {}, gbc);
} else {
final JLabel label = new JLabel("No editable input fields", SwingConstants.CENTER);
label.setFont(label.getFont().deriveFont(Font.ITALIC));
add(label, gbc);
}
} else {
final JLabel label = new JLabel("Node does not support defaults", SwingConstants.CENTER);
label.setFont(label.getFont().deriveFont(Font.ITALIC));
add(label, gbc);
}
}
revalidate();
repaint();
}
}
/**
* Gets an editing component for an input field.
*
* @param field the input field
*
* @return the editing component for the specified input field, or
* <code>null</code> if there is no editable component for
* the given input field
*
* TODO More user customization on editors for different types/validators.
* Perhaps have an interface with a function similar to below.
*/
private JComponent getEditComponentForField(final InputField field, Object defaultValue) {
// XXX allow enabled field here?
if(field.getKey() == OpNode.ENABLED_FIELD.getKey())
return null;
// Editable component returned is currently based on a fixed set of
// classes, and only if the validator of the field is a class-based
// validator which accepts a single class
JComponent ret = null;
if(field.getValidator() != null && field.getValidator() instanceof ClassValidator) {
final ClassValidator validator = (ClassValidator)field.getValidator();
if(validator.getClasses().size() == 1) {
Class<?> cls = validator.getClasses().get(0);
// Check default value against this class
if(defaultValue != null) {
if(!cls.isAssignableFrom(defaultValue.getClass())) {
LOGGER.warning("Default value for input field '" + field.getKey() +"' should be '" + cls.getName() + "' but got '" + defaultValue.getClass().getName() + "' instead");
defaultValue = null;
}
}
if(cls == String.class) {
// JTextArea for java.lang.String
String initialText = "";
if(defaultValue != null)
initialText = (String)defaultValue;
final JTextArea stringEditable = new JTextArea(initialText);
stringEditable.setBorder(BorderFactory.createEtchedBorder());
stringEditable.setLineWrap(true);
stringEditable.setTabSize(4);
stringEditable.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
if(node != null) {
final NodeMetadata meta = node.getExtension(NodeMetadata.class);
if(meta != null)
meta.setDefault(field, stringEditable.getText());
}
}
@Override
public void insertUpdate(DocumentEvent e) {
if(node != null) {
final NodeMetadata meta = node.getExtension(NodeMetadata.class);
if(meta != null)
meta.setDefault(field, stringEditable.getText());
}
}
@Override
public void changedUpdate(DocumentEvent e) {
if(node != null) {
final NodeMetadata meta = node.getExtension(NodeMetadata.class);
if(meta != null)
meta.setDefault(field, stringEditable.getText());
}
}
});
ret = stringEditable;
} else if(cls == Boolean.class) {
// JTextBox for java.lang.Boolean
boolean initial = false;
if(defaultValue != null)
initial = (Boolean)defaultValue;
final JCheckBox booleanEditable = new JCheckBox();
booleanEditable.setSelected(initial);
booleanEditable.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if(node != null) {
final NodeMetadata meta = node.getExtension(NodeMetadata.class);
if(meta != null)
meta.setDefault(field, e.getStateChange() == ItemEvent.SELECTED);
}
}
});
ret = booleanEditable;
} else if(Number.class.isAssignableFrom(cls)) {
Number initial = null;
if(defaultValue != null)
initial = (Number)defaultValue;
final NumberFormat formatter = NumberFormat.getInstance();
final JFormattedTextField numberEditable = new JFormattedTextField(formatter);
numberEditable.setValue(initial);
numberEditable.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
if(node != null && e.getPropertyName().equals("value")) {
final NodeMetadata meta = node.getExtension(NodeMetadata.class);
if(meta != null)
meta.setDefault(field, e.getNewValue());
}
}
});
ret = numberEditable;
}
}
}
return ret;
}
}