JXTable.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:81k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: JXTable.java,v 1.79 2005/10/12 13:57:01 kleopatra Exp $
  3.  *
  4.  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.swingx;
  22. import java.awt.Component;
  23. import java.awt.ComponentOrientation;
  24. import java.awt.Container;
  25. import java.awt.Cursor;
  26. import java.awt.Dimension;
  27. import java.awt.Point;
  28. import java.awt.Rectangle;
  29. import java.awt.event.ActionEvent;
  30. import java.awt.event.ActionListener;
  31. import java.awt.print.PrinterException;
  32. import java.beans.PropertyChangeEvent;
  33. import java.beans.PropertyChangeListener;
  34. import java.lang.reflect.Field;
  35. import java.text.DateFormat;
  36. import java.text.NumberFormat;
  37. import java.util.Collections;
  38. import java.util.Date;
  39. import java.util.Enumeration;
  40. import java.util.HashMap;
  41. import java.util.Hashtable;
  42. import java.util.Iterator;
  43. import java.util.List;
  44. import java.util.Map;
  45. import java.util.Vector;
  46. import java.util.regex.Matcher;
  47. import java.util.regex.Pattern;
  48. import javax.swing.AbstractButton;
  49. import javax.swing.Action;
  50. import javax.swing.ActionMap;
  51. import javax.swing.DefaultCellEditor;
  52. import javax.swing.Icon;
  53. import javax.swing.ImageIcon;
  54. import javax.swing.JCheckBox;
  55. import javax.swing.JComponent;
  56. import javax.swing.JLabel;
  57. import javax.swing.JScrollPane;
  58. import javax.swing.JTable;
  59. import javax.swing.JViewport;
  60. import javax.swing.KeyStroke;
  61. import javax.swing.ListCellRenderer;
  62. import javax.swing.ListSelectionModel;
  63. import javax.swing.ScrollPaneConstants;
  64. import javax.swing.SizeSequence;
  65. import javax.swing.UIDefaults;
  66. import javax.swing.UIManager;
  67. import javax.swing.event.ChangeEvent;
  68. import javax.swing.event.ChangeListener;
  69. import javax.swing.event.ListSelectionEvent;
  70. import javax.swing.event.TableColumnModelEvent;
  71. import javax.swing.event.TableModelEvent;
  72. import javax.swing.table.DefaultTableCellRenderer;
  73. import javax.swing.table.JTableHeader;
  74. import javax.swing.table.TableCellEditor;
  75. import javax.swing.table.TableCellRenderer;
  76. import javax.swing.table.TableColumn;
  77. import javax.swing.table.TableColumnModel;
  78. import javax.swing.table.TableModel;
  79. import org.jdesktop.swingx.action.BoundAction;
  80. import org.jdesktop.swingx.decorator.ComponentAdapter;
  81. import org.jdesktop.swingx.decorator.FilterPipeline;
  82. import org.jdesktop.swingx.decorator.Highlighter;
  83. import org.jdesktop.swingx.decorator.HighlighterPipeline;
  84. import org.jdesktop.swingx.decorator.PatternHighlighter;
  85. import org.jdesktop.swingx.decorator.PipelineEvent;
  86. import org.jdesktop.swingx.decorator.PipelineListener;
  87. import org.jdesktop.swingx.decorator.RowSizing;
  88. import org.jdesktop.swingx.decorator.SearchHighlighter;
  89. import org.jdesktop.swingx.decorator.Selection;
  90. import org.jdesktop.swingx.decorator.Sorter;
  91. import org.jdesktop.swingx.icon.ColumnControlIcon;
  92. import org.jdesktop.swingx.plaf.LookAndFeelAddons;
  93. import org.jdesktop.swingx.table.ColumnControlButton;
  94. import org.jdesktop.swingx.table.ColumnFactory;
  95. import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
  96. import org.jdesktop.swingx.table.TableColumnExt;
  97. import org.jdesktop.swingx.table.TableColumnModelExt;
  98. /**
  99.  * <p>
  100.  * A JXTable is a JTable with built-in support for row sorting, filtering, and
  101.  * highlighting, column visibility and a special popup control on the column
  102.  * header for quick access to table configuration. You can instantiate a JXTable
  103.  * just as you would a JTable, using a TableModel. However, a JXTable
  104.  * automatically wraps TableColumns inside a TableColumnExt instance.
  105.  * TableColumnExt supports visibility, sortability, and prototype values for
  106.  * column sizing, none of which are available in TableColumn. You can retrieve
  107.  * the TableColumnExt instance for a column using {@link #getColumnExt(Object)}
  108.  * or {@link #getColumnExt(int colnumber)}.
  109.  * 
  110.  * <p>
  111.  * A JXTable is, by default, sortable by clicking on column headers; each
  112.  * subsequent click on a header reverses the order of the sort, and a sort arrow
  113.  * icon is automatically drawn on the header. Sorting can be disabled using
  114.  * {@link #setSortable(boolean)}. Sorting on columns is handled by a Sorter
  115.  * instance which contains a Comparator used to compare values in two rows of a
  116.  * column. You can replace the Comparator for a given column by using
  117.  * <code>getColumnExt("column").getSorter().setComparator(customComparator)</code>
  118.  * 
  119.  * <p>
  120.  * Columns can be hidden or shown by setting the visible property on the
  121.  * TableColumnExt using {@link TableColumnExt#setVisible(boolean)}. Columns can
  122.  * also be shown or hidden from the column control popup.
  123.  * 
  124.  * <p>
  125.  * The column control popup is triggered by an icon drawn to the far right of
  126.  * the column headers, above the table's scrollbar (when installed in a
  127.  * JScrollPane). The popup allows the user to select which columns should be
  128.  * shown or hidden, as well as to pack columns and turn on horizontal scrolling.
  129.  * To show or hide the column control, use the
  130.  * {@link #setColumnControlVisible(boolean show)}method.
  131.  * 
  132.  * <p>
  133.  * Rows can be filtered from a JXTable using a Filter class and a
  134.  * FilterPipeline. One assigns a FilterPipeline to the table using
  135.  * {@link #setFilters(FilterPipeline)}. Filtering hides, but does not delete or
  136.  * permanently remove rows from a JXTable. Filters are used to provide sorting
  137.  * to the table--rows are not removed, but the table is made to believe rows in
  138.  * the model are in a sorted order.
  139.  * 
  140.  * <p>
  141.  * One can automatically highlight certain rows in a JXTable by attaching
  142.  * Highlighters in the {@link #setHighlighters(HighlighterPipeline)}method. An
  143.  * example would be a Highlighter that colors alternate rows in the table for
  144.  * readability; AlternateRowHighlighter does this. Again, like Filters,
  145.  * Highlighters can be chained together in a HighlighterPipeline to achieve more
  146.  * interesting effects.
  147.  * 
  148.  * <p>
  149.  * You can resize all columns, selected columns, or a single column using the
  150.  * methods like {@link #packAll()}. Packing combines several other aspects of a
  151.  * JXTable. If horizontal scrolling is enabled using
  152.  * {@link #setHorizontalScrollEnabled(boolean)}, then the scrollpane will allow
  153.  * the table to scroll right-left, and columns will be sized to their preferred
  154.  * size. To control the preferred sizing of a column, you can provide a
  155.  * prototype value for the column in the TableColumnExt using
  156.  * {@link TableColumnExt#setPrototypeValue(Object)}. The prototype is used as
  157.  * an indicator of the preferred size of the column. This can be useful if some
  158.  * data in a given column is very long, but where the resize algorithm would
  159.  * normally not pick this up.
  160.  * 
  161.  * <p>
  162.  * Last, you can also provide searches on a JXTable using the Searchable property.
  163.  * 
  164.  * <p>
  165.  * Keys/Actions registered with this component:
  166.  * 
  167.  * <ul>
  168.  * <li> "find" - open an appropriate search widget for searching cell content. The
  169.  *   default action registeres itself with the SearchFactory as search target.
  170.  * <li> "print" - print the table
  171.  * <li> {@link JXTable#HORIZONTAL_ACTION_COMMAND} - toggle the horizontal scrollbar
  172.  * <li> {@link JXTable#PACKSELECTED_ACTION_COMMAND} - resize the selected column to fit the widest
  173.  *  cell content 
  174.  * <li> {@link JXTable#PACKALL_ACTION_COMMAND} - resize all columns to fit the widest
  175.  *  cell content in each column
  176.  * 
  177.  * </ul>
  178.  * 
  179.  * <p>
  180.  * Key bindings.
  181.  * 
  182.  * <ul>
  183.  * <li> "control F" - bound to actionKey "find".
  184.  * </ul>
  185.  * 
  186.  * <p>
  187.  * Client Properties.
  188.  * 
  189.  * <ul>
  190.  * <li> {@link JXTable#MATCH_HIGHLIGHTER} - set to Boolean.TRUE to 
  191.  *  use a SearchHighlighter to mark a cell as matching.
  192.  * </ul>
  193.  * 
  194.  * @author Ramesh Gupta
  195.  * @author Amy Fowler
  196.  * @author Mark Davidson
  197.  * @author Jeanette Winzenburg
  198.  */
  199. public class JXTable extends JTable { 
  200.     /**
  201.      * Constant string for horizontal scroll actions, used in JXTable's Action
  202.      * Map.
  203.      */
  204.     public static final String HORIZONTALSCROLL_ACTION_COMMAND = 
  205.         ColumnControlButton.COLUMN_CONTROL_MARKER + "horizontalScroll";
  206.     /** Constant string for packing all columns, used in JXTable's Action Map. */
  207.     public static final String PACKALL_ACTION_COMMAND = 
  208.         ColumnControlButton.COLUMN_CONTROL_MARKER + "packAll";
  209.     /**
  210.      * Constant string for packing selected columns, used in JXTable's Action
  211.      * Map.
  212.      */
  213.     public static final String PACKSELECTED_ACTION_COMMAND = 
  214.         ColumnControlButton.COLUMN_CONTROL_MARKER + "packSelected";
  215.     /** The prefix marker to find component related properties in the resourcebundle. */
  216.     public static final String UIPREFIX = "JXTable.";
  217.     /** key for client property to use SearchHighlighter as match marker. */
  218.     public static final String MATCH_HIGHLIGHTER = AbstractSearchable.MATCH_HIGHLIGHTER;
  219.     static {
  220.         // Hack: make sure the resource bundle is loaded
  221.         LookAndFeelAddons.getAddon();
  222.     }
  223.     /** The FilterPipeline for the table. */
  224.     protected FilterPipeline filters;
  225.     /** The HighlighterPipeline for the table. */
  226.     protected HighlighterPipeline highlighters;
  227.     /** The ComponentAdapter for model data access. */
  228.     protected ComponentAdapter dataAdapter;
  229.     /** The handler for mapping view/model coordinates of row selection. */
  230.     private Selection selection;
  231.     /** flag to indicate if table is interactively sortable. */
  232.     private boolean sortable;
  233.     /** future - enable/disable autosort on cell updates not used */
  234. //    private boolean automaticSortDisabled;
  235.     /** Listens for changes from the filters. */
  236.     private PipelineListener pipelineListener;
  237.     /** Listens for changes from the highlighters. */
  238.     private ChangeListener highlighterChangeListener;
  239.     /** the factory to use for column creation and configuration. */
  240.     private ColumnFactory columnFactory;
  241.     /** The default number of visible rows (in a ScrollPane). */
  242.     private int visibleRowCount = 18;
  243.     private RowSizing rowSizing;
  244.     private Field rowModelField;
  245.     private boolean rowHeightEnabled;
  246.     /**
  247.      * flag to indicate if the column control is visible.
  248.      */
  249.     private boolean columnControlVisible;
  250.     /**
  251.      * ScrollPane's original vertical scroll policy. If the columnControl is
  252.      * visible the policy is set to ALWAYS.
  253.      */
  254.     private int verticalScrollPolicy;
  255.     /**
  256.      * A button that allows the user to select which columns to display, and
  257.      * which to hide
  258.      */
  259.     private JComponent columnControlButton;
  260.     /**
  261.      * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
  262.      */
  263.     private RolloverProducer rolloverProducer;
  264.     /**
  265.      * RolloverController: listens to cell over events and repaints
  266.      * entered/exited rows.
  267.      */
  268.     private LinkController linkController;
  269.     /** field to store the autoResizeMode while interactively setting 
  270.      *  horizontal scrollbar to visible.
  271.      */
  272.     private int oldAutoResizeMode;
  273.     /** temporary hack: rowheight will be internally adjusted to font size 
  274.      *  on instantiation and in updateUI if 
  275.      *  the height has not been set explicitly by the application.
  276.      */
  277.     protected boolean isXTableRowHeightSet;
  278.     protected Searchable searchable;
  279.     /** Instantiates a JXTable with a default table model, no data. */
  280.     public JXTable() {
  281.         init();
  282.     }
  283.     /**
  284.      * Instantiates a JXTable with a specific table model.
  285.      * 
  286.      * @param dm
  287.      *            The model to use.
  288.      */
  289.     public JXTable(TableModel dm) {
  290.         super(dm);
  291.         init();
  292.     }
  293.     /**
  294.      * Instantiates a JXTable with a specific table model.
  295.      * 
  296.      * @param dm
  297.      *            The model to use.
  298.      */
  299.     public JXTable(TableModel dm, TableColumnModel cm) {
  300.         super(dm, cm);
  301.         init();
  302.     }
  303.     /**
  304.      * Instantiates a JXTable with a specific table model, column model, and
  305.      * selection model.
  306.      * 
  307.      * @param dm
  308.      *            The table model to use.
  309.      * @param cm
  310.      *            The colomn model to use.
  311.      * @param sm
  312.      *            The list selection model to use.
  313.      */
  314.     public JXTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
  315.         super(dm, cm, sm);
  316.         init();
  317.     }
  318.     /**
  319.      * Instantiates a JXTable for a given number of columns and rows.
  320.      * 
  321.      * @param numRows
  322.      *            Count of rows to accomodate.
  323.      * @param numColumns
  324.      *            Count of columns to accomodate.
  325.      */
  326.     public JXTable(int numRows, int numColumns) {
  327.         super(numRows, numColumns);
  328.         init();
  329.     }
  330.     /**
  331.      * Instantiates a JXTable with data in a vector or rows and column names.
  332.      * 
  333.      * @param rowData
  334.      *            Row data, as a Vector of Objects.
  335.      * @param columnNames
  336.      *            Column names, as a Vector of Strings.
  337.      */
  338.     public JXTable(Vector rowData, Vector columnNames) {
  339.         super(rowData, columnNames);
  340.         init();
  341.     }
  342.     /**
  343.      * Instantiates a JXTable with data in a array or rows and column names.
  344.      * 
  345.      * @param rowData
  346.      *            Row data, as a two-dimensional Array of Objects (by row, for
  347.      *            column).
  348.      * @param columnNames
  349.      *            Column names, as a Array of Strings.
  350.      */
  351.     public JXTable(Object[][] rowData, Object[] columnNames) {
  352.         super(rowData, columnNames);
  353.         init();
  354.     }
  355.     /** Initializes the table for use. */
  356.     protected void init() {
  357.         setSortable(true);
  358.         // guarantee getFilters() to return != null
  359.         setFilters(null);
  360.         initActionsAndBindings();
  361.         // instantiate row height depending on font size
  362.         updateRowHeightUI(false);
  363.     }
  364.     /**
  365.      * Property to enable/disable rollover support. This can be enabled to show
  366.      * "live" rollover behaviour, f.i. the cursor over LinkModel cells. Default
  367.      * is disabled. If using a RolloverHighlighter on the table, this should be
  368.      * set to true.
  369.      * 
  370.      * @param rolloverEnabled
  371.      */
  372.     public void setRolloverEnabled(boolean rolloverEnabled) {
  373.         boolean old = isRolloverEnabled();
  374.         if (rolloverEnabled == old)
  375.             return;
  376.         if (rolloverEnabled) {
  377.             rolloverProducer = createRolloverProducer();
  378.             addMouseListener(rolloverProducer);
  379.             addMouseMotionListener(rolloverProducer);
  380.             linkController = new LinkController();
  381.             addPropertyChangeListener(linkController);
  382.         } else {
  383.             removeMouseListener(rolloverProducer);
  384.             removeMouseMotionListener(rolloverProducer);
  385.             rolloverProducer = null;
  386.             removePropertyChangeListener(linkController);
  387.             linkController = null;
  388.         }
  389.         firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
  390.     }
  391.     /**
  392.      * creates and returns the RolloverProducer to use.
  393.      * 
  394.      * @return
  395.      */
  396.     protected RolloverProducer createRolloverProducer() {
  397.         RolloverProducer r = new RolloverProducer() {
  398.             protected void updateRolloverPoint(JComponent component,
  399.                     Point mousePoint) {
  400.                 JXTable table = (JXTable) component;
  401.                 int col = table.columnAtPoint(mousePoint);
  402.                 int row = table.rowAtPoint(mousePoint);
  403.                 if ((col < 0) || (row < 0)) {
  404.                     row = -1;
  405.                     col = -1;
  406.                 }
  407.                 rollover.x = col;
  408.                 rollover.y = row;
  409.             }
  410.         };
  411.         return r;
  412.     }
  413.     /**
  414.      * Returns the rolloverEnabled property.
  415.      * 
  416.      * @return <code>true</code> if rollover is enabled
  417.      */
  418.     public boolean isRolloverEnabled() {
  419.         return rolloverProducer != null;
  420.     }
  421.     /**
  422.      * If the default editor for LinkModel.class is of type LinkRenderer enables
  423.      * link visiting with the given linkVisitor. As a side-effect the rollover
  424.      * property is set to true.
  425.      * 
  426.      * @param linkVisitor
  427.      */
  428.     public void setDefaultLinkVisitor(ActionListener linkVisitor) {
  429. //        TableCellEditor editor = getDefaultEditor(LinkModel.class);
  430. //        if (editor instanceof LinkRenderer) {
  431. //            ((LinkRenderer) editor).setVisitingDelegate(linkVisitor);
  432. //        }
  433.         TableCellRenderer renderer = getDefaultRenderer(LinkModel.class);
  434.         if (renderer instanceof LinkRenderer) {
  435.             ((LinkRenderer) renderer).setVisitingDelegate(linkVisitor);
  436.         }
  437.         setRolloverEnabled(true);
  438.     }
  439.     /**
  440.      * listens to rollover properties. 
  441.      * Repaints effected component regions.
  442.      * Updates link cursor.
  443.      * 
  444.      * @author Jeanette Winzenburg
  445.      */
  446.     public  class LinkController implements PropertyChangeListener {
  447.         private Cursor oldCursor;
  448.         public void propertyChange(PropertyChangeEvent evt) {
  449.             if (RolloverProducer.ROLLOVER_KEY.equals(evt.getPropertyName())) {
  450.                rollover((JXTable) evt.getSource(), (Point) evt
  451.                             .getOldValue(), (Point) evt.getNewValue());
  452.             } else if (RolloverProducer.CLICKED_KEY.equals(evt.getPropertyName())) {
  453.                 click((JXTable) evt.getSource(), (Point) evt.getOldValue(),
  454.                         (Point) evt.getNewValue());
  455.             }
  456.         }
  457. //    --------------------------- JTable rollover
  458.         
  459.         private void rollover(JXTable table, Point oldLocation, Point newLocation) {
  460.             if (oldLocation != null) {
  461.                 Rectangle r = table.getCellRect(oldLocation.y, oldLocation.x, false);
  462.                 r.x = 0;
  463.                 r.width = table.getWidth();
  464.                 table.repaint(r);
  465.             }
  466.             if (newLocation != null) {
  467.                 Rectangle r = table.getCellRect(newLocation.y, newLocation.x, false);
  468.                 r.x = 0;
  469.                 r.width = table.getWidth();
  470.                 table.repaint(r);
  471.             }
  472.             setLinkCursor(table, newLocation);
  473.         }
  474.         private void click(JXTable list, Point oldLocation, Point newLocation) {
  475.             if (!isLinkColumn(list, newLocation)) return;
  476.             TableCellRenderer renderer = list.getCellRenderer(newLocation.y, newLocation.x);
  477.             // PENDING: JW - don't ask the model, ask the list!
  478.             Component comp = list.prepareRenderer(renderer, newLocation.y,  newLocation.x);
  479.             if (comp instanceof AbstractButton) {
  480.                 // this is fishy - needs to be removed as soon as JList is editable
  481.                 ((AbstractButton) comp).doClick();
  482.                 list.repaint();
  483.             }
  484.         }
  485.         private void setLinkCursor(JXTable table, Point location) {
  486.             if (isLinkColumn(table, location)) {
  487.                 if (oldCursor == null) {
  488.                     oldCursor = table.getCursor();
  489.                     table.setCursor(Cursor
  490.                             .getPredefinedCursor(Cursor.HAND_CURSOR));
  491.                 }
  492.             } else {
  493.                 if (oldCursor != null) {
  494.                     table.setCursor(oldCursor);
  495.                     oldCursor = null;
  496.                 }
  497.             }
  498.         }
  499.         private boolean isLinkColumn(JXTable table, Point location) {
  500.             if (location == null || location.x < 0) return false;
  501.             return (table.getColumnClass(location.x) == LinkModel.class);
  502.         }
  503.     }
  504.     
  505. //--------------------------------- ColumnControl
  506.     
  507.     /**
  508.      * overridden to addionally configure the upper right corner of an enclosing
  509.      * scrollpane with the ColumnControl.
  510.      */
  511.     protected void configureEnclosingScrollPane() {
  512.         super.configureEnclosingScrollPane();
  513.         configureColumnControl();
  514.         configureViewportBackground();
  515.     }
  516.     /**
  517.      * set's the viewports background to this.background.<p> 
  518.      * 
  519.      * PENDING: need to
  520.      * repeat on background changes to this!
  521.      * 
  522.      */
  523.     protected void configureViewportBackground() {
  524.         Container p = getParent();
  525.         if (p instanceof JViewport) {
  526.             p.setBackground(getBackground());
  527.         }
  528.     }
  529.     /**
  530.      * configure the upper right corner of an enclosing scrollpane with/o the
  531.      * ColumnControl, depending on setting of columnControl visibility flag.<p>
  532.      * 
  533.      * PENDING: should choose corner depending on component orientation.
  534.      */
  535.     private void configureColumnControl() {
  536.         Container p = getParent();
  537.         if (p instanceof JViewport) {
  538.             Container gp = p.getParent();
  539.             if (gp instanceof JScrollPane) {
  540.                 JScrollPane scrollPane = (JScrollPane) gp;
  541.                 // Make certain we are the viewPort's view and not, for
  542.                 // example, the rowHeaderView of the scrollPane -
  543.                 // an implementor of fixed columns might do this.
  544.                 JViewport viewport = scrollPane.getViewport();
  545.                 if (viewport == null || viewport.getView() != this) {
  546.                     return;
  547.                 }
  548.                 if (isColumnControlVisible()) {
  549.                     verticalScrollPolicy = scrollPane
  550.                             .getVerticalScrollBarPolicy();
  551.                     scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
  552.                             getColumnControl());
  553.                     scrollPane
  554.                             .setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
  555.                 } else {
  556.                     if (verticalScrollPolicy != 0) {
  557.                         // Fix #155-swingx: reset only if we had force always before
  558.                         // PENDING: JW - doesn't cope with dynamically changing the policy
  559.                         // shouldn't be much of a problem because doesn't happen too often?? 
  560.                         scrollPane.setVerticalScrollBarPolicy(verticalScrollPolicy);
  561.                     }
  562.                     try {
  563.                         scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
  564.                                 null);
  565.                     } catch (Exception ex) {
  566.                         // Ignore spurious exception thrown by JScrollPane. This
  567.                         // is a Swing bug!
  568.                     }
  569.                 }
  570.             }
  571.         }
  572.     }
  573.     /**
  574.      * Hack around core swing JScrollPane bug: can't cope with
  575.      * corners when changing component orientation at runtime.
  576.      * overridden to re-configure the columnControl.
  577.      */
  578.     @Override
  579.     public void setComponentOrientation(ComponentOrientation o) {
  580.         super.setComponentOrientation(o);
  581.         configureColumnControl();
  582.     }
  583.     /**
  584.      * returns visibility flag of column control.
  585.      * <p>
  586.      * 
  587.      * Note: if the table is not inside a JScrollPane the column control is not
  588.      * shown even if this returns true. In this case it's the responsibility of
  589.      * the client code to actually show it.
  590.      * 
  591.      * @return
  592.      */
  593.     public boolean isColumnControlVisible() {
  594.         return columnControlVisible;
  595.     }
  596.     /**
  597.      * returns the component for column control.
  598.      * 
  599.      * @return
  600.      */
  601.     public JComponent getColumnControl() {
  602.         if (columnControlButton == null) {
  603.             columnControlButton = new ColumnControlButton(this,
  604.                     new ColumnControlIcon());
  605.         }
  606.         return columnControlButton;
  607.     }
  608.     /**
  609.      * bound property to flag visibility state of column control.
  610.      * 
  611.      * @param showColumnControl
  612.      */
  613.     public void setColumnControlVisible(boolean showColumnControl) {
  614.         boolean old = columnControlVisible;
  615.         this.columnControlVisible = showColumnControl;
  616.         configureColumnControl();
  617.         firePropertyChange("columnControlVisible", old, columnControlVisible);
  618.     }
  619.     
  620. //--------------------- actions
  621.     
  622.     /**
  623.      * A small class which dispatches actions. TODO: Is there a way that we can
  624.      * make this static? JW: I hate those if constructs... we are in OO-land!
  625.      */
  626.     private class Actions extends UIAction {
  627.         Actions(String name) {
  628.             super(name);
  629.         }
  630.         public void actionPerformed(ActionEvent evt) {
  631.             if ("print".equals(getName())) {
  632.                 try {
  633.                     print();
  634.                 } catch (PrinterException ex) {
  635.                     // REMIND(aim): should invoke pluggable application error
  636.                     // handler
  637.                     ex.printStackTrace();
  638.                 }
  639.             } else if ("find".equals(getName())) {
  640.                 find();
  641.             }
  642.         }
  643.     }
  644.     private void initActionsAndBindings() {
  645.         // Register the actions that this class can handle.
  646.         ActionMap map = getActionMap();
  647.         map.put("print", new Actions("print"));
  648.         map.put("find", new Actions("find"));
  649.         map.put(PACKALL_ACTION_COMMAND, createPackAllAction());
  650.         map.put(PACKSELECTED_ACTION_COMMAND, createPackSelectedAction());
  651.         map.put(HORIZONTALSCROLL_ACTION_COMMAND, createHorizontalScrollAction());
  652.         // JW: this should be handled by the LF!
  653.         KeyStroke findStroke = KeyStroke.getKeyStroke("control F");
  654.         getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
  655.     }
  656.     /** Creates an Action for horizontal scrolling. */
  657.     private Action createHorizontalScrollAction() {
  658.         String actionName = getUIString(HORIZONTALSCROLL_ACTION_COMMAND);
  659.         BoundAction action = new BoundAction(actionName,
  660.                 HORIZONTALSCROLL_ACTION_COMMAND);
  661.         action.setStateAction();
  662.         action.registerCallback(this, "setHorizontalScrollEnabled");
  663.         action.setSelected(isHorizontalScrollEnabled());
  664.         return action;
  665.     }
  666.     private String getUIString(String key) {
  667.         String text = UIManager.getString(UIPREFIX + key);
  668.         return text != null ? text : key;
  669.     }
  670.     /** Creates an Action for packing selected columns. */
  671.     private Action createPackSelectedAction() {
  672.         String text = getUIString(PACKSELECTED_ACTION_COMMAND);
  673.         BoundAction action = new BoundAction(text, PACKSELECTED_ACTION_COMMAND);
  674.         action.registerCallback(this, "packSelected");
  675.         action.setEnabled(getSelectedColumnCount() > 0);
  676.         return action;
  677.     }
  678.     /** Creates an Action for packing all columns. */
  679.     private Action createPackAllAction() {
  680.         String text = getUIString(PACKALL_ACTION_COMMAND);
  681.         BoundAction action = new BoundAction(text, PACKALL_ACTION_COMMAND);
  682.         action.registerCallback(this, "packAll");
  683.         return action;
  684.     }
  685.     
  686. //------------------ bound action callback methods
  687.     
  688.     /**
  689.      * This resizes all columns to fit the viewport; if horizontal scrolling is
  690.      * enabled, all columns will get their preferred width. This can be
  691.      * triggered by the "packAll" BoundAction on the table as well.
  692.      */
  693.     public void packAll() {
  694.         packTable(getDefaultPackMargin());
  695.     }
  696.     /**
  697.      * This resizes selected columns to fit the viewport; if horizontal
  698.      * scrolling is enabled, selected columns will get their preferred width.
  699.      * This can be triggered by the "packSelected" BoundAction on the table as
  700.      * well.
  701.      */
  702.     public void packSelected() {
  703.         int selected = getSelectedColumn();
  704.         if (selected >= 0) {
  705.             packColumn(selected, getDefaultPackMargin());
  706.         }
  707.     }
  708.     /**
  709.      * Controls horizontal scrolling in the viewport, and works in coordination
  710.      * with column sizing.
  711.      * 
  712.      * @param enabled
  713.      *            If true, the scrollpane will allow the table to scroll
  714.      *            horizontally, and columns will resize to their preferred
  715.      *            width. If false, columns will resize to fit the viewport.
  716.      */
  717.     public void setHorizontalScrollEnabled(boolean enabled) {
  718.         if (enabled == (isHorizontalScrollEnabled()))
  719.             return;
  720.         if (enabled) {
  721.             oldAutoResizeMode = getAutoResizeMode();
  722.             setAutoResizeMode(AUTO_RESIZE_OFF);
  723.         } else {
  724.             setAutoResizeMode(oldAutoResizeMode);
  725.         }
  726.     }
  727.     /** Returns the current setting for horizontal scrolling. */
  728.     private boolean isHorizontalScrollEnabled() {
  729.         return getAutoResizeMode() == AUTO_RESIZE_OFF;
  730.     }
  731.     /** Returns the default margin for packing columns. */
  732.     private int getDefaultPackMargin() {
  733.         return 4;
  734.     }
  735.     /** Notifies the table that a new column has been selected. 
  736.      *  overridden to update the enabled state of the packSelected
  737.      *  action.
  738.      */
  739.     public void columnSelectionChanged(ListSelectionEvent e) {
  740.         super.columnSelectionChanged(e);
  741.         if (e.getValueIsAdjusting())
  742.             return;
  743.         Action packSelected = getActionMap().get(PACKSELECTED_ACTION_COMMAND);
  744.         if ((packSelected != null)) {
  745.             packSelected.setEnabled(!((ListSelectionModel) e.getSource())
  746.                     .isSelectionEmpty());
  747.         }
  748.     }
  749.     /** 
  750.      * overridden to update the show horizontal scrollbar action's
  751.      * selected state. 
  752.      */
  753.     public void setAutoResizeMode(int mode) {
  754.         super.setAutoResizeMode(mode);
  755.         Action showHorizontal = getActionMap().get(
  756.                 HORIZONTALSCROLL_ACTION_COMMAND);
  757.         if (showHorizontal instanceof BoundAction) {
  758.             ((BoundAction) showHorizontal)
  759.                     .setSelected(isHorizontalScrollEnabled());
  760.         }
  761.     }
  762. //------------------------ override super because of filter-awareness
  763.     
  764.     /**
  765.      * Returns the row count in the table; if filters are applied, this is the
  766.      * filtered row count.
  767.      */
  768.     @Override public int getRowCount() {
  769.         // RG: If there are no filters, call superclass version rather than
  770.         // accessing model directly
  771.         return filters == null ?
  772.                 super.getRowCount() : filters.getOutputSize();
  773.     }
  774.     public boolean isHierarchical(int column) {
  775.         return false;
  776.     }
  777.     /**
  778.      * Convert row index from view coordinates to model coordinates accounting
  779.      * for the presence of sorters and filters.
  780.      * 
  781.      * @param row
  782.      *            row index in view coordinates
  783.      * @return row index in model coordinates
  784.      */
  785.     public int convertRowIndexToModel(int row) {
  786.         return getFilters().convertRowIndexToModel(row);
  787.     }
  788.     /**
  789.      * Convert row index from model coordinates to view coordinates accounting
  790.      * for the presence of sorters and filters.
  791.      * 
  792.      * @param row
  793.      *            row index in model coordinates
  794.      * @return row index in view coordinates
  795.      */
  796.     public int convertRowIndexToView(int row) {
  797.         return getFilters().convertRowIndexToView(row);
  798.     }
  799.     /**
  800.      * {@inheritDoc}
  801.      */
  802.     public Object getValueAt(int row, int column) {
  803.         return getModel().getValueAt(convertRowIndexToModel(row), 
  804.                 convertColumnIndexToModel(column));
  805.     }
  806.     /**
  807.      * {@inheritDoc}
  808.      */
  809.     public void setValueAt(Object aValue, int row, int column) {
  810.         getModel().setValueAt(aValue, convertRowIndexToModel(row),
  811.                 convertColumnIndexToModel(column));
  812.     }
  813.     /**
  814.      * {@inheritDoc}
  815.      */
  816.     public boolean isCellEditable(int row, int column) {
  817.         return getModel().isCellEditable(convertRowIndexToModel(row),
  818.                 convertColumnIndexToModel(column));
  819.     }
  820.     /**
  821.      * {@inheritDoc}
  822.      */
  823.     public void setModel(TableModel newModel) {
  824.         // JW: need to look here? is done in tableChanged as well. 
  825.         getSelection().lock();
  826.         super.setModel(newModel);
  827.     }
  828.     /** 
  829.      * additionally updates filtered state.
  830.      * {@inheritDoc}
  831.      */
  832.     public void tableChanged(TableModelEvent e) {
  833.         // JW: make Selection deaf ... super doesn't know about row
  834.         // mapping and sets rowSelection in model coordinates
  835.         // causing complete confusion.
  836.         getSelection().lock();
  837.         super.tableChanged(e);
  838.         updateSelectionAndRowModel(e);
  839.         use(filters);
  840.     }
  841.     /**
  842.      * reset model selection coordinates in Selection after
  843.      * model events.
  844.      * 
  845.      * @param e
  846.      */
  847.     private void updateSelectionAndRowModel(TableModelEvent e) {
  848.         // JW: c&p from JTable
  849.         // JW: still missing: checkLeadAnchor (#172-swingx)
  850.         // super checkLeadAnchor is subtly buggy in lead/anchor update
  851.         // because it calls model.getRowCount() instead of getRowCount!!
  852.         if (e.getType() == TableModelEvent.INSERT) {
  853.             int start = e.getFirstRow();
  854.             int end = e.getLastRow();
  855.             if (start < 0) {
  856.                 start = 0;
  857.             }
  858.             if (end < 0) {
  859.                 end = getModel().getRowCount() - 1;
  860.             }
  861.             // Adjust the selection to account for the new rows.
  862.             int length = end - start + 1;
  863.             getSelection().insertIndexInterval(start, length, true);
  864.             getRowSizing().insertIndexInterval(start, length, getRowHeight());
  865.         } else if (e.getType() == TableModelEvent.DELETE) {
  866.             int start = e.getFirstRow();
  867.             int end = e.getLastRow();
  868.             if (start < 0) {
  869.                 start = 0;
  870.             }
  871.             if (end < 0) {
  872.                 end = getModel().getRowCount() - 1;
  873.             }
  874.             int deletedCount = end - start + 1;
  875.             int previousRowCount = getModel().getRowCount() + deletedCount;
  876.             // Adjust the selection to account for the new rows
  877.             getSelection().removeIndexInterval(start, end);
  878.             getRowSizing().removeIndexInterval(start, deletedCount);
  879.         } else if (getSelectionModel().isSelectionEmpty()) {
  880.             // JW: this is incomplete! see #167-swingx
  881.             // possibly got a dataChanged or structureChanged
  882.             // super will have cleared selection
  883.             getSelection().clearModelSelection();
  884.             getRowSizing().clearModelSizes();
  885.             updateViewSizeSequence();
  886.              
  887.         }
  888.     }
  889.     /**
  890.      * Called if individual row height mapping need to be updated.
  891.      * This implementation guards against unnessary access of 
  892.      * super's private rowModel field.
  893.      */
  894.     protected void updateViewSizeSequence() {
  895.         SizeSequence sizeSequence = null;
  896.         if (isRowHeightEnabled()) {
  897.             sizeSequence = getSuperRowModel();
  898.         }
  899.         getRowSizing().setViewSizeSequence(sizeSequence, getRowHeight());
  900.     }
  901.     
  902.     private Selection getSelection() {
  903.         if (selection == null) {
  904.             selection = new Selection(filters, getSelectionModel());
  905.         }
  906.         return selection;
  907.     }
  908. //----------------------------- filters
  909.     
  910.     /** Returns the FilterPipeline for the table. */
  911.     public FilterPipeline getFilters() {
  912.         // PENDING: this is guaranteed to be != null because
  913.         // init calls setFilters(null) which enforces an empty
  914.         // pipeline
  915.         return filters;
  916.     }
  917.     /**
  918.      * setModel() and setFilters() may be called in either order.
  919.      * 
  920.      * @param pipeline
  921.      */
  922.     private void use(FilterPipeline pipeline) {
  923.         if (pipeline != null) {
  924.             // check JW: adding listener multiple times (after setModel)?
  925.             if (initialUse(pipeline)) {
  926.                 pipeline.addPipelineListener(getFilterPipelineListener());
  927.                 pipeline.assign(getComponentAdapter());
  928.             } else {
  929.                 pipeline.flush();
  930.             }
  931.         }
  932.     }
  933.     /**
  934.      * @return true is not yet used in this JXTable, false otherwise
  935.      */
  936.     private boolean initialUse(FilterPipeline pipeline) {
  937.         if (pipelineListener == null) return true;
  938.         PipelineListener[] l = pipeline.getPipelineListeners();
  939.         for (int i = 0; i < l.length; i++) {
  940.             if (pipelineListener.equals(l[i]))
  941.                 return false;
  942.         }
  943.         return true;
  944.     }
  945.     /** Sets the FilterPipeline for filtering table rows. */
  946.     public void setFilters(FilterPipeline pipeline) {
  947.         FilterPipeline old = getFilters();
  948.         Sorter sorter = null;
  949.         if (old != null) {
  950.             old.removePipelineListener(pipelineListener);
  951.             sorter = old.getSorter();
  952.         }
  953.         if (pipeline == null) {
  954.             pipeline = new FilterPipeline();
  955.         }
  956.         filters = pipeline;
  957.         filters.setSorter(sorter);
  958.         // JW: first assign to prevent (short?) illegal internal state
  959.         // #173-swingx
  960.         use(filters);
  961.         getRowSizing().setFilters(filters);
  962.         getSelection().setFilters(filters);
  963.     }
  964.     /** returns the listener for changes in filters. */
  965.     protected PipelineListener getFilterPipelineListener() {
  966.         if (pipelineListener == null) {
  967.             pipelineListener = createPipelineListener();
  968.         }
  969.         return pipelineListener;
  970.     }
  971.     /** creates the listener for changes in filters. */
  972.     protected PipelineListener createPipelineListener() {
  973.         PipelineListener l = new PipelineListener() {
  974.             public void contentsChanged(PipelineEvent e) {
  975.                 updateOnFilterContentChanged();
  976.             }
  977.         };
  978.         return l;
  979.     }
  980.     /** 
  981.      * method called on change notification from filterpipeline.
  982.      */
  983.     protected void updateOnFilterContentChanged() {
  984.         revalidate();
  985.         repaint();
  986.     }
  987. //-------------------------------- sorting 
  988.     /**
  989.      * Sets &quot;sortable&quot; property indicating whether or not this table
  990.      * supports sortable columns. If <code>sortable</code> is
  991.      * <code>true</code> then sorting will be enabled on all columns whose
  992.      * <code>sortable</code> property is <code>true</code>. If
  993.      * <code>sortable</code> is <code>false</code> then sorting will be
  994.      * disabled for all columns, regardless of each column's individual
  995.      * <code>sorting</code> property. The default is <code>true</code>.
  996.      * 
  997.      * @see TableColumnExt#isSortable()
  998.      * @param sortable
  999.      *            boolean indicating whether or not this table supports sortable
  1000.      *            columns
  1001.      */
  1002.     public void setSortable(boolean sortable) {
  1003.         if (sortable == isSortable())
  1004.             return;
  1005.         this.sortable = sortable;
  1006.         if (!isSortable()) resetSorter();
  1007.         firePropertyChange("sortable", !sortable, sortable);
  1008.     }
  1009.     /** Returns true if the table is sortable. */
  1010.     public boolean isSortable() {
  1011.         return sortable;
  1012.     }
  1013.     private void setInteractiveSorter(Sorter sorter) {
  1014.         // this check is for the sake of the very first call after instantiation
  1015.         if (filters == null)
  1016.             return;
  1017.         getFilters().setSorter(sorter);
  1018.     }
  1019.     private Sorter getInteractiveSorter() {
  1020.         // this check is for the sake of the very first call after instantiation
  1021.         if (filters == null)
  1022.             return null;
  1023.         return getFilters().getSorter();
  1024.     }
  1025.     /**
  1026.      * Removes the interactive sorter.
  1027.      * Used by headerListener.
  1028.      * 
  1029.      */
  1030.     protected void resetSorter() {
  1031.         // JW PENDING: think about notification instead of manual repaint.
  1032.         setInteractiveSorter(null);
  1033.         getTableHeader().repaint();
  1034.     }
  1035.     public void columnRemoved(TableColumnModelEvent e) {
  1036.         // JW - old problem: need access to removed column
  1037.         // to get hold of removed modelIndex
  1038.         // to remove interactive sorter if any
  1039.         // no way
  1040.         // int modelIndex = convertColumnIndexToModel(e.getFromIndex());
  1041.         updateSorterAfterColumnRemoved();
  1042.         super.columnRemoved(e);
  1043.     }
  1044.     /**
  1045.      * guarantee that the interactive sorter is removed if its column
  1046.      * is removed.
  1047.      * 
  1048.      */
  1049.     private void updateSorterAfterColumnRemoved() {
  1050.         // bloody hack: get sorter and check if there's a column with it
  1051.         // available
  1052.         Sorter sorter = getInteractiveSorter();
  1053.         if (sorter != null) {
  1054.             int sorterColumn = sorter.getColumnIndex();
  1055.             List columns = getColumns(true);
  1056.             for (Iterator iter = columns.iterator(); iter.hasNext();) {
  1057.                 TableColumn column = (TableColumn) iter.next();
  1058.                 if (column.getModelIndex() == sorterColumn)
  1059.                     return;
  1060.             }
  1061.             // didn't find a column with the sorter's index - remove
  1062.             resetSorter();
  1063.         }
  1064.     }
  1065.     /**
  1066.      * 
  1067.      * request to sort the column at columnIndex in view coordinates. if there
  1068.      * is already an interactive sorter for this column it's sort order is
  1069.      * reversed. Otherwise the columns sorter is used as is.
  1070.      * Used by headerListener.
  1071.      * 
  1072.      */
  1073.     protected void setSorter(int columnIndex) {
  1074.         if (!isSortable())
  1075.             return;
  1076.         Sorter sorter = getInteractiveSorter();
  1077.         if ((sorter != null)
  1078.             && (sorter.getColumnIndex() == convertColumnIndexToModel(columnIndex))) {
  1079.             sorter.toggle();
  1080.         } else {
  1081.             TableColumnExt column = getColumnExt(columnIndex);
  1082.             getFilters().setSorter(column != null ? column.getSorter() : null);
  1083.         }
  1084.     }
  1085.     /**
  1086.      * Returns the interactive sorter if it is set from the given column.
  1087.      * Used by ColumnHeaderRenderer.getTableCellRendererComponent().
  1088.      * 
  1089.      * @param columnIndex the column index in view coordinates.
  1090.      * @return the interactive sorter if matches the column or null.
  1091.      */
  1092.     public Sorter getSorter(int columnIndex) {
  1093.         Sorter sorter = getInteractiveSorter();
  1094.         return sorter == null ? null
  1095.                 : sorter.getColumnIndex() == convertColumnIndexToModel(columnIndex) ? sorter
  1096.                         : null;
  1097.     }
  1098.     
  1099. //---------------------- enhanced TableColumn/Model support    
  1100.     /**
  1101.      * Remove all columns, make sure to include hidden.
  1102.      * 
  1103.      */
  1104.     protected void removeColumns() {
  1105.         /**
  1106.          * @todo promote this method to superclass, and change
  1107.          *       createDefaultColumnsFromModel() to call this method
  1108.          */
  1109.         List columns = getColumns(true);
  1110.         for (Iterator iter = columns.iterator(); iter.hasNext();) {
  1111.             getColumnModel().removeColumn((TableColumn) iter.next());
  1112.         }
  1113.     }
  1114.     /**
  1115.      * returns a list of all visible TableColumns.
  1116.      * 
  1117.      * @return
  1118.      */
  1119.     public List getColumns() {
  1120.         return Collections.list(getColumnModel().getColumns());
  1121.     }
  1122.     /**
  1123.      * returns a list of TableColumns including hidden if the parameter is set
  1124.      * to true.
  1125.      * 
  1126.      * @param includeHidden
  1127.      * @return
  1128.      */
  1129.     public List getColumns(boolean includeHidden) {
  1130.         if (includeHidden && (getColumnModel() instanceof TableColumnModelExt)) {
  1131.             return ((TableColumnModelExt) getColumnModel())
  1132.                     .getColumns(includeHidden);
  1133.         }
  1134.         return getColumns();
  1135.     }
  1136.     /**
  1137.      * returns the number of TableColumns including hidden if the parameter is set 
  1138.      * to true.
  1139.      * 
  1140.      * @param includeHidden
  1141.      * @return
  1142.      */
  1143.     public int getColumnCount(boolean includeHidden) {
  1144.         if (getColumnModel() instanceof TableColumnModelExt) {
  1145.             return ((TableColumnModelExt) getColumnModel())
  1146.                     .getColumnCount(includeHidden);
  1147.         }
  1148.         return getColumnCount();
  1149.     }
  1150.     /**
  1151.      * reorders the columns in the sequence given array. Logical names that do
  1152.      * not correspond to any column in the model will be ignored. Columns with
  1153.      * logical names not contained are added at the end.
  1154.      * 
  1155.      * @param columnNames
  1156.      *            array of logical column names
  1157.      */
  1158.     public void setColumnSequence(Object[] identifiers) {
  1159.         List columns = getColumns(true);
  1160.         Map map = new HashMap();
  1161.         for (Iterator iter = columns.iterator(); iter.hasNext();) {
  1162.             // PENDING: handle duplicate identifiers ...
  1163.             TableColumn column = (TableColumn) iter.next();
  1164.             map.put(column.getIdentifier(), column);
  1165.             getColumnModel().removeColumn(column);
  1166.         }
  1167.         for (int i = 0; i < identifiers.length; i++) {
  1168.             TableColumn column = (TableColumn) map.get(identifiers[i]);
  1169.             if (column != null) {
  1170.                 getColumnModel().addColumn(column);
  1171.                 columns.remove(column);
  1172.             }
  1173.         }
  1174.         for (Iterator iter = columns.iterator(); iter.hasNext();) {
  1175.             TableColumn column = (TableColumn) iter.next();
  1176.             getColumnModel().addColumn(column);
  1177.         }
  1178.     }
  1179.     /**
  1180.      * Returns the <code>TableColumnExt</code> object for the column in the
  1181.      * table whose identifier is equal to <code>identifier</code>, when
  1182.      * compared using <code>equals</code>. The returned TableColumn is
  1183.      * guaranteed to be part of the current ColumnModel but may be hidden, that
  1184.      * is
  1185.      * 
  1186.      * <pre> <code>
  1187.      * TableColumnExt column = table.getColumnExt(id);
  1188.      * if (column != null) {
  1189.      *     int viewIndex = table.convertColumnIndexToView(column.getModelIndex());
  1190.      *     assertEquals(column.isVisible(), viewIndex &gt;= 0);
  1191.      * }
  1192.      * </code> </pre>
  1193.      * 
  1194.      * @param identifier
  1195.      *            the identifier object
  1196.      * 
  1197.      * @return the <code>TableColumnExt</code> object that matches the
  1198.      *         identifier or null if none is found.
  1199.      */
  1200.     public TableColumnExt getColumnExt(Object identifier) {
  1201.         if (getColumnModel() instanceof TableColumnModelExt) {
  1202.             return ((TableColumnModelExt) getColumnModel())
  1203.                     .getColumnExt(identifier);
  1204.         } else {
  1205.             // PENDING: not tested!
  1206.             try {
  1207.                 TableColumn column = getColumn(identifier);
  1208.                 if (column instanceof TableColumnExt) {
  1209.                     return (TableColumnExt) column;
  1210.                 }
  1211.             } catch (Exception e) {
  1212.                 // TODO: handle exception
  1213.             }
  1214.         }
  1215.         return null;
  1216.     }
  1217.     /**
  1218.      * Returns the <code>TableColumnExt</code> object for the column in the
  1219.      * table whose column index is equal to <code>viewColumnIndex</code>
  1220.      * 
  1221.      * @param viewColumnIndex
  1222.      *            index of the column with the object in question
  1223.      * 
  1224.      * @return the <code>TableColumnExt</code> object that matches the column
  1225.      *         index
  1226.      * @exception IllegalArgumentException
  1227.      *                if no <code>TableColumn</code> has this identifier
  1228.      */
  1229.     public TableColumnExt getColumnExt(int viewColumnIndex) {
  1230.         return (TableColumnExt) getColumnModel().getColumn(viewColumnIndex);
  1231.     }
  1232.     public void createDefaultColumnsFromModel() {
  1233.         TableModel model = getModel();
  1234.         if (model != null) {
  1235.             // Create new columns from the data model info
  1236.             // Note: it's critical to create the new columns before
  1237.             // deleting the old ones. Why?
  1238.             // JW PENDING: the reason is somewhere in the early forums - search!
  1239.             int modelColumnCount = model.getColumnCount();
  1240.             TableColumn newColumns[] = new TableColumn[modelColumnCount];
  1241.             for (int i = 0; i < newColumns.length; i++) {
  1242.                 newColumns[i] = createAndConfigureColumn(model, i);
  1243.             }
  1244.             // Remove any current columns
  1245.             removeColumns();
  1246.             // Now add the new columns to the column model
  1247.             for (int i = 0; i < newColumns.length; i++) {
  1248.                 addColumn(newColumns[i]);
  1249.             }
  1250.         }
  1251.     }
  1252.     protected TableColumn createAndConfigureColumn(TableModel model,
  1253.             int modelColumn) {
  1254.         return getColumnFactory().createAndConfigureTableColumn(model,
  1255.                 modelColumn);
  1256.     }
  1257.     protected ColumnFactory getColumnFactory() {
  1258.         if (columnFactory == null) {
  1259.             columnFactory = ColumnFactory.getInstance();
  1260.         }
  1261.         return columnFactory;
  1262.     }
  1263.     
  1264. //----------------------- delegating methods?? from super    
  1265.     /**
  1266.      * Returns the margin between columns.
  1267.      * 
  1268.      * @return the margin between columns
  1269.      */
  1270.     public int getColumnMargin() {
  1271.         return getColumnModel().getColumnMargin();
  1272.     }
  1273.     /**
  1274.      * Sets the margin between columns.
  1275.      * 
  1276.      * @param value
  1277.      *            margin between columns; must be greater than or equal to zero.
  1278.      */
  1279.     public void setColumnMargin(int value) {
  1280.         getColumnModel().setColumnMargin(value);
  1281.     }
  1282.     /**
  1283.      * Returns the selection mode used by this table's selection model.
  1284.      * 
  1285.      * @return the selection mode used by this table's selection model
  1286.      */
  1287.     public int getSelectionMode() {
  1288.         return getSelectionModel().getSelectionMode();
  1289.     }
  1290. //----------------------- Search support 
  1291.     /** Opens the find widget for the table. */
  1292.     private void find() {
  1293.         SearchFactory.getInstance().showFindInput(this, getSearchable());
  1294.     }
  1295.     /**
  1296.      * 
  1297.      * @returns a not-null Searchable for this editor.  
  1298.      */
  1299.     public Searchable getSearchable() {
  1300.         if (searchable == null) {
  1301.             searchable = new TableSearchable();
  1302.         }
  1303.         return searchable;
  1304.     }
  1305.     /**
  1306.      * sets the Searchable for this editor. If null, a default 
  1307.      * searchable will be used.
  1308.      * 
  1309.      * @param searchable
  1310.      */
  1311.     public void setSearchable(Searchable searchable) {
  1312.         this.searchable = searchable;
  1313.     }
  1314.     public class TableSearchable extends AbstractSearchable {
  1315.         private SearchHighlighter searchHighlighter;
  1316.         @Override
  1317.         protected void findMatchAndUpdateState(Pattern pattern, int startRow,
  1318.                 boolean backwards) {
  1319.             SearchResult matchRow = null;
  1320.             if (backwards) {
  1321.                 // CHECK: off-one end still needed?
  1322.                 // Probably not - the findXX don't have side-effects any longer
  1323.                 // hmmm... still needed: even without side-effects we need to
  1324.                 // guarantee calling the notfound update at the very end of the
  1325.                 // loop.
  1326.                 for (int r = startRow; r >= -1 && matchRow == null; r--) {
  1327.                     matchRow = findMatchBackwardsInRow(pattern, r);
  1328.                     updateState(matchRow);
  1329.                 }
  1330.             } else {
  1331.                 for (int r = startRow; r <= getSize() && matchRow == null; r++) {
  1332.                     matchRow = findMatchForwardInRow(pattern, r);
  1333.                     updateState(matchRow);
  1334.                 }
  1335.             }
  1336.             // JW: Needed to update if loop wasn't entered!
  1337.             // the alternative is to go one off in the loop. Hmm - which is
  1338.             // preferable?
  1339.             // updateState(matchRow);
  1340.         }
  1341.         /**
  1342.          * called if sameRowIndex && !hasEqualRegEx. Matches the cell at
  1343.          * row/lastFoundColumn against the pattern. PRE: lastFoundColumn valid.
  1344.          * 
  1345.          * @param pattern
  1346.          * @param row
  1347.          * @return
  1348.          */
  1349.         protected SearchResult findExtendedMatch(Pattern pattern, int row) {
  1350.             return findMatchAt(pattern, row, lastSearchResult.foundColumn);
  1351.         }
  1352.         /**
  1353.          * Searches forward through columns of the given row. Starts at
  1354.          * lastFoundColumn or first column if lastFoundColumn < 0. returns an
  1355.          * appropriate SearchResult if a matching cell is found in this row or
  1356.          * null if no match is found. A row index out off range results in a
  1357.          * no-match.
  1358.          * 
  1359.          * @param pattern
  1360.          * @param row
  1361.          *            the row to search
  1362.          * @return
  1363.          */
  1364.         private SearchResult findMatchForwardInRow(Pattern pattern, int row) {
  1365.             int startColumn = (lastSearchResult.foundColumn < 0) ? 0 : lastSearchResult.foundColumn;
  1366.             if (isValidIndex(row)) {
  1367.                 for (int column = startColumn; column < getColumnCount(); column++) {
  1368.                     SearchResult result = findMatchAt(pattern, row, column);
  1369.                     if (result != null)
  1370.                         return result;
  1371.                 }
  1372.             }
  1373.             return null;
  1374.         }
  1375.         /**
  1376.          * Searches forward through columns of the given row. Starts at
  1377.          * lastFoundColumn or first column if lastFoundColumn < 0. returns an
  1378.          * appropriate SearchResult if a matching cell is found in this row or
  1379.          * null if no match is found. A row index out off range results in a
  1380.          * no-match.
  1381.          * 
  1382.          * @param pattern
  1383.          * @param row
  1384.          *            the row to search
  1385.          * @return
  1386.          */
  1387.         private SearchResult findMatchBackwardsInRow(Pattern pattern, int row) {
  1388.             int startColumn = (lastSearchResult.foundColumn < 0) ? getColumnCount() - 1
  1389.                     : lastSearchResult.foundColumn;
  1390.             if (isValidIndex(row)) {
  1391.                 for (int column = startColumn; column >= 0; column--) {
  1392.                     SearchResult result = findMatchAt(pattern, row, column);
  1393.                     if (result != null)
  1394.                         return result;
  1395.                 }
  1396.             }
  1397.             return null;
  1398.         }
  1399.         /**
  1400.          * Matches the cell content at row/col against the given Pattern.
  1401.          * Returns an appropriate SearchResult if matching or null if no
  1402.          * matching
  1403.          * 
  1404.          * @param pattern
  1405.          * @param row
  1406.          *            a valid row index in view coordinates
  1407.          * @param column
  1408.          *            a valid column index in view coordinates
  1409.          * @return
  1410.          */
  1411.         protected SearchResult findMatchAt(Pattern pattern, int row, int column) {
  1412.             Object value = getValueAt(row, column);
  1413.             if (value != null) {
  1414.                 Matcher matcher = pattern.matcher(value.toString());
  1415.                 if (matcher.find()) {
  1416.                     return createSearchResult(matcher, row, column);
  1417.                 }
  1418.             }
  1419.             return null;
  1420.         }
  1421.         /**
  1422.          * Called if startIndex is different from last search, reset the column
  1423.          * to -1 and make sure a backwards/forwards search starts at last/first
  1424.          * row, respectively.
  1425.          * 
  1426.          * @param startIndex
  1427.          * @param backwards
  1428.          * @return
  1429.          */
  1430.         protected int adjustStartPosition(int startIndex, boolean backwards) {
  1431.             lastSearchResult.foundColumn = -1;
  1432.             return super.adjustStartPosition(startIndex, backwards);
  1433.         }
  1434.         /**
  1435.          * Moves the internal start for matching as appropriate and returns the
  1436.          * new startIndex to use. Called if search was messaged with the same
  1437.          * startIndex as previously.
  1438.          * 
  1439.          * @param startRow
  1440.          * @param backwards
  1441.          * @return
  1442.          */
  1443.         @Override
  1444.         protected int moveStartPosition(int startRow, boolean backwards) {
  1445.             if (backwards) {
  1446.                 lastSearchResult.foundColumn--;
  1447.                 if (lastSearchResult.foundColumn < 0) {
  1448.                     startRow--;
  1449.                 }
  1450.             } else {
  1451.                 lastSearchResult.foundColumn++;
  1452.                 if (lastSearchResult.foundColumn >= getColumnCount()) {
  1453.                     lastSearchResult.foundColumn = -1;
  1454.                     startRow++;
  1455.                 }
  1456.             }
  1457.             return startRow;
  1458.         }
  1459.         /**
  1460.          * Checks if the startIndex is a candidate for trying a re-match.
  1461.          * 
  1462.          * 
  1463.          * @param startIndex
  1464.          * @return true if the startIndex should be re-matched, false if not.
  1465.          */
  1466.         protected boolean isEqualStartIndex(final int startIndex) {
  1467.             return super.isEqualStartIndex(startIndex)
  1468.                     && isValidColumn(lastSearchResult.foundColumn);
  1469.         }
  1470.         /**
  1471.          * checks if row is in range: 0 <= row < getRowCount().
  1472.          * 
  1473.          * @param column
  1474.          * @return
  1475.          */
  1476.         private boolean isValidColumn(int column) {
  1477.             return column >= 0 && column < getColumnCount();
  1478.         }
  1479.         @Override
  1480.         protected int getSize() {
  1481.             return getRowCount();
  1482.         }
  1483.         @Override
  1484.         protected void moveMatchMarker() {
  1485.             int row = lastSearchResult.foundRow;
  1486.             int column = lastSearchResult.foundColumn;
  1487.             Pattern pattern = lastSearchResult.pattern;
  1488.             if (markByHighlighter()) {
  1489.                 Rectangle cellRect = getCellRect(row, column, true);
  1490.                 if (cellRect != null) {
  1491.                     scrollRectToVisible(cellRect);
  1492.                 }
  1493.                 ensureInsertedSearchHighlighters();
  1494.                 // TODO (JW) - cleanup SearchHighlighter state management
  1495.                 if ((row >= 0) && (column >= 0)) {
  1496.                     getSearchHighlighter().setPattern(pattern);
  1497.                     int modelColumn = convertColumnIndexToModel(column);
  1498.                     getSearchHighlighter().setHighlightCell(row, modelColumn);
  1499.                 } else {
  1500.                     getSearchHighlighter().setPattern(null);
  1501.                 }
  1502.             } else { // use selection
  1503.                 changeSelection(row, column, false, false);
  1504.                 if (!getAutoscrolls()) {
  1505.                     // scrolling not handled by moving selection
  1506.                     Rectangle cellRect = getCellRect(row, column, true);
  1507.                     if (cellRect != null) {
  1508.                         scrollRectToVisible(cellRect);
  1509.                     }
  1510.                 }
  1511.             }
  1512.         }
  1513.         private boolean markByHighlighter() {
  1514.             return Boolean.TRUE.equals(getClientProperty(MATCH_HIGHLIGHTER));
  1515.         }
  1516.         private SearchHighlighter getSearchHighlighter() {
  1517.             if (searchHighlighter == null) {
  1518.                 searchHighlighter = createSearchHighlighter();
  1519.             }
  1520.             return searchHighlighter;
  1521.         }
  1522.         private void ensureInsertedSearchHighlighters() {
  1523.             if (getHighlighters() == null) {
  1524.                 setHighlighters(new HighlighterPipeline(
  1525.                         new Highlighter[] { getSearchHighlighter() }));
  1526.             } else if (!isInPipeline(getSearchHighlighter())) {
  1527.                 getHighlighters().addHighlighter(getSearchHighlighter());
  1528.             }
  1529.         }
  1530.         private boolean isInPipeline(PatternHighlighter searchHighlighter) {
  1531.             Highlighter[] inPipeline = getHighlighters().getHighlighters();
  1532.             if ((inPipeline.length > 0) && 
  1533.                (searchHighlighter.equals(inPipeline[inPipeline.length -1]))) {
  1534.                 return true;
  1535.             }
  1536.             getHighlighters().removeHighlighter(searchHighlighter);
  1537.             return false;
  1538.         }
  1539.         protected SearchHighlighter createSearchHighlighter() {
  1540.             return new SearchHighlighter();
  1541.         }
  1542.     }
  1543. //-------------------------------- sizing support
  1544.     
  1545.     /** ? */
  1546.     public void setVisibleRowCount(int visibleRowCount) {
  1547.         this.visibleRowCount = visibleRowCount;
  1548.     }
  1549.     /** ? */
  1550.     public int getVisibleRowCount() {
  1551.         return visibleRowCount;
  1552.     }
  1553.     public Dimension getPreferredScrollableViewportSize() {
  1554.         Dimension prefSize = super.getPreferredScrollableViewportSize();
  1555.         // JTable hardcodes this to 450 X 400, so we'll calculate it
  1556.         // based on the preferred widths of the columns and the
  1557.         // visibleRowCount property instead...
  1558.         if (prefSize.getWidth() == 450 && prefSize.getHeight() == 400) {
  1559.             TableColumnModel columnModel = getColumnModel();
  1560.             int columnCount = columnModel.getColumnCount();
  1561.             int w = 0;
  1562.             for (int i = 0; i < columnCount; i++) {
  1563.                 TableColumn column = columnModel.getColumn(i);
  1564.                 initializeColumnPreferredWidth(column);
  1565.                 w += column.getPreferredWidth();
  1566.             }
  1567.             prefSize.width = w;
  1568.             JTableHeader header = getTableHeader();
  1569.             // remind(aim): height is still off...???
  1570.             int rowCount = getVisibleRowCount();
  1571.             prefSize.height = rowCount * getRowHeight()
  1572.                     + (header != null ? header.getPreferredSize().height : 0);
  1573.             setPreferredScrollableViewportSize(prefSize);
  1574.         }
  1575.         return prefSize;
  1576.     }
  1577.     /**
  1578.      * Packs all the columns to their optimal size. Works best with auto
  1579.      * resizing turned off.
  1580.      * 
  1581.      * Contributed by M. Hillary (Issue #60)
  1582.      * 
  1583.      * @param margin
  1584.      *            the margin to apply to each column.
  1585.      */
  1586.     public void packTable(int margin) {
  1587.         for (int c = 0; c < getColumnCount(); c++)
  1588.             packColumn(c, margin, -1);
  1589.     }
  1590.     /**
  1591.      * Packs an indivudal column in the table. Contributed by M. Hillary (Issue
  1592.      * #60)
  1593.      * 
  1594.      * @param column
  1595.      *            The Column index to pack in View Coordinates
  1596.      * @param margin
  1597.      *            The Margin to apply to the column width.
  1598.      */
  1599.     public void packColumn(int column, int margin) {
  1600.         packColumn(column, margin, -1);
  1601.     }
  1602.     /**
  1603.      * Packs an indivual column in the table to less than or equal to the
  1604.      * maximum witdth. If maximun is -1 then the column is made as wide as it
  1605.      * needs. Contributed by M. Hillary (Issue #60)
  1606.      * 
  1607.      * @param column
  1608.      *            The Column index to pack in View Coordinates
  1609.      * @param margin
  1610.      *            The margin to apply to the column
  1611.      * @param max
  1612.      *            The maximum width the column can be resized to. -1 mean any
  1613.      *            size.
  1614.      */
  1615.     public void packColumn(int column, int margin, int max) {
  1616.         getColumnFactory().packColumn(this, getColumnExt(column), margin, max);
  1617.     }
  1618.     /**
  1619.      * Initialize the preferredWidth of the specified column based on the
  1620.      * column's prototypeValue property. If the column is not an instance of
  1621.      * <code>TableColumnExt</code> or prototypeValue is <code>null</code>
  1622.      * then the preferredWidth is left unmodified.
  1623.      * 
  1624.      * @see org.jdesktop.swingx.table.TableColumnExt#setPrototypeValue
  1625.      * @param column
  1626.      *            TableColumn object representing view column
  1627.      */
  1628.     protected void initializeColumnPreferredWidth(TableColumn column) {
  1629.         if (column instanceof TableColumnExt) {
  1630.             getColumnFactory().configureColumnWidths(this,
  1631.                     (TableColumnExt) column);
  1632.         }
  1633.     }
  1634.     
  1635. //----------------------------------- uniform data model access
  1636.     
  1637.     protected ComponentAdapter getComponentAdapter() {
  1638.         if (dataAdapter == null) {
  1639.             dataAdapter = new TableAdapter(this);
  1640.         }
  1641.         return dataAdapter;
  1642.     }
  1643.     
  1644.     protected static class TableAdapter extends ComponentAdapter {
  1645.         private final JXTable table;
  1646.         /**
  1647.          * Constructs a <code>TableDataAdapter</code> for the specified target
  1648.          * component.
  1649.          * 
  1650.          * @param component
  1651.          *            the target component
  1652.          */
  1653.         public TableAdapter(JXTable component) {
  1654.             super(component);
  1655.             table = component;
  1656.         }
  1657.         /**
  1658.          * Typesafe accessor for the target component.
  1659.          * 
  1660.          * @return the target component as a {@link javax.swing.JTable}
  1661.          */
  1662.         public JXTable getTable() {
  1663.             return table;
  1664.         }
  1665.         public String getColumnName(int columnIndex) {
  1666.             TableColumn column = getColumnByModelIndex(columnIndex);
  1667.             return column == null ? "" : column.getHeaderValue().toString();
  1668.         }
  1669.         protected TableColumn getColumnByModelIndex(int modelColumn) {
  1670.             List columns = table.getColumns(true);
  1671.             for (Iterator iter = columns.iterator(); iter.hasNext();) {
  1672.                 TableColumn column = (TableColumn) iter.next();
  1673.                 if (column.getModelIndex() == modelColumn) {
  1674.                     return column;
  1675.                 }
  1676.             }
  1677.             return null;
  1678.         }
  1679.         
  1680.         public String getColumnIdentifier(int columnIndex) {
  1681.             
  1682.             TableColumn column = getColumnByModelIndex(columnIndex);
  1683.             Object identifier = column != null ? column.getIdentifier() : null;
  1684.             return identifier != null ? identifier.toString() : null;
  1685.         }
  1686.         
  1687.         public int getColumnCount() {
  1688.             return table.getModel().getColumnCount();
  1689.         }
  1690.         public int getRowCount() {
  1691.             return table.getModel().getRowCount();
  1692.         }
  1693.         /**
  1694.          * {@inheritDoc}
  1695.          */
  1696.         public Object getValueAt(int row, int column) {
  1697.             return table.getModel().getValueAt(row, column);
  1698.         }
  1699.         public void setValueAt(Object aValue, int row, int column) {
  1700.             table.getModel().setValueAt(aValue, row, column);
  1701.         }
  1702.         public boolean isCellEditable(int row, int column) {
  1703.             return table.getModel().isCellEditable(row, column);
  1704.         }
  1705.         
  1706.         
  1707.         public boolean isTestable(int column) {
  1708.             return getColumnByModelIndex(column) != null;
  1709.         }
  1710. //-------------------------- accessing view state/values
  1711.         
  1712.         public Object getFilteredValueAt(int row, int column) {
  1713.             return table.getValueAt(row, modelToView(column)); // in view coordinates
  1714.         }
  1715.         /**
  1716.          * {@inheritDoc}
  1717.          */
  1718.         public boolean isSelected() {
  1719.             return table.isCellSelected(row, column);
  1720.         }
  1721.         /**
  1722.          * {@inheritDoc}
  1723.          */
  1724.         public boolean hasFocus() {
  1725.             boolean rowIsLead = (table.getSelectionModel()
  1726.                     .getLeadSelectionIndex() == row);
  1727.             boolean colIsLead = (table.getColumnModel().getSelectionModel()
  1728.                     .getLeadSelectionIndex() == column);
  1729.             return table.isFocusOwner() && (rowIsLead && colIsLead);
  1730.         }
  1731.         /**
  1732.          * {@inheritDoc}
  1733.          */
  1734.         public int modelToView(int columnIndex) {
  1735.             return table.convertColumnIndexToView(columnIndex);
  1736.         }
  1737.         /**
  1738.          * {@inheritDoc}
  1739.          */
  1740.         public int viewToModel(int columnIndex) {
  1741.             return table.convertColumnIndexToModel(columnIndex);
  1742.         }
  1743.     }
  1744.  
  1745. //--------------------- managing renderers/editors
  1746.     
  1747.     /** Returns the HighlighterPipeline assigned to the table, null if none. */
  1748.     public HighlighterPipeline getHighlighters() {
  1749.         return highlighters;
  1750.     }
  1751.     /**
  1752.      * Assigns a HighlighterPipeline to the table. bound property.
  1753.      */
  1754.     public void setHighlighters(HighlighterPipeline pipeline) {
  1755.         HighlighterPipeline old = getHighlighters();
  1756.         if (old != null) {
  1757.             old.removeChangeListener(getHighlighterChangeListener());
  1758.         }
  1759.         highlighters = pipeline;
  1760.         if (highlighters != null) {
  1761.             highlighters.addChangeListener(getHighlighterChangeListener());
  1762.         }
  1763.         firePropertyChange("highlighters", old, getHighlighters());
  1764.     }
  1765.     /**
  1766.      * returns the ChangeListener to use with highlighters. Creates one if
  1767.      * necessary.
  1768.      * 
  1769.      * @return != null
  1770.      */
  1771.     private ChangeListener getHighlighterChangeListener() {
  1772.         if (highlighterChangeListener == null) {
  1773.             highlighterChangeListener = new ChangeListener() {
  1774.                 public void stateChanged(ChangeEvent e) {
  1775.                     repaint();
  1776.                 }
  1777.             };
  1778.         }
  1779.         return highlighterChangeListener;
  1780.     }
  1781.     
  1782.     /**
  1783.      * Returns the decorated <code>Component</code> used as a stamp to render
  1784.      * the specified cell. Overrides superclass version to provide support for
  1785.      * cell decorators. 
  1786.      * 
  1787.      * Adjusts component orientation (guaranteed to happen before applying 
  1788.      * Highlighters).
  1789.      * see - https://swingx.dev.java.net/issues/show_bug.cgi?id=145
  1790.      * 
  1791.      * @param renderer
  1792.      *            the <code>TableCellRenderer</code> to prepare
  1793.      * @param row
  1794.      *            the row of the cell to render, where 0 is the first row
  1795.      * @param column
  1796.      *            the column of the cell to render, where 0 is the first column
  1797.      * @return the decorated <code>Component</code> used as a stamp to render
  1798.      *         the specified cell
  1799.      * @see org.jdesktop.swingx.decorator.Highlighter
  1800.      */
  1801.     public Component prepareRenderer(TableCellRenderer renderer, int row,
  1802.             int column) {
  1803.         Component stamp = super.prepareRenderer(renderer, row, column);
  1804.         adjustComponentOrientation(stamp);
  1805.         if (highlighters == null) {
  1806.             return stamp; // no need to decorate renderer with highlighters
  1807.         } else {
  1808.             // PENDING - JW: code duplication - 
  1809.             // add method to access component adapter with row/column
  1810.             // set as needed!
  1811.             ComponentAdapter adapter = getComponentAdapter();
  1812.             adapter.row = row;
  1813.             adapter.column = column;
  1814.             return highlighters.apply(stamp, adapter);
  1815.         }
  1816.     }
  1817.     
  1818.     /**
  1819.      * Overridden to adjust the editor's component orientation if 
  1820.      * appropriate.
  1821.      */
  1822.     @Override
  1823.     public Component prepareEditor(TableCellEditor editor, int row, int column) {
  1824.         Component comp =  super.prepareEditor(editor, row, column);
  1825.         adjustComponentOrientation(comp);
  1826.         return comp;
  1827.     }
  1828.     /**
  1829.      * adjusts the Component's orientation to JXTable's CO if appropriate.
  1830.      * Here: always.
  1831.      * 
  1832.      * @param stamp
  1833.      */
  1834.     protected void adjustComponentOrientation(Component stamp) {
  1835.         if (stamp.getComponentOrientation().equals(getComponentOrientation())) return;
  1836.         stamp.applyComponentOrientation(getComponentOrientation());
  1837.     }
  1838.     /**
  1839.      * Returns a new instance of the default renderer for the specified class.
  1840.      * This differs from <code>getDefaultRenderer()</code> in that it returns
  1841.      * a <b>new </b> instance each time so that the renderer may be set and
  1842.      * customized on a particular column.
  1843.      * 
  1844.      * PENDING: must not return null!
  1845.      * 
  1846.      * @param columnClass
  1847.      *            Class of value being rendered
  1848.      * @return TableCellRenderer instance which renders values of the specified
  1849.      *         type
  1850.      */
  1851.     public TableCellRenderer getNewDefaultRenderer(Class columnClass) {
  1852.         TableCellRenderer renderer = getDefaultRenderer(columnClass);
  1853.         if (renderer != null) {
  1854.             try {
  1855.                 return (TableCellRenderer) renderer.getClass().newInstance();
  1856.             } catch (Exception e) {
  1857.                 e.printStackTrace();
  1858.             }
  1859.         }
  1860.         return null;
  1861.     }
  1862.     /** ? */
  1863.     protected void createDefaultEditors() {
  1864.         super.createDefaultEditors();
  1865. //        setLazyEditor(LinkModel.class, "org.jdesktop.swingx.LinkRenderer");
  1866.     }
  1867.     /**
  1868.      * Creates default cell renderers for objects, numbers, doubles, dates,
  1869.      * booleans, icons, and links.
  1870.      * THINK: delegate to TableCellRenderers?
  1871.      * 
  1872.      */
  1873.     protected void createDefaultRenderers() {
  1874.         // super.createDefaultRenderers();
  1875.         // This duplicates JTable's functionality in order to make the renderers
  1876.         // available in getNewDefaultRenderer(); If JTable's renderers either
  1877.         // were public, or it provided a factory for *new* renderers, this would
  1878.         // not be needed
  1879.         defaultRenderersByColumnClass = new UIDefaults();
  1880.         // Objects
  1881.         setLazyRenderer(Object.class,
  1882.                 "javax.swing.table.DefaultTableCellRenderer");
  1883.         // Numbers
  1884.         setLazyRenderer(Number.class,
  1885.                 "org.jdesktop.swingx.JXTable$NumberRenderer");
  1886.         // Doubles and Floats
  1887.         setLazyRenderer(Float.class,
  1888.                 "org.jdesktop.swingx.JXTable$DoubleRenderer");
  1889.         setLazyRenderer(Double.class,
  1890.                 "org.jdesktop.swingx.JXTable$DoubleRenderer");
  1891.         // Dates
  1892.         setLazyRenderer(Date.class, "org.jdesktop.swingx.JXTable$DateRenderer");
  1893.         // Icons and ImageIcons
  1894.         setLazyRenderer(Icon.class, "org.jdesktop.swingx.JXTable$IconRenderer");
  1895.         setLazyRenderer(ImageIcon.class,
  1896.                 "org.jdesktop.swingx.JXTable$IconRenderer");
  1897.         // Booleans
  1898.         setLazyRenderer(Boolean.class,
  1899.                 "org.jdesktop.swingx.JXTable$BooleanRenderer");
  1900.         // Other
  1901.         setLazyRenderer(LinkModel.class, "org.jdesktop.swingx.LinkRenderer");
  1902.     }
  1903.     /** ? */
  1904.     private void setLazyValue(Hashtable h, Class c, String s) {
  1905.         h.put(c, new UIDefaults.ProxyLazyValue(s));
  1906.     }
  1907.     /** ? */
  1908.     private void setLazyRenderer(Class c, String s) {
  1909.         setLazyValue(defaultRenderersByColumnClass, c, s);
  1910.     }
  1911.     /** ? */
  1912.     private void setLazyEditor(Class c, String s) {
  1913.         setLazyValue(defaultEditorsByColumnClass, c, s);
  1914.     }
  1915.     /*
  1916.      * Default Type-based Renderers: JTable's default table cell renderer
  1917.      * classes are private and JTable:getDefaultRenderer() returns a *shared*
  1918.      * cell renderer instance, thus there is no way for us to instantiate a new
  1919.      * instance of one of its default renderers. So, we must replicate the
  1920.      * default renderer classes here so that we can instantiate them when we
  1921.      * need to create renderers to be set on specific columns.
  1922.      */
  1923.     public static class NumberRenderer extends DefaultTableCellRenderer {
  1924.         public NumberRenderer() {
  1925.             super();
  1926.             setHorizontalAlignment(JLabel.TRAILING);
  1927.         }
  1928.     }
  1929.     public static class DoubleRenderer extends NumberRenderer {
  1930.         NumberFormat formatter;
  1931.         public DoubleRenderer() {
  1932.             super();
  1933.         }
  1934.         public void setValue(Object value) {
  1935.             if (formatter == null) {
  1936.                 formatter = NumberFormat.getInstance();
  1937.             }
  1938.             setText((value == null) ? "" : formatter.format(value));
  1939.         }
  1940.     }
  1941.     public static class DateRenderer extends DefaultTableCellRenderer {
  1942.         DateFormat formatter;
  1943.         public DateRenderer() {
  1944.             super();
  1945.         }
  1946.         public void setValue(Object value) {
  1947.             if (formatter == null) {
  1948.                 formatter = DateFormat.getDateInstance();
  1949.             }
  1950.             setText((value == null) ? "" : formatter.format(value));
  1951.         }
  1952.     }
  1953.     public static class IconRenderer extends DefaultTableCellRenderer {
  1954.         public IconRenderer() {
  1955.             super();
  1956.             setHorizontalAlignment(JLabel.CENTER);
  1957.         }
  1958.         public void setValue(Object value) {
  1959.             setIcon((value instanceof Icon) ? (Icon) value : null);
  1960.         }
  1961.     }
  1962.     public static class BooleanRenderer extends JCheckBox implements
  1963.             TableCellRenderer {
  1964.         public BooleanRenderer() {
  1965.             super();
  1966.             setHorizontalAlignment(JLabel.CENTER);
  1967.         }
  1968.         public Component getTableCellRendererComponent(JTable table,
  1969.                 Object value, boolean isSelected, boolean hasFocus, int row,
  1970.                 int column) {
  1971.             if (isSelected) {
  1972.                 setForeground(table.getSelectionForeground());
  1973.                 super.setBackground(table.getSelectionBackground());
  1974.             } else {
  1975.                 setForeground(table.getForeground());
  1976.                 setBackground(table.getBackground());
  1977.             }
  1978.             setSelected((value != null && ((Boolean) value).booleanValue()));
  1979.             return this;
  1980.         }
  1981.     }
  1982. //---------------------------- updateUI support
  1983.     
  1984.     /**
  1985.      * bug fix: super doesn't update all renderers/editors.
  1986.      */
  1987.     public void updateUI() {
  1988.         super.updateUI();
  1989.         // JW PENDING: update columnControl
  1990.         if (columnControlButton != null) {
  1991.             columnControlButton.updateUI();
  1992.         }
  1993.         for (Enumeration defaultEditors = defaultEditorsByColumnClass
  1994.                 .elements(); defaultEditors.hasMoreElements();) {
  1995.             updateEditorUI(defaultEditors.nextElement());
  1996.         }
  1997.         for (Enumeration defaultRenderers = defaultRenderersByColumnClass
  1998.                 .elements(); defaultRenderers.hasMoreElements();) {
  1999.             updateRendererUI(defaultRenderers.nextElement());
  2000.         }
  2001.         Enumeration columns = getColumnModel().getColumns();
  2002.         if (getColumnModel() instanceof TableColumnModelExt) {
  2003.             columns = Collections
  2004.                     .enumeration(((TableColumnModelExt) getColumnModel())
  2005.                             .getAllColumns());
  2006.         }
  2007.         while (columns.hasMoreElements()) {
  2008.             TableColumn column = (TableColumn) columns.nextElement();
  2009.             updateEditorUI(column.getCellEditor());
  2010.             updateRendererUI(column.getCellRenderer());
  2011.             updateRendererUI(column.getHeaderRenderer());
  2012.         }
  2013.         updateRowHeightUI(true);
  2014.         configureViewportBackground();
  2015.     }
  2016.     /** ? */
  2017.     private void updateRowHeightUI(boolean respectRowSetFlag) {
  2018.         if (respectRowSetFlag && isXTableRowHeightSet)
  2019.             return;
  2020.         int minimumSize = getFont().getSize() + 6;
  2021.         int uiSize = UIManager.getInt(UIPREFIX + "rowHeight");
  2022.         setRowHeight(Math.max(minimumSize, uiSize != 0 ? uiSize : 18));
  2023.         isXTableRowHeightSet = false;
  2024.     }
  2025.     /** Changes the row height for all rows in the table. */
  2026.     public void setRowHeight(int rowHeight) {
  2027.         super.setRowHeight(rowHeight);
  2028.         if (rowHeight > 0) {
  2029.             isXTableRowHeightSet = true;
  2030.         }
  2031.         updateViewSizeSequence();
  2032.     }
  2033.     
  2034.     public void setRowHeight(int row, int rowHeight) {
  2035.         if (!isRowHeightEnabled()) return;
  2036.         super.setRowHeight(row, rowHeight);
  2037.         updateViewSizeSequence();
  2038.         resizeAndRepaint();
  2039.     }
  2040.     /**
  2041.      * sets enabled state of individual rowHeight support. The default 
  2042.      * is false.
  2043.      * Enabling the support envolves reflective access
  2044.      * to super's private field rowModel which may fail due to security
  2045.      * issues. If failing the support is not enabled.
  2046.      * 
  2047.      * PENDING: should we throw an Exception if the enabled fails? 
  2048.      * Or silently fail - depends on runtime context, 
  2049.      * can't do anything about it.
  2050.      * 
  2051.      * @param enabled
  2052.      */
  2053.     public void setRowHeightEnabled(boolean enabled) {
  2054.         boolean old = isRowHeightEnabled();
  2055.         if (old == enabled) return;
  2056.         if (enabled && !canEnableRowHeight()) return;
  2057.         rowHeightEnabled = enabled;
  2058.         if (!enabled) {
  2059.             adminSetRowHeight(getRowHeight());
  2060.         }
  2061.         firePropertyChange("rowHeightEnabled", old, rowHeightEnabled);
  2062.     }
  2063.     
  2064.     private boolean canEnableRowHeight() {
  2065.         return getRowModelField() != null;
  2066.     }
  2067.     public boolean isRowHeightEnabled() {
  2068.         return rowHeightEnabled;
  2069.     }
  2070.     private SizeSequence getSuperRowModel() {
  2071.         try {
  2072.             Field field = getRowModelField();
  2073.             if (field != null) {
  2074.                 return (SizeSequence) field.get(this);
  2075.             }
  2076.         } catch (SecurityException e) {
  2077.             // TODO Auto-generated catch block
  2078.             e.printStackTrace();
  2079.         } catch (IllegalArgumentException e) {
  2080.             // TODO Auto-generated catch block
  2081.             e.printStackTrace();
  2082.         } catch (IllegalAccessException e) {
  2083.             // TODO Auto-generated catch block
  2084.             e.printStackTrace();
  2085.         }
  2086.         return null;
  2087.     }
  2088.     /**
  2089.      * @return
  2090.      * @throws NoSuchFieldException
  2091.      */
  2092.     private Field getRowModelField() {
  2093.         if (rowModelField == null) {
  2094.             try {
  2095.                 rowModelField = JTable.class.getDeclaredField("rowModel");
  2096.                 rowModelField.setAccessible(true);
  2097.             } catch (SecurityException e) {
  2098.                 rowModelField = null;
  2099.                 e.printStackTrace();
  2100.             } catch (NoSuchFieldException e) {
  2101.                 // TODO Auto-generated catch block
  2102.                 e.printStackTrace();
  2103.             }
  2104.         }
  2105.         return rowModelField;
  2106.     }
  2107.     
  2108.     /**
  2109.      * 
  2110.      * @return
  2111.      */
  2112.     protected RowSizing getRowSizing() {
  2113.         if (rowSizing == null) {
  2114.             rowSizing = new RowSizing(filters);
  2115.         }
  2116.         return rowSizing;
  2117.     }
  2118.     /**
  2119.      * calling setRowHeight for internal reasons.
  2120.      * Keeps the isXTableRowHeight unchanged.
  2121.      */
  2122.     protected void adminSetRowHeight(int rowHeight) {
  2123.         boolean heightSet = isXTableRowHeightSet;
  2124.         setRowHeight(rowHeight); 
  2125.         isXTableRowHeightSet = heightSet;
  2126.     }
  2127.     private void updateEditorUI(Object value) {
  2128.         // maybe null or proxyValue
  2129.         if (!(value instanceof TableCellEditor))
  2130.             return;
  2131.         // super handled this
  2132.         if ((value instanceof JComponent)
  2133.                 || (value instanceof DefaultCellEditor))
  2134.             return;
  2135.         // custom editors might balk about fake rows/columns
  2136.         try {
  2137.             Component comp = ((TableCellEditor) value)
  2138.                     .getTableCellEditorComponent(this, null, false, -1, -1);
  2139.             if (comp instanceof JComponent) {
  2140.                 ((JComponent) comp).updateUI();
  2141.             }
  2142.         } catch (Exception e) {
  2143.             // ignore - can't do anything
  2144.         }
  2145.     }
  2146.     /** ? */
  2147.     private void updateRendererUI(Object value) {
  2148.         // maybe null or proxyValue
  2149.         if (!(value instanceof TableCellRenderer))
  2150.             return;
  2151.         // super handled this
  2152.         if (value instanceof JComponent)
  2153.             return;
  2154.         // custom editors might balk about fake rows/columns
  2155.         try {
  2156.             Component comp = ((TableCellRenderer) value)
  2157.                     .getTableCellRendererComponent(this, null, false, false,
  2158.                             -1, -1);
  2159.             if (comp instanceof JComponent) {
  2160.                 ((JComponent) comp).updateUI();
  2161.             }
  2162.         } catch (Exception e) {
  2163.             // ignore - can't do anything
  2164.         }
  2165.     }
  2166.     
  2167. //---------------------------- overriding super factory methods and buggy
  2168.     /**
  2169.      * workaround bug in JTable. (Bug Parade ID #6291631 - negative y is mapped
  2170.      * to row 0).
  2171.      */
  2172.     public int rowAtPoint(Point point) {
  2173.         if (point.y < 0)
  2174.             return -1;
  2175.         return super.rowAtPoint(point);
  2176.     }
  2177.     
  2178.     /** ? */
  2179.     protected JTableHeader createDefaultTableHeader() {
  2180.         return new JXTableHeader(columnModel);
  2181.     }
  2182.     /** ? */
  2183.     protected TableColumnModel createDefaultColumnModel() {
  2184.         return new DefaultTableColumnModelExt();
  2185.     }
  2186.     
  2187. }