View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Michael Powers
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.Cursor;
24  import java.awt.Font;
25  import java.awt.Insets;
26  import java.awt.event.ActionEvent;
27  import java.awt.event.ActionListener;
28  import java.lang.reflect.Method;
29  import java.util.Vector;
30  
31  import javax.swing.BorderFactory;
32  import javax.swing.DefaultCellEditor;
33  import javax.swing.JButton;
34  import javax.swing.JCheckBox;
35  import javax.swing.JColorChooser;
36  import javax.swing.JDialog;
37  import javax.swing.JLabel;
38  import javax.swing.JTable;
39  import javax.swing.ListSelectionModel;
40  import javax.swing.SwingUtilities;
41  import javax.swing.border.Border;
42  import javax.swing.table.DefaultTableCellRenderer;
43  import javax.swing.table.TableCellEditor;
44  import javax.swing.table.TableCellRenderer;
45  import javax.swing.table.TableColumn;
46  import javax.swing.table.TableColumnModel;
47  import javax.swing.table.TableModel;
48  
49  /***
50  * PropertyEditorTable is a table designed to display and edit the properties
51  * of an object.  Because JTable assumes all cells in a column display
52  * the same data type, we have to subclass to determine the class
53  * based on the cell contents.
54  *
55  * @author michael@mpowers.net
56  * @author $Author: cgruber $
57  * @version $Revision: 904 $
58  */
59  public class PropertyEditorTable extends JTable {
60  
61  //
62  // Constructors
63  //
64  
65      /***
66       * Constructs a default JTable which is initialized with a default
67       * data model, a default column model, and a default selection
68       * model.
69       *
70       * @see #createDefaultDataModel
71       * @see #createDefaultColumnModel
72       * @see #createDefaultSelectionModel
73       */
74      public PropertyEditorTable() {
75          super(null, null, null);
76      }
77  
78      /***
79       * Constructs a JTable which is initialized with <i>dm</i> as the
80       * data model, a default column model, and a default selection
81       * model.
82       *
83       * @param dm        The data model for the table
84       * @see #createDefaultColumnModel
85       * @see #createDefaultSelectionModel
86       */
87      public PropertyEditorTable(TableModel dm) {
88          super(dm, null, null);
89      }
90  
91      /***
92       * Constructs a JTable which is initialized with <i>dm</i> as the
93       * data model, <i>cm</i> as the column model, and a default selection
94       * model.
95       *
96       * @param dm        The data model for the table
97       * @param cm        The column model for the table
98       * @see #createDefaultSelectionModel
99       */
100     public PropertyEditorTable(TableModel dm, TableColumnModel cm) {
101         super(dm, cm, null);
102     }
103 
104     /***
105      * Constructs a JTable which is initialized with <i>dm</i> as the
106      * data model, <i>cm</i> as the column model, and <i>sm</i> as the
107      * selection model.  If any of the parameters are <b>null</b> this
108      * method will initialize the table with the corresponding
109      * default model. The <i>autoCreateColumnsFromModel</i> flag is set
110      * to false if <i>cm</i> is non-null, otherwise it is set to true
111      * and the column model is populated with suitable TableColumns
112      * for the columns in <i>dm</i>.
113      *
114      * @param dm        The data model for the table
115      * @param cm        The column model for the table
116      * @param sm        The row selection model for the table
117      * @see #createDefaultDataModel
118      * @see #createDefaultColumnModel
119      * @see #createDefaultSelectionModel
120      */
121     public PropertyEditorTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
122 		super( dm, cm, sm );
123 	}
124 
125     /***
126      * Constructs a JTable with <i>numRows</i> and <i>numColumns</i> of
127      * empty cells using the DefaultTableModel.  The columns will have
128      * names of the form "A", "B", "C", etc.
129      *
130      * @param numRows           The number of rows the table holds
131      * @param numColumns        The number of columns the table holds
132      */
133     public PropertyEditorTable(int numRows, int numColumns) {
134 		super( numRows, numColumns );
135     }
136 
137     /***
138      * Constructs a JTable to display the values in the Vector of Vectors,
139      * <i>rowData</i>, with column names, <i>columnNames</i>.
140      * The Vectors contained in <i>rowData</i> should contain the values
141      * for that row. In other words, the value of the cell at row 1,
142      * column 5 can be obtained with the following code:
143      * <p>
144      * <pre>((Vector)rowData.elementAt(1)).elementAt(5);</pre>
145      * <p>
146      * All rows must be of the same length as <i>columnNames</i>.
147      * <p>
148      * @param rowData           The data for the new table
149      * @param columnNames       Names of each column
150      */
151     public PropertyEditorTable(final Vector rowData, final Vector columnNames) {
152 		super( rowData, columnNames );
153 	}	
154 
155     /***
156      * Constructs a JTable to display the values in the two dimensional array,
157      * <i>rowData</i>, with column names, <i>columnNames</i>.
158      * <i>rowData</i> is an Array of rows, so the value of the cell at row 1,
159      * column 5 can be obtained with the following code:
160      * <p>
161      * <pre> rowData[1][5]; </pre>
162      * <p>
163      * All rows must be of the same length as <i>columnNames</i>.
164      * <p>
165      * @param rowData           The data for the new table
166      * @param columnNames       Names of each column
167      */
168     public PropertyEditorTable(final Object[][] rowData, final Object[] columnNames) {
169 		super( rowData, columnNames );
170 	}
171 
172     /***
173      * Returns the type of the column at the specified view position.
174      *
175      * @return the type of the column at position <I>column</I> in the view
176      *         where the first column is column 0.
177 	 *
178 	 * Modified mln: now a wrapper for getCellClass()
179 	 *
180      */
181     public Class getColumnClass(int column) {
182 		return getCellClass( 0, column );
183     }
184 
185     /***
186      * Returns the type of the cell at the specified view position.
187      *
188      * @return the type of the cell at position <I>row</I>, <I>column</I> in the view
189      *         where the first column is column 0.
190 	 *
191 	 * Modified mln: new methods
192 	 *
193      */
194     public Class getCellClass(int row, int column) {
195 		TableModel model = getModel();
196 		if ( model instanceof PropertyEditorTableModel )
197 			return ((PropertyEditorTableModel)model).getCellClass( row, column );
198 		else 
199 			return model.getColumnClass(convertColumnIndexToModel(column));
200     }
201 
202     /***
203      * Return an appropriate renderer for the cell specified by this this row and
204      * column. If the TableColumn for this column has a non-null renderer, return that.
205      * If not, find the class of the data in this column (using getColumnClass())
206      * and return the default renderer for this type of data.
207      *
208      * @param row       the row of the cell to render, where 0 is the first
209      * @param column    the column of the cell to render, where 0 is the first
210 	 *
211 	 * Modified mln: calls getCellClass if there's no column model
212 	 *
213      */
214     public TableCellRenderer getCellRenderer(int row, int column) {
215         TableColumn tableColumn = getColumnModel().getColumn(column);
216         TableCellRenderer renderer = tableColumn.getCellRenderer();
217         if (renderer == null) {
218             renderer = getDefaultRenderer(getCellClass(row, column));
219         }
220         return renderer;
221     }
222 
223 
224     /***
225      * Return an appropriate editor for the cell specified by this this row and
226      * column. If the TableColumn for this column has a non-null editor, return that.
227      * If not, find the class of the data in this column (using getColumnClass())
228      * and return the default editor for this type of data.
229      *
230      * @param row       the row of the cell to edit, where 0 is the first
231      * @param column    the column of the cell to edit, where 0 is the first
232 	 *
233 	 * Modified mp: calls getCellClass if there's no column model
234 	 *
235      */
236     public TableCellEditor getCellEditor(int row, int column) {
237         TableColumn tableColumn = getColumnModel().getColumn(column);
238         TableCellEditor editor = tableColumn.getCellEditor();
239         if (editor == null) {
240             editor = getDefaultEditor(getCellClass(row, column));
241         }
242         return editor;
243     }
244 
245 	protected void createDefaultRenderers() {
246 		super.createDefaultRenderers();
247 
248 /*  // copying this code here as a sample of creating a renderer
249 	// Dates
250         DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() {
251 	    DateFormat formatter = DateFormat.getDateInstance(); 
252             public void setValue(Object value) { 
253 	        setText((value == null) ? "" : formatter.format(value)); }
254         };
255         dateRenderer.setHorizontalAlignment(JLabel.RIGHT);
256 		setDefaultRenderer(Date.class, dateRenderer); 
257 */
258 		
259         DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() {
260             public void setValue(Object value) { 
261 				setText( getFontDescription( (Font) value ) );
262 			}
263 		};
264         fontRenderer.setHorizontalAlignment(JLabel.RIGHT);
265 		setDefaultRenderer(Font.class, fontRenderer); 
266 
267 		setUpColorRenderer( this );
268 		setUpMethodRenderer( this );
269     }
270 
271 	protected String getFontDescription( Font f ) {
272 		String s;
273 		if ( f != null ) {
274 			s = f.getName();
275 			if ( f.isBold() ) s += " Bold";
276 			if ( f.isItalic() ) s += " Italic";
277 			s += " " + f.getSize();
278 		} else {
279 			s = "";
280 		}
281 		return s;
282 	}
283 
284     protected void createDefaultEditors() {
285 		super.createDefaultEditors();
286 
287 /*  // copying this code here as a sample of creating an editor
288         // Numbers
289         JTextField rightAlignedTextField = new JTextField();
290         rightAlignedTextField.setHorizontalAlignment(JTextField.RIGHT);
291         rightAlignedTextField.setBorder(new LineBorder(Color.black));
292         setDefaultEditor(Number.class, new DefaultCellEditor(rightAlignedTextField));
293 
294         // Booleans
295         JCheckBox centeredCheckBox = new JCheckBox();
296         centeredCheckBox.setHorizontalAlignment(JCheckBox.CENTER);
297         setDefaultEditor(Boolean.class, new DefaultCellEditor(centeredCheckBox));
298 */
299 		setUpColorEditor( this );
300         setUpMethodEditor( this );
301 	}
302 
303 
304 	// following code lifted from: 
305 	// http://java.sun.com/docs/books/tutorial/ui/swing/example-swing/TableDialogEditDemo.java
306 
307     class ColorRenderer extends JLabel
308                         implements TableCellRenderer {
309         Border unselectedBorder = null;
310         Border selectedBorder = null;
311         boolean isBordered = true;
312 
313         public ColorRenderer(boolean isBordered) {
314             super();
315             this.isBordered = isBordered;
316             this.setOpaque(true); //MUST do this for background to show up.
317         }
318         
319         public Component getTableCellRendererComponent(
320                                 JTable table, Object color, 
321                                 boolean isSelected, boolean hasFocus,
322                                 int row, int column) {
323             this.setBackground((Color)color);
324             if (isBordered) {
325                 if (isSelected) {
326                     if (selectedBorder == null) {
327                         selectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
328                                                   table.getSelectionBackground());
329                     }
330                     this.setBorder(selectedBorder);
331                 } else {
332                     if (unselectedBorder == null) {
333                         unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
334                                                   table.getBackground());
335                     }
336                     this.setBorder(unselectedBorder);
337                 }
338             }
339             return this;
340         }
341     }
342 
343     private void setUpColorRenderer(JTable table) {
344         table.setDefaultRenderer(Color.class,
345                                  new ColorRenderer(true));
346     }
347 
348     //Set up the editor for the Color cells.
349     private void setUpColorEditor(JTable table) {
350         //First, set up the button that brings up the dialog.
351         final JButton button = new JButton("") {
352             public void setText(String s) {
353                 //Button never shows text -- only color.
354             }
355         };
356         button.setBackground(Color.white);
357         button.setBorderPainted(false);
358         button.setMargin(new Insets(0,0,0,0));
359 
360         //Now create an editor to encapsulate the button, and
361         //set it up as the editor for all Color cells.
362         final ColorEditor colorEditor = new ColorEditor(button);
363         table.setDefaultEditor(Color.class, colorEditor);
364 
365         //Set up the dialog that the button brings up.
366         final JColorChooser colorChooser = new JColorChooser();
367         //XXX: PENDING: add the following when setPreviewPanel
368         //XXX: starts working.
369         //JComponent preview = new ColorRenderer(false);
370         //preview.setPreferredSize(new Dimension(50, 10));
371         //colorChooser.setPreviewPanel(preview);
372         ActionListener okListener = new ActionListener() {
373             public void actionPerformed(ActionEvent e) {
374                 colorEditor.currentColor = colorChooser.getColor();
375             }
376         };
377         final JDialog dialog = JColorChooser.createDialog(button,
378                                         "Pick a Color",
379                                         true,
380                                         colorChooser,
381                                         okListener,
382                                         null); //XXXDoublecheck this is OK
383 
384         //Here's the code that brings up the dialog.
385         button.addActionListener(new ActionListener() {
386             public void actionPerformed(ActionEvent e) {
387                 button.setBackground(colorEditor.currentColor);
388                 colorChooser.setColor(colorEditor.currentColor);
389                 //Without the following line, the dialog comes up
390                 //in the middle of the screen.
391                 //dialog.setLocationRelativeTo(button);
392                 dialog.show();
393             }
394         });
395     }
396 
397     /*
398      * The editor button that brings up the dialog.
399      * We extend DefaultCellEditor for convenience,
400      * even though it mean we have to create a dummy
401      * check box.  Another approach would be to copy
402      * the implementation of TableCellEditor methods
403      * from the source code for DefaultCellEditor.
404      */
405     class ColorEditor extends DefaultCellEditor {
406         Color currentColor = null;
407 
408         public ColorEditor(JButton b) {
409                 super(new JCheckBox()); //Unfortunately, the constructor
410                                         //expects a check box, combo box,
411                                         //or text field.
412             editorComponent = b;
413             setClickCountToStart(1); //This is usually 1 or 2.
414 
415             //Must do this so that editing stops when appropriate.
416             b.addActionListener(new ActionListener() {
417                 public void actionPerformed(ActionEvent e) {
418                     fireEditingStopped();
419                 }
420             });
421         }
422 
423         protected void fireEditingStopped() {
424             super.fireEditingStopped();
425         }
426 
427         public Object getCellEditorValue() {
428             return currentColor;
429         }
430 
431         public Component getTableCellEditorComponent(JTable table, 
432                                                      Object value,
433                                                      boolean isSelected,
434                                                      int row,
435                                                      int column) {
436             ((JButton)editorComponent).setText(value.toString());
437             currentColor = (Color)value;
438             return editorComponent;
439         }
440     }
441 
442     class MethodRenderer extends JLabel
443                         implements TableCellRenderer {
444 
445         Method theMethod = null;
446         JTable theTable = null;
447 
448         public MethodRenderer() {
449             super();
450         }
451 
452         public Component getTableCellRendererComponent(
453                                 JTable table, Object method,
454                                 boolean isSelected, boolean hasFocus,
455                                 int row, int column) {
456             theMethod = (Method) method;
457             theTable = table;
458             setText( "  " + theMethod.getReturnType().getName() );
459             return this;
460         }
461     }
462 
463     private void setUpMethodRenderer(JTable table) {
464         table.setDefaultRenderer(Method.class,
465                                  new MethodRenderer());
466     }
467 
468     /*
469      * We extend DefaultCellEditor for convenience,
470      * as with ColorEditor.
471      */
472     class MethodEditor extends DefaultCellEditor {
473         Method theMethod = null;
474         JTable theTable = null;
475 
476         public MethodEditor(JButton b) {
477                 super(new JCheckBox()); //Unfortunately, the constructor
478                                         //expects a check box, combo box,
479                                         //or text field.
480             editorComponent = b;
481             setClickCountToStart(1); //This is usually 1 or 2.
482 
483             //Must do this so that editing stops when appropriate.
484             b.addActionListener(new ActionListener() {
485                 public void actionPerformed(ActionEvent e) {
486                     fireEditingStopped();
487                 }
488             });
489         }
490 
491         protected void fireEditingStopped() {
492             super.fireEditingStopped();
493         }
494 
495         public Object getCellEditorValue() {
496             return theMethod;
497         }
498 
499         public Component getTableCellEditorComponent(JTable table,
500                                                      Object value,
501                                                      boolean isSelected,
502                                                      int row,
503                                                      int column) {
504             ((JButton)editorComponent).setText(value.toString());
505             theMethod = (Method)value;
506             theTable = table;
507             return editorComponent;
508         }
509     }
510 
511     //Set up the editor for the Method cells.
512     private void setUpMethodEditor(PropertyEditorTable table) {
513         //First, set up the button that brings up the dialog.
514         final JButton button = new JButton("invoking method") {
515             public void setText(String s) {
516                 //Button never shows text -- only color.
517             }
518         };
519         button.setBackground(Color.white);
520         button.setBorderPainted(false);
521         button.setMargin(new Insets(0,0,0,0));
522 
523         //Now create an editor to encapsulate the button, and
524         //set it up as the editor for all Color cells.
525         final MethodEditor methodEditor = new MethodEditor(button);
526         table.setDefaultEditor(Method.class, methodEditor);
527 
528         // handle the button-click
529         final PropertyEditorTable theTable = table;
530         button.addActionListener(new ActionListener() {
531             public void actionPerformed(ActionEvent e) {
532 
533                 Component parent = SwingUtilities.getRoot( theTable );
534                 if ( parent == null ) parent = theTable;
535 
536                 Cursor oldCursor = parent.getCursor();
537                 parent.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
538 
539                 Object result = null;
540                 Object inspectedObject = ((PropertyEditorTableModel)
541                           methodEditor.theTable.getModel()).inspectedObject;
542                 try
543                 {
544                     methodEditor.theMethod.setAccessible( true );
545                     result = methodEditor.theMethod.invoke(
546                         inspectedObject, (Object[])null );
547                 }
548                 catch ( Exception exc )
549                 {
550                     System.err.println( "PropertyEditorTable.MethodRenderer.actionPerformed: " +
551                         "Error occurred: " + exc );
552                 }
553                 theTable.methodInvoked( inspectedObject, methodEditor.theMethod, result );
554 
555                 parent.setCursor( oldCursor );
556             }
557         });
558     }
559 
560 /***
561 * Called by the method cell editor when a method is invoked.
562 * @param anObject The object upon which the method was invoked.
563 * @param aMethod The method that was invoked.
564 * @param aResult The result of the method invocation; may be null.
565 */
566     public void methodInvoked( Object anObject, Method aMethod, Object aResult )
567     {
568         System.out.println( aMethod.getName() + ": " + aResult );
569     }
570 }
571 
572