View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.ui.swing.components;
20  
21  import java.awt.Color;
22  import java.awt.Component;
23  import java.awt.event.FocusEvent;
24  import java.awt.event.FocusListener;
25  import java.awt.event.KeyEvent;
26  import java.awt.event.KeyListener;
27  import java.io.Serializable;
28  import java.text.Format;
29  import java.util.ArrayList;
30  import java.util.EventObject;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Vector;
34  
35  import javax.swing.JTable;
36  import javax.swing.JTextField;
37  import javax.swing.border.LineBorder;
38  import javax.swing.event.CellEditorListener;
39  import javax.swing.event.ChangeEvent;
40  import javax.swing.table.TableCellEditor;
41  
42  /***
43  * A table cell editor customized for keyboard navigation, much like
44  * working with a spreadsheet.  The default cell editor unfortunately
45  * does none of these things:
46  * <ul>
47  *    <li> Selects text on start of editing.
48  *    <li> Up and down keys move edit cell up and down.
49  *    <li> Right and left keys move cell when selection caret is at end of text.
50  *    <li> Escape cancels editing.
51  *    <li> Enter commits edit.
52  *    <li> Edits are properly committed on lost focus.
53  *    <li> Tab and shift-tab work as expected.
54  *    <li> Cell selection moves with the edit cell.
55  * </ul>
56  *
57  * @author michael@mpowers.net
58  * @author $Author: cgruber $
59  * @version $Revision: 904 $
60  * $Date: 2006-02-18 23:19:05 +0000 (Sat, 18 Feb 2006) $
61  */
62  public class KeyableCellEditor implements TableCellEditor, FocusListener,
63                                          KeyListener, Serializable
64  {
65      List listeners;
66      JTextField textField;
67      Object lastValue;
68      Format currentFormat;
69  
70      JTable table;
71  
72  /***
73  * Default constructor - a standard JTextField will be used for editing.
74  */
75      public KeyableCellEditor()
76      {
77          this( (JTextField) null );
78      }
79  
80  /***
81  * Constructor specifying a type of JTextField to be used for editing.
82  * The JTextField will have its border replaced with a black line border.
83  * @param aTextField A JTextField or subclass for editing values.
84  */
85      public KeyableCellEditor( JTextField aTextField )
86      {
87          listeners = new Vector();
88          lastValue = null;
89  
90          // default to stock JTextField
91          textField = aTextField;
92          if ( textField == null )
93          {
94              textField = new JTextField();
95          }
96  
97          textField.setBorder(new LineBorder(Color.black));
98  
99          // handle arrow keys while caret is showing
100         textField.addKeyListener( this );
101 
102         // handle lost focus
103         textField.addFocusListener( this );
104     }
105 
106     public Component getTableCellEditorComponent(JTable table,
107                                                  Object value,
108                                                  boolean isSelected,
109                                                  int row,
110                                                  int column)
111     {
112         this.table = table;
113         table.removeKeyListener( this ); // if any
114         table.addKeyListener( this );
115         return getEditorComponent( value );
116     }
117 
118     protected Component getEditorComponent( Object value )
119     {
120         if ( value != null )
121         {
122             textField.setText( value.toString() );
123         }
124         else
125         {
126             textField.setText( "" );
127         }
128 
129         if ( value instanceof Number )
130         {
131             textField.setHorizontalAlignment(JTextField.RIGHT);
132         }
133         else
134         {
135             textField.setHorizontalAlignment(JTextField.LEFT);
136         }
137 
138         // remember original value
139         lastValue = value;
140 
141         // select all text and get focus
142         textField.selectAll();
143         textField.requestFocus();
144 
145         return textField;
146     }
147 
148     public Object getCellEditorValue()
149     {
150         return lastValue;
151     }
152 
153     public boolean isCellEditable(EventObject anEvent)
154     {
155         // key events should replace the selection
156         // NOTE: For whatever reason, key events trigger result in a null parameter
157         if ( anEvent == null )
158         {
159             textField.setText("");
160             textField.requestFocus();
161             return true;
162         }
163 
164         return true;
165     }
166 
167     public boolean shouldSelectCell(EventObject anEvent)
168     { // System.out.println( "KeyableCellEditor.shouldSelectCell: " + anEvent );
169 
170         // key events should replace the selection
171         // NOTE: For whatever reason, key events are not generated
172         if ( anEvent instanceof KeyEvent )
173         {
174             textField.setText("");
175             textField.requestFocus();
176             return true;
177         }
178 
179         // otherwise, select all text and continue
180         textField.selectAll();
181         textField.requestFocus();
182 
183         return true;
184     }
185 
186     public boolean stopCellEditing()
187     {
188         lastValue = textField.getText();
189         fireEditingStopped();
190         table.removeKeyListener( this ); // if any
191         return true;
192     }
193 
194     public void cancelCellEditing()
195     {
196         fireEditingCanceled();
197         table.removeKeyListener( this ); // if any
198     }
199 
200     public void addCellEditorListener(CellEditorListener l)
201     {
202         listeners.add( l );
203     }
204 
205     public void removeCellEditorListener(CellEditorListener l)
206     {
207         listeners.remove( l );
208     }
209 
210     protected void fireEditingCanceled()
211     {
212         ChangeEvent event = new ChangeEvent( this );
213         Iterator it = new ArrayList( listeners ).iterator(); // copy to prevent modification exception
214         while ( it.hasNext() )
215         {
216             ((CellEditorListener)it.next()).editingCanceled( event );
217         }
218     }
219 
220     protected void fireEditingStopped()
221     {
222         ChangeEvent event = new ChangeEvent( this );
223         Iterator it = new ArrayList( listeners ).iterator(); // copy to prevent modification exception
224         while ( it.hasNext() )
225         {
226             ((CellEditorListener)it.next()).editingStopped( event );
227         }
228     }
229 
230     protected void onEnterKey()
231     {
232         stopCellEditing();
233     }
234 
235     protected void onEscapeKey()
236     {
237         cancelCellEditing();
238     }
239 
240     protected void moveEditCell( int dRow, int dCol )
241     {
242         if ( table == null ) return;
243         int row = table.getSelectedRow() + dRow;
244         int col = table.getSelectedColumn() + dCol;
245 
246         row = Math.max( 0, row );
247         row = Math.min( row, table.getRowCount() - 1 );
248         col = Math.max( 0, col );
249         col = Math.min( col, table.getColumnCount() - 1 );
250 
251         stopCellEditing();
252         table.setRowSelectionInterval( row, row );
253         table.setColumnSelectionInterval( col, col );
254         table.editCellAt( row, col );
255         textField.selectAll();
256         textField.requestFocus();
257     }
258 
259     // interface KeyListener
260 
261     public void keyTyped(KeyEvent e)
262     { // System.out.println( "KeyableCellEditor.keyTyped: " + KeyEvent.getKeyText( e.getKeyCode() ) );
263     }
264 
265     public void keyPressed(KeyEvent e)
266     { // System.out.println( "KeyableCellEditor.keyPressed: " + KeyEvent.getKeyText( e.getKeyCode() ) );
267 
268         // catch LEFT and RIGHT here before JTextField consumes them
269 
270         int keyCode = e.getKeyCode();
271         if ( keyCode == KeyEvent.VK_LEFT )
272         {
273             if ( textField.getSelectionStart() == 0 )
274             {
275                 moveEditCell( 0, -1 );
276                 e.consume();
277                 return;
278             }
279         }
280         if ( keyCode == KeyEvent.VK_RIGHT )
281         {
282             if ( textField.getSelectionEnd() == textField.getText().length() )
283             {
284                 moveEditCell( 0, 1 );
285                 e.consume();
286                 return;
287             }
288         }
289         if ( keyCode == KeyEvent.VK_UP )
290         {
291             moveEditCell( -1, 0 );
292             e.consume();
293             return;
294         }
295         if ( keyCode == KeyEvent.VK_DOWN )
296         {
297             moveEditCell( 1, 0 );
298             e.consume();
299             return;
300         }
301     }
302 
303     public void keyReleased(KeyEvent e)
304     { // System.out.println( "KeyableCellEditor.keyReleased: " + KeyEvent.getKeyText( e.getKeyCode() ) );
305 
306         // catch ENTER here to allow JTextField to process it as well
307 
308         int keyCode = e.getKeyCode();
309         if ( keyCode == KeyEvent.VK_ENTER )
310         {
311             onEnterKey();
312             return;
313         }
314         if ( keyCode == KeyEvent.VK_ESCAPE )
315         {
316             onEscapeKey();
317             return;
318         }
319 
320         // tabs are apparently only received on key release
321         if ( keyCode == KeyEvent.VK_TAB )
322         {
323             if ( e.isShiftDown() )
324             {
325                 moveEditCell( 0, -1 );
326             }
327             else
328             {
329                 moveEditCell( 0, 1 );
330             }
331             e.consume();
332             return;
333         }
334 
335     }
336 
337     // interface FocusListener
338 
339     public void focusGained(FocusEvent e)
340     { // System.out.println( "focusGained: " );        
341     }
342 
343     public void focusLost(FocusEvent e)
344     { // System.out.println( "focusLost: " );        
345         stopCellEditing();
346     }
347 
348 }
349 
350