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 }