org.jdesktop.swingx.table.NumberEditorExt Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id$
*
* Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.table;
import java.awt.Color;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.text.ParseException;
import javax.swing.DefaultCellEditor;
import javax.swing.InputMap;
import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.border.LineBorder;
import javax.swing.text.NumberFormatter;
import org.jdesktop.swingx.text.StrictNumberFormatter;
import org.jdesktop.swingx.text.NumberFormatExt;
/**
*
* Issue #393-swingx: localized NumberEditor. Added feature to use StrictNumberFormatter.
*
* @author Noel Grandin
* @author Jeanette Winzenburg
*/
public class NumberEditorExt extends DefaultCellEditor {
private static Class>[] argTypes = new Class[]{String.class};
java.lang.reflect.Constructor> constructor;
private boolean useStrictFormatter;
/**
* Instantiates an editor with default NumberFormat and default NumberFormatter.
*/
public NumberEditorExt() {
this(null);
}
/**
* Instantiates an editor with the given NumberFormat and default NumberFormatter.
*
* @param format the NumberFormat to use for conversion, may be null to indicate
* usage of default NumberFormat.
*/
public NumberEditorExt(NumberFormat format) {
this(format, false);
}
/**
* Instantiates an editor with default NumberFormat and NumberFormatter depending
* on useStrictFormatter.
*
* @param useStrictFormatter if true, uses a StrictNumberFormatter, else uses
* default NumberFormatter
*/
public NumberEditorExt(boolean useStrictFormatter) {
this(null, useStrictFormatter);
}
/**
* Instantiates an editor with the given NumberFormat and NumberFormatter depending on
* useStrictFormatter.
*
* @param format the NumberFormat to use for conversion, may be null to indicate
* usage of default NumberFormat
* @param useStrictFormatter if true, uses a StrictNumberFormatter, else uses
* default NumberFormatter
*/
public NumberEditorExt(NumberFormat format, boolean useStrictFormatter) {
super(useStrictFormatter ? createFormattedTextFieldX(format) : createFormattedTextField(format));
this.useStrictFormatter = useStrictFormatter;
final JFormattedTextField textField = getComponent();
textField.setName("Table.editor");
textField.setHorizontalAlignment(JTextField.RIGHT);
// remove action listener added in DefaultCellEditor
textField.removeActionListener(delegate);
// replace the delegate created in DefaultCellEditor
delegate = new EditorDelegate() {
@Override
public void setValue(Object value) {
getComponent().setValue(value);
}
@Override
public Object getCellEditorValue() {
try {
getComponent().commitEdit();
return getComponent().getValue();
} catch (ParseException ex) {
return null;
}
}
};
textField.addActionListener(delegate);
}
@Override
public boolean stopCellEditing() {
if (!isValid()) return false;
return super.stopCellEditing();
}
/**
* Returns a boolean indicating whether the current text is valid for
* instantiating the expected Number type.
*
* @return true if text is valid, false otherwise.
*/
protected boolean isValid() {
if (!getComponent().isEditValid()) return false;
try {
if (!hasStrictFormatter())
getNumber();
return true;
} catch (Exception ex) {
}
return false;
}
/**
* Returns the editor value as number. May fail for a variety of reasons,
* as it forces parsing of the current text as well as reflective construction
* of the target type.
*
* @return the editor value or null
* @throws Exception if creation of the expected type fails in some way.
*/
protected Number getNumber() throws Exception {
Number number = (Number) super.getCellEditorValue();
if (number==null) return null;
return hasStrictFormatter() ? number :
(Number) constructor.newInstance(new Object[]{number.toString()});
}
/**
* @return
*/
protected boolean hasStrictFormatter() {
return useStrictFormatter;
}
/** Override and set the border back to normal in case there was an error previously */
@Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected,
int row, int column) {
((JComponent)getComponent()).setBorder(new LineBorder(Color.black));
try {
final Class> type = table.getColumnClass(column);
if (hasStrictFormatter()) {
// delegate to formatter which decides at parsing time
// then either handles or throws
((NumberFormatter) getComponent().getFormatter()).setValueClass(type);
} else {
// Assume that the Number object we are dealing with has a constructor which takes
// a single string parameter.
if (!Number.class.isAssignableFrom(type)) {
throw new IllegalStateException("NumberEditor can only handle subclasses of java.lang.Number");
}
constructor = type.getConstructor(argTypes);
}
// JW: in strict mode this may fail in setting the value in the formatter
return super.getTableCellEditorComponent(table, value, isSelected, row, column);
} catch (Exception ex) {
// PENDING JW: super generic editor swallows all failures and returns null
// should we do so as well?
throw new IllegalStateException("value/type not compatible with Number", ex);
}
}
/**
* {@inheritDoc}
*
* Overridden to instantiate a Number of the expected type. Note that this
* may throw a IllegalStateException if invoked without querying
* for a valid value with stopCellEditing. This should not happen during
* normal usage.
*
* @throws IllegalStateException if current value invalid
*
*/
@Override
public Number getCellEditorValue() throws IllegalStateException {
try {
return getNumber();
} catch (Exception ex) {
throw new IllegalStateException("Number conversion not possible from " +
"current string " + getComponent().getText());
}
}
/**
* {@inheritDoc}
*
* Convenience override with type cast.
*/
@Override
public JFormattedTextField getComponent() {
return (JFormattedTextField) super.getComponent();
}
/**
* Creates and returns a JFormattedTextField configured with SwingX extended
* NumberFormat and StrictNumberFormatter. This method is called if
* the constructor parameter useStrictFormatter is true.
*
* Use a static method so that we can do some stuff before calling the
* superclass.
*/
private static JFormattedTextField createFormattedTextFieldX(
NumberFormat format) {
StrictNumberFormatter formatter = new StrictNumberFormatter(
new NumberFormatExt(format));
final JFormattedTextField textField = new JFormattedTextField(
formatter);
/*
* FIXME: I am sure there is a better way to do this, but I don't know
* what it is. JTable sets up a binding for the ESCAPE key, but
* JFormattedTextField overrides that binding with it's own. Remove the
* JFormattedTextField binding.
*/
InputMap map = textField.getInputMap();
map.put(KeyStroke.getKeyStroke("ESCAPE"), "none");
// while (map != null) {
// map.remove(KeyStroke.getKeyStroke("pressed ESCAPE"));
// map = map.getParent();
// }
/*
* Set an input verifier to prevent the cell losing focus when the value
* is invalid
*/
textField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
JFormattedTextField ftf = (JFormattedTextField) input;
return ftf.isEditValid();
}
});
/*
* The formatted text field will not call stopCellEditing() until the
* value is valid. So do the red border thing here.
*/
textField.addPropertyChangeListener("editValid",
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == Boolean.TRUE) {
((JFormattedTextField) evt.getSource())
.setBorder(new LineBorder(Color.black));
} else {
((JFormattedTextField) evt.getSource())
.setBorder(new LineBorder(Color.red));
}
}
});
return textField;
}
/**
* Creates and returns a JFormattedTextField configured with defaults. This
* method is called if the contructor useStrictFormatter is false.
*
* Use a static method so that we can do some stuff before calling the
* superclass.
*/
private static JFormattedTextField createFormattedTextField(
NumberFormat formatter) {
final JFormattedTextField textField = new JFormattedTextField(
new NumberFormatExt(formatter));
/*
* FIXME: I am sure there is a better way to do this, but I don't know
* what it is. JTable sets up a binding for the ESCAPE key, but
* JFormattedTextField overrides that binding with it's own. Remove the
* JFormattedTextField binding.
*/
InputMap map = textField.getInputMap();
map.put(KeyStroke.getKeyStroke("ESCAPE"), "none");
// while (map != null) {
// map.remove(KeyStroke.getKeyStroke("pressed ESCAPE"));
// map = map.getParent();
// }
/*
* Set an input verifier to prevent the cell losing focus when the value
* is invalid
*/
textField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
JFormattedTextField ftf = (JFormattedTextField) input;
return ftf.isEditValid();
}
});
/*
* The formatted text field will not call stopCellEditing() until the
* value is valid. So do the red border thing here.
*/
textField.addPropertyChangeListener("editValid",
new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == Boolean.TRUE) {
((JFormattedTextField) evt.getSource())
.setBorder(new LineBorder(Color.black));
} else {
((JFormattedTextField) evt.getSource())
.setBorder(new LineBorder(Color.red));
}
}
});
return textField;
}
}