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    }