Coverage Report - net.wotonomy.ui.swing.components.PropertyEditorTable
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertyEditorTable
0% 
0% 
1.333
 
 1  0
 /*
 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  0
         super(null, null, null);
 76  0
     }
 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  0
         super(dm, null, null);
 89  0
     }
 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  0
         super(dm, cm, null);
 102  0
     }
 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  0
                 super( dm, cm, sm );
 123  0
         }
 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  0
                 super( numRows, numColumns );
 135  0
     }
 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  0
                 super( rowData, columnNames );
 153  0
         }        
 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  0
                 super( rowData, columnNames );
 170  0
         }
 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  0
                 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  0
                 TableModel model = getModel();
 196  0
                 if ( model instanceof PropertyEditorTableModel )
 197  0
                         return ((PropertyEditorTableModel)model).getCellClass( row, column );
 198  
                 else 
 199  0
                         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  0
         TableColumn tableColumn = getColumnModel().getColumn(column);
 216  0
         TableCellRenderer renderer = tableColumn.getCellRenderer();
 217  0
         if (renderer == null) {
 218  0
             renderer = getDefaultRenderer(getCellClass(row, column));
 219  
         }
 220  0
         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  0
         TableColumn tableColumn = getColumnModel().getColumn(column);
 238  0
         TableCellEditor editor = tableColumn.getCellEditor();
 239  0
         if (editor == null) {
 240  0
             editor = getDefaultEditor(getCellClass(row, column));
 241  
         }
 242  0
         return editor;
 243  
     }
 244  
 
 245  
         protected void createDefaultRenderers() {
 246  0
                 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  0
         DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() {
 260  0
             public void setValue(Object value) { 
 261  0
                                 setText( getFontDescription( (Font) value ) );
 262  0
                         }
 263  
                 };
 264  0
         fontRenderer.setHorizontalAlignment(JLabel.RIGHT);
 265  0
                 setDefaultRenderer(Font.class, fontRenderer); 
 266  
 
 267  0
                 setUpColorRenderer( this );
 268  0
                 setUpMethodRenderer( this );
 269  0
     }
 270  
 
 271  
         protected String getFontDescription( Font f ) {
 272  
                 String s;
 273  0
                 if ( f != null ) {
 274  0
                         s = f.getName();
 275  0
                         if ( f.isBold() ) s += " Bold";
 276  0
                         if ( f.isItalic() ) s += " Italic";
 277  0
                         s += " " + f.getSize();
 278  0
                 } else {
 279  0
                         s = "";
 280  
                 }
 281  0
                 return s;
 282  
         }
 283  
 
 284  
     protected void createDefaultEditors() {
 285  0
                 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  0
                 setUpColorEditor( this );
 300  0
         setUpMethodEditor( this );
 301  0
         }
 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  0
         Border unselectedBorder = null;
 310  0
         Border selectedBorder = null;
 311  0
         boolean isBordered = true;
 312  
 
 313  0
         public ColorRenderer(boolean isBordered) {
 314  0
             super();
 315  0
             this.isBordered = isBordered;
 316  0
             this.setOpaque(true); //MUST do this for background to show up.
 317  0
         }
 318  
         
 319  
         public Component getTableCellRendererComponent(
 320  
                                 JTable table, Object color, 
 321  
                                 boolean isSelected, boolean hasFocus,
 322  
                                 int row, int column) {
 323  0
             this.setBackground((Color)color);
 324  0
             if (isBordered) {
 325  0
                 if (isSelected) {
 326  0
                     if (selectedBorder == null) {
 327  0
                         selectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
 328  0
                                                   table.getSelectionBackground());
 329  
                     }
 330  0
                     this.setBorder(selectedBorder);
 331  0
                 } else {
 332  0
                     if (unselectedBorder == null) {
 333  0
                         unselectedBorder = BorderFactory.createMatteBorder(2,5,2,5,
 334  0
                                                   table.getBackground());
 335  
                     }
 336  0
                     this.setBorder(unselectedBorder);
 337  
                 }
 338  
             }
 339  0
             return this;
 340  
         }
 341  
     }
 342  
 
 343  
     private void setUpColorRenderer(JTable table) {
 344  0
         table.setDefaultRenderer(Color.class,
 345  0
                                  new ColorRenderer(true));
 346  0
     }
 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  0
         final JButton button = new JButton("") {
 352  0
             public void setText(String s) {
 353  
                 //Button never shows text -- only color.
 354  0
             }
 355  
         };
 356  0
         button.setBackground(Color.white);
 357  0
         button.setBorderPainted(false);
 358  0
         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  0
         final ColorEditor colorEditor = new ColorEditor(button);
 363  0
         table.setDefaultEditor(Color.class, colorEditor);
 364  
 
 365  
         //Set up the dialog that the button brings up.
 366  0
         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  0
         ActionListener okListener = new ActionListener() {
 373  0
             public void actionPerformed(ActionEvent e) {
 374  0
                 colorEditor.currentColor = colorChooser.getColor();
 375  0
             }
 376  
         };
 377  0
         final JDialog dialog = JColorChooser.createDialog(button,
 378  0
                                         "Pick a Color",
 379  0
                                         true,
 380  0
                                         colorChooser,
 381  0
                                         okListener,
 382  0
                                         null); //XXXDoublecheck this is OK
 383  
 
 384  
         //Here's the code that brings up the dialog.
 385  0
         button.addActionListener(new ActionListener() {
 386  0
             public void actionPerformed(ActionEvent e) {
 387  0
                 button.setBackground(colorEditor.currentColor);
 388  0
                 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  0
                 dialog.show();
 393  0
             }
 394  
         });
 395  0
     }
 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  0
         Color currentColor = null;
 407  
 
 408  0
         public ColorEditor(JButton b) {
 409  0
                 super(new JCheckBox()); //Unfortunately, the constructor
 410  
                                         //expects a check box, combo box,
 411  
                                         //or text field.
 412  0
             editorComponent = b;
 413  0
             setClickCountToStart(1); //This is usually 1 or 2.
 414  
 
 415  
             //Must do this so that editing stops when appropriate.
 416  0
             b.addActionListener(new ActionListener() {
 417  0
                 public void actionPerformed(ActionEvent e) {
 418  0
                     fireEditingStopped();
 419  0
                 }
 420  
             });
 421  0
         }
 422  
 
 423  
         protected void fireEditingStopped() {
 424  0
             super.fireEditingStopped();
 425  0
         }
 426  
 
 427  
         public Object getCellEditorValue() {
 428  0
             return currentColor;
 429  
         }
 430  
 
 431  
         public Component getTableCellEditorComponent(JTable table, 
 432  
                                                      Object value,
 433  
                                                      boolean isSelected,
 434  
                                                      int row,
 435  
                                                      int column) {
 436  0
             ((JButton)editorComponent).setText(value.toString());
 437  0
             currentColor = (Color)value;
 438  0
             return editorComponent;
 439  
         }
 440  
     }
 441  
 
 442  
     class MethodRenderer extends JLabel
 443  
                         implements TableCellRenderer {
 444  
 
 445  0
         Method theMethod = null;
 446  0
         JTable theTable = null;
 447  
 
 448  0
         public MethodRenderer() {
 449  0
             super();
 450  0
         }
 451  
 
 452  
         public Component getTableCellRendererComponent(
 453  
                                 JTable table, Object method,
 454  
                                 boolean isSelected, boolean hasFocus,
 455  
                                 int row, int column) {
 456  0
             theMethod = (Method) method;
 457  0
             theTable = table;
 458  0
             setText( "  " + theMethod.getReturnType().getName() );
 459  0
             return this;
 460  
         }
 461  
     }
 462  
 
 463  
     private void setUpMethodRenderer(JTable table) {
 464  0
         table.setDefaultRenderer(Method.class,
 465  0
                                  new MethodRenderer());
 466  0
     }
 467  
 
 468  
     /*
 469  
      * We extend DefaultCellEditor for convenience,
 470  
      * as with ColorEditor.
 471  
      */
 472  
     class MethodEditor extends DefaultCellEditor {
 473  0
         Method theMethod = null;
 474  0
         JTable theTable = null;
 475  
 
 476  0
         public MethodEditor(JButton b) {
 477  0
                 super(new JCheckBox()); //Unfortunately, the constructor
 478  
                                         //expects a check box, combo box,
 479  
                                         //or text field.
 480  0
             editorComponent = b;
 481  0
             setClickCountToStart(1); //This is usually 1 or 2.
 482  
 
 483  
             //Must do this so that editing stops when appropriate.
 484  0
             b.addActionListener(new ActionListener() {
 485  0
                 public void actionPerformed(ActionEvent e) {
 486  0
                     fireEditingStopped();
 487  0
                 }
 488  
             });
 489  0
         }
 490  
 
 491  
         protected void fireEditingStopped() {
 492  0
             super.fireEditingStopped();
 493  0
         }
 494  
 
 495  
         public Object getCellEditorValue() {
 496  0
             return theMethod;
 497  
         }
 498  
 
 499  
         public Component getTableCellEditorComponent(JTable table,
 500  
                                                      Object value,
 501  
                                                      boolean isSelected,
 502  
                                                      int row,
 503  
                                                      int column) {
 504  0
             ((JButton)editorComponent).setText(value.toString());
 505  0
             theMethod = (Method)value;
 506  0
             theTable = table;
 507  0
             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  0
         final JButton button = new JButton("invoking method") {
 515  0
             public void setText(String s) {
 516  
                 //Button never shows text -- only color.
 517  0
             }
 518  
         };
 519  0
         button.setBackground(Color.white);
 520  0
         button.setBorderPainted(false);
 521  0
         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  0
         final MethodEditor methodEditor = new MethodEditor(button);
 526  0
         table.setDefaultEditor(Method.class, methodEditor);
 527  
 
 528  
         // handle the button-click
 529  0
         final PropertyEditorTable theTable = table;
 530  0
         button.addActionListener(new ActionListener() {
 531  0
             public void actionPerformed(ActionEvent e) {
 532  
 
 533  0
                 Component parent = SwingUtilities.getRoot( theTable );
 534  0
                 if ( parent == null ) parent = theTable;
 535  
 
 536  0
                 Cursor oldCursor = parent.getCursor();
 537  0
                 parent.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
 538  
 
 539  0
                 Object result = null;
 540  0
                 Object inspectedObject = ((PropertyEditorTableModel)
 541  0
                           methodEditor.theTable.getModel()).inspectedObject;
 542  
                 try
 543  
                 {
 544  0
                     methodEditor.theMethod.setAccessible( true );
 545  0
                     result = methodEditor.theMethod.invoke(
 546  0
                         inspectedObject, (Object[])null );
 547  
                 }
 548  0
                 catch ( Exception exc )
 549  
                 {
 550  0
                     System.err.println( "PropertyEditorTable.MethodRenderer.actionPerformed: " +
 551  0
                         "Error occurred: " + exc );
 552  0
                 }
 553  0
                 theTable.methodInvoked( inspectedObject, methodEditor.theMethod, result );
 554  
 
 555  0
                 parent.setCursor( oldCursor );
 556  0
             }
 557  
         });
 558  0
     }
 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  0
         System.out.println( aMethod.getName() + ": " + aResult );
 569  0
     }
 570  
 }
 571  
 
 572