/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui.swing.components; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Font; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.reflect.Method; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.DefaultCellEditor; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JColorChooser; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; /** * PropertyEditorTable is a table designed to display and edit the properties of * an object. Because JTable assumes all cells in a column display the same data * type, we have to subclass to determine the class based on the cell contents. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 904 $ */ public class PropertyEditorTable extends JTable { // // Constructors // /** * Constructs a default JTable which is initialized with a default data model, a * default column model, and a default selection model. * * @see #createDefaultDataModel * @see #createDefaultColumnModel * @see #createDefaultSelectionModel */ public PropertyEditorTable() { super(null, null, null); } /** * Constructs a JTable which is initialized with dm as the data model, a * default column model, and a default selection model. * * @param dm The data model for the table * @see #createDefaultColumnModel * @see #createDefaultSelectionModel */ public PropertyEditorTable(TableModel dm) { super(dm, null, null); } /** * Constructs a JTable which is initialized with dm as the data model, * cm as the column model, and a default selection model. * * @param dm The data model for the table * @param cm The column model for the table * @see #createDefaultSelectionModel */ public PropertyEditorTable(TableModel dm, TableColumnModel cm) { super(dm, cm, null); } /** * Constructs a JTable which is initialized with dm as the data model, * cm as the column model, and sm as the selection model. If any * of the parameters are null this method will initialize the table with * the corresponding default model. The autoCreateColumnsFromModel flag * is set to false if cm is non-null, otherwise it is set to true and the * column model is populated with suitable TableColumns for the columns in * dm. * * @param dm The data model for the table * @param cm The column model for the table * @param sm The row selection model for the table * @see #createDefaultDataModel * @see #createDefaultColumnModel * @see #createDefaultSelectionModel */ public PropertyEditorTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) { super(dm, cm, sm); } /** * Constructs a JTable with numRows and numColumns of empty cells * using the DefaultTableModel. The columns will have names of the form "A", * "B", "C", etc. * * @param numRows The number of rows the table holds * @param numColumns The number of columns the table holds */ public PropertyEditorTable(int numRows, int numColumns) { super(numRows, numColumns); } /** * Constructs a JTable to display the values in the Vector of Vectors, * rowData, with column names, columnNames. The Vectors contained * in rowData should contain the values for that row. In other words, the * value of the cell at row 1, column 5 can be obtained with the following code: *
* *
* ((Vector) rowData.elementAt(1)).elementAt(5); **
* All rows must be of the same length as columnNames. *
* * @param rowData The data for the new table * @param columnNames Names of each column */ public PropertyEditorTable(final Vector rowData, final Vector columnNames) { super(rowData, columnNames); } /** * Constructs a JTable to display the values in the two dimensional array, * rowData, with column names, columnNames. rowData is an * Array of rows, so the value of the cell at row 1, column 5 can be obtained * with the following code: *
* *
* rowData[1][5]; **
* All rows must be of the same length as columnNames. *
* * @param rowData The data for the new table * @param columnNames Names of each column */ public PropertyEditorTable(final Object[][] rowData, final Object[] columnNames) { super(rowData, columnNames); } /** * Returns the type of the column at the specified view position. * * @return the type of the column at position column in the view where * the first column is column 0. * * Modified mln: now a wrapper for getCellClass() * */ public Class getColumnClass(int column) { return getCellClass(0, column); } /** * Returns the type of the cell at the specified view position. * * @return the type of the cell at position row, column in the * view where the first column is column 0. * * Modified mln: new methods * */ public Class getCellClass(int row, int column) { TableModel model = getModel(); if (model instanceof PropertyEditorTableModel) return ((PropertyEditorTableModel) model).getCellClass(row, column); else return model.getColumnClass(convertColumnIndexToModel(column)); } /** * Return an appropriate renderer for the cell specified by this this row and * column. If the TableColumn for this column has a non-null renderer, return * that. If not, find the class of the data in this column (using * getColumnClass()) and return the default renderer for this type of data. * * @param row the row of the cell to render, where 0 is the first * @param column the column of the cell to render, where 0 is the first * * Modified mln: calls getCellClass if there's no column model * */ public TableCellRenderer getCellRenderer(int row, int column) { TableColumn tableColumn = getColumnModel().getColumn(column); TableCellRenderer renderer = tableColumn.getCellRenderer(); if (renderer == null) { renderer = getDefaultRenderer(getCellClass(row, column)); } return renderer; } /** * Return an appropriate editor for the cell specified by this this row and * column. If the TableColumn for this column has a non-null editor, return * that. If not, find the class of the data in this column (using * getColumnClass()) and return the default editor for this type of data. * * @param row the row of the cell to edit, where 0 is the first * @param column the column of the cell to edit, where 0 is the first * * Modified mp: calls getCellClass if there's no column model * */ public TableCellEditor getCellEditor(int row, int column) { TableColumn tableColumn = getColumnModel().getColumn(column); TableCellEditor editor = tableColumn.getCellEditor(); if (editor == null) { editor = getDefaultEditor(getCellClass(row, column)); } return editor; } protected void createDefaultRenderers() { super.createDefaultRenderers(); /* * // copying this code here as a sample of creating a renderer // Dates * DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() { * DateFormat formatter = DateFormat.getDateInstance(); public void * setValue(Object value) { setText((value == null) ? "" : * formatter.format(value)); } }; * dateRenderer.setHorizontalAlignment(JLabel.RIGHT); * setDefaultRenderer(Date.class, dateRenderer); */ DefaultTableCellRenderer fontRenderer = new DefaultTableCellRenderer() { public void setValue(Object value) { setText(getFontDescription((Font) value)); } }; fontRenderer.setHorizontalAlignment(JLabel.RIGHT); setDefaultRenderer(Font.class, fontRenderer); setUpColorRenderer(this); setUpMethodRenderer(this); } protected String getFontDescription(Font f) { String s; if (f != null) { s = f.getName(); if (f.isBold()) s += " Bold"; if (f.isItalic()) s += " Italic"; s += " " + f.getSize(); } else { s = ""; } return s; } protected void createDefaultEditors() { super.createDefaultEditors(); /* * // copying this code here as a sample of creating an editor // Numbers * JTextField rightAlignedTextField = new JTextField(); * rightAlignedTextField.setHorizontalAlignment(JTextField.RIGHT); * rightAlignedTextField.setBorder(new LineBorder(Color.black)); * setDefaultEditor(Number.class, new DefaultCellEditor(rightAlignedTextField)); * * // Booleans JCheckBox centeredCheckBox = new JCheckBox(); * centeredCheckBox.setHorizontalAlignment(JCheckBox.CENTER); * setDefaultEditor(Boolean.class, new DefaultCellEditor(centeredCheckBox)); */ setUpColorEditor(this); setUpMethodEditor(this); } // following code lifted from: // http://java.sun.com/docs/books/tutorial/ui/swing/example-swing/TableDialogEditDemo.java class ColorRenderer extends JLabel implements TableCellRenderer { Border unselectedBorder = null; Border selectedBorder = null; boolean isBordered = true; public ColorRenderer(boolean isBordered) { super(); this.isBordered = isBordered; this.setOpaque(true); // MUST do this for background to show up. } public Component getTableCellRendererComponent(JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { this.setBackground((Color) color); if (isBordered) { if (isSelected) { if (selectedBorder == null) { selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getSelectionBackground()); } this.setBorder(selectedBorder); } else { if (unselectedBorder == null) { unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5, table.getBackground()); } this.setBorder(unselectedBorder); } } return this; } } private void setUpColorRenderer(JTable table) { table.setDefaultRenderer(Color.class, new ColorRenderer(true)); } // Set up the editor for the Color cells. private void setUpColorEditor(JTable table) { // First, set up the button that brings up the dialog. final JButton button = new JButton("") { public void setText(String s) { // Button never shows text -- only color. } }; button.setBackground(Color.white); button.setBorderPainted(false); button.setMargin(new Insets(0, 0, 0, 0)); // Now create an editor to encapsulate the button, and // set it up as the editor for all Color cells. final ColorEditor colorEditor = new ColorEditor(button); table.setDefaultEditor(Color.class, colorEditor); // Set up the dialog that the button brings up. final JColorChooser colorChooser = new JColorChooser(); // XXX: PENDING: add the following when setPreviewPanel // XXX: starts working. // JComponent preview = new ColorRenderer(false); // preview.setPreferredSize(new Dimension(50, 10)); // colorChooser.setPreviewPanel(preview); ActionListener okListener = new ActionListener() { public void actionPerformed(ActionEvent e) { colorEditor.currentColor = colorChooser.getColor(); } }; final JDialog dialog = JColorChooser.createDialog(button, "Pick a Color", true, colorChooser, okListener, null); // XXXDoublecheck // this // is // OK // Here's the code that brings up the dialog. button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { button.setBackground(colorEditor.currentColor); colorChooser.setColor(colorEditor.currentColor); // Without the following line, the dialog comes up // in the middle of the screen. // dialog.setLocationRelativeTo(button); dialog.show(); } }); } /* * The editor button that brings up the dialog. We extend DefaultCellEditor for * convenience, even though it mean we have to create a dummy check box. Another * approach would be to copy the implementation of TableCellEditor methods from * the source code for DefaultCellEditor. */ class ColorEditor extends DefaultCellEditor { Color currentColor = null; public ColorEditor(JButton b) { super(new JCheckBox()); // Unfortunately, the constructor // expects a check box, combo box, // or text field. editorComponent = b; setClickCountToStart(1); // This is usually 1 or 2. // Must do this so that editing stops when appropriate. b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fireEditingStopped(); } }); } protected void fireEditingStopped() { super.fireEditingStopped(); } public Object getCellEditorValue() { return currentColor; } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { ((JButton) editorComponent).setText(value.toString()); currentColor = (Color) value; return editorComponent; } } class MethodRenderer extends JLabel implements TableCellRenderer { Method theMethod = null; JTable theTable = null; public MethodRenderer() { super(); } public Component getTableCellRendererComponent(JTable table, Object method, boolean isSelected, boolean hasFocus, int row, int column) { theMethod = (Method) method; theTable = table; setText(" " + theMethod.getReturnType().getName()); return this; } } private void setUpMethodRenderer(JTable table) { table.setDefaultRenderer(Method.class, new MethodRenderer()); } /* * We extend DefaultCellEditor for convenience, as with ColorEditor. */ class MethodEditor extends DefaultCellEditor { Method theMethod = null; JTable theTable = null; public MethodEditor(JButton b) { super(new JCheckBox()); // Unfortunately, the constructor // expects a check box, combo box, // or text field. editorComponent = b; setClickCountToStart(1); // This is usually 1 or 2. // Must do this so that editing stops when appropriate. b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fireEditingStopped(); } }); } protected void fireEditingStopped() { super.fireEditingStopped(); } public Object getCellEditorValue() { return theMethod; } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { ((JButton) editorComponent).setText(value.toString()); theMethod = (Method) value; theTable = table; return editorComponent; } } // Set up the editor for the Method cells. private void setUpMethodEditor(PropertyEditorTable table) { // First, set up the button that brings up the dialog. final JButton button = new JButton("invoking method") { public void setText(String s) { // Button never shows text -- only color. } }; button.setBackground(Color.white); button.setBorderPainted(false); button.setMargin(new Insets(0, 0, 0, 0)); // Now create an editor to encapsulate the button, and // set it up as the editor for all Color cells. final MethodEditor methodEditor = new MethodEditor(button); table.setDefaultEditor(Method.class, methodEditor); // handle the button-click final PropertyEditorTable theTable = table; button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Component parent = SwingUtilities.getRoot(theTable); if (parent == null) parent = theTable; Cursor oldCursor = parent.getCursor(); parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); Object result = null; Object inspectedObject = ((PropertyEditorTableModel) methodEditor.theTable.getModel()).inspectedObject; try { methodEditor.theMethod.setAccessible(true); result = methodEditor.theMethod.invoke(inspectedObject, (Object[]) null); } catch (Exception exc) { System.err .println("PropertyEditorTable.MethodRenderer.actionPerformed: " + "Error occurred: " + exc); } theTable.methodInvoked(inspectedObject, methodEditor.theMethod, result); parent.setCursor(oldCursor); } }); } /** * Called by the method cell editor when a method is invoked. * * @param anObject The object upon which the method was invoked. * @param aMethod The method that was invoked. * @param aResult The result of the method invocation; may be null. */ public void methodInvoked(Object anObject, Method aMethod, Object aResult) { System.out.println(aMethod.getName() + ": " + aResult); } }