001 /*
002 * $Id: NumberEditorExt.java 3100 2008-10-14 22:33:10Z rah003 $
003 *
004 * Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx.table;
022
023 import java.awt.Color;
024 import java.awt.Component;
025 import java.beans.PropertyChangeEvent;
026 import java.beans.PropertyChangeListener;
027 import java.lang.reflect.InvocationTargetException;
028 import java.text.NumberFormat;
029 import java.text.ParseException;
030
031 import javax.swing.DefaultCellEditor;
032 import javax.swing.InputMap;
033 import javax.swing.InputVerifier;
034 import javax.swing.JComponent;
035 import javax.swing.JFormattedTextField;
036 import javax.swing.JTable;
037 import javax.swing.JTextField;
038 import javax.swing.KeyStroke;
039 import javax.swing.border.LineBorder;
040
041
042 /**
043 *
044 * Issue #393-swingx: localized NumberEditor.
045 *
046 * @author Noel Grandin
047 */
048 public class NumberEditorExt extends DefaultCellEditor {
049
050 private static Class[] argTypes = new Class[]{String.class};
051 java.lang.reflect.Constructor constructor;
052
053 public NumberEditorExt() {
054 this(null);
055 }
056 public NumberEditorExt(NumberFormat formatter) {
057 super(createFormattedTextField(formatter));
058 final JFormattedTextField textField = ((JFormattedTextField)getComponent());
059
060 textField.setName("Table.editor");
061 textField.setHorizontalAlignment(JTextField.RIGHT);
062
063 // remove action listener added in DefaultCellEditor
064 textField.removeActionListener(delegate);
065 // replace the delegate created in DefaultCellEditor
066 delegate = new EditorDelegate() {
067 @Override
068 public void setValue(Object value) {
069 ((JFormattedTextField)getComponent()).setValue(value);
070 }
071
072 @Override
073 public Object getCellEditorValue() {
074 JFormattedTextField textField = ((JFormattedTextField)getComponent());
075 try {
076 textField.commitEdit();
077 return textField.getValue();
078 } catch (ParseException ex) {
079 return null;
080 }
081 }
082 };
083 textField.addActionListener(delegate);
084 }
085
086 @Override
087 public boolean stopCellEditing() {
088 // If the user tries to tab out of the field, the textField will call stopCellEditing().
089 // Check for a valid edit, and don't let the focus leave until the edit is valid.
090 if (!((JFormattedTextField) editorComponent).isEditValid()) return false;
091 return super.stopCellEditing();
092 }
093
094 /** Override and set the border back to normal in case there was an error previously */
095 @Override
096 public Component getTableCellEditorComponent(JTable table, Object value,
097 boolean isSelected,
098 int row, int column) {
099 ((JComponent)getComponent()).setBorder(new LineBorder(Color.black));
100 try {
101 final Class type = table.getColumnClass(column);
102 // Assume that the Number object we are dealing with has a constructor which takes
103 // a single string parameter.
104 if (!Number.class.isAssignableFrom(type)) {
105 throw new IllegalStateException("NumberEditor can only handle subclasses of java.lang.Number");
106 }
107 constructor = type.getConstructor(argTypes);
108 }
109 catch (Exception ex) {
110 throw new IllegalStateException("Number subclass must have a constructor which takes a string", ex);
111 }
112 return super.getTableCellEditorComponent(table, value, isSelected, row, column);
113 }
114
115 @Override
116 public Object getCellEditorValue() {
117 Number number = (Number) super.getCellEditorValue();
118 if (number==null) return null;
119 // we use a String value as an intermediary between the Number object returned by the
120 // the NumberFormat and the kind of Object the column wants.
121 try {
122 return constructor.newInstance(new Object[]{number.toString()});
123 } catch (IllegalArgumentException ex) {
124 throw new RuntimeException("NumberEditor not propertly configured", ex);
125 } catch (InstantiationException ex) {
126 throw new RuntimeException("NumberEditor not propertly configured", ex);
127 } catch (IllegalAccessException ex) {
128 throw new RuntimeException("NumberEditor not propertly configured", ex);
129 } catch (InvocationTargetException ex) {
130 throw new RuntimeException("NumberEditor not propertly configured", ex);
131 }
132 }
133
134
135 /**
136 * Use a static method so that we can do some stuff before calling the
137 * superclass.
138 */
139 private static JFormattedTextField createFormattedTextField(
140 NumberFormat formatter) {
141 final JFormattedTextField textField = new JFormattedTextField(
142 new NumberEditorNumberFormat(formatter));
143 /*
144 * FIXME: I am sure there is a better way to do this, but I don't know
145 * what it is. JTable sets up a binding for the ESCAPE key, but
146 * JFormattedTextField overrides that binding with it's own. Remove the
147 * JFormattedTextField binding.
148 */
149 InputMap map = textField.getInputMap();
150 while (map != null) {
151 map.remove(KeyStroke.getKeyStroke("pressed ESCAPE"));
152 map = map.getParent();
153 }
154 /*
155 * Set an input verifier to prevent the cell losing focus when the value
156 * is invalid
157 */
158 textField.setInputVerifier(new InputVerifier() {
159 @Override
160 public boolean verify(JComponent input) {
161 JFormattedTextField ftf = (JFormattedTextField) input;
162 return ftf.isEditValid();
163 }
164 });
165 /*
166 * The formatted text field will not call stopCellEditing() until the
167 * value is valid. So do the red border thing here.
168 */
169 textField.addPropertyChangeListener("editValid",
170 new PropertyChangeListener() {
171 public void propertyChange(PropertyChangeEvent evt) {
172 if (evt.getNewValue() == Boolean.TRUE) {
173 ((JFormattedTextField) evt.getSource())
174 .setBorder(new LineBorder(Color.black));
175 } else {
176 ((JFormattedTextField) evt.getSource())
177 .setBorder(new LineBorder(Color.red));
178 }
179 }
180 });
181 return textField;
182 }
183 }