001    /*
002     * $Id: JXTable.java 3425 2009-07-30 11:18:51Z kleopatra $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.applet.Applet;
025    import java.awt.Color;
026    import java.awt.Component;
027    import java.awt.ComponentOrientation;
028    import java.awt.Container;
029    import java.awt.Dimension;
030    import java.awt.KeyboardFocusManager;
031    import java.awt.Point;
032    import java.awt.Rectangle;
033    import java.awt.Window;
034    import java.awt.event.ActionEvent;
035    import java.awt.print.PrinterException;
036    import java.beans.PropertyChangeEvent;
037    import java.beans.PropertyChangeListener;
038    import java.util.Collections;
039    import java.util.Comparator;
040    import java.util.Date;
041    import java.util.Enumeration;
042    import java.util.EventObject;
043    import java.util.HashMap;
044    import java.util.Hashtable;
045    import java.util.Iterator;
046    import java.util.List;
047    import java.util.Locale;
048    import java.util.Map;
049    import java.util.TreeSet;
050    import java.util.Vector;
051    import java.util.logging.Level;
052    import java.util.logging.Logger;
053    
054    import javax.swing.Action;
055    import javax.swing.ActionMap;
056    import javax.swing.DefaultCellEditor;
057    import javax.swing.Icon;
058    import javax.swing.ImageIcon;
059    import javax.swing.JCheckBox;
060    import javax.swing.JComponent;
061    import javax.swing.JLabel;
062    import javax.swing.JPopupMenu;
063    import javax.swing.JScrollPane;
064    import javax.swing.JTable;
065    import javax.swing.JTextField;
066    import javax.swing.JViewport;
067    import javax.swing.KeyStroke;
068    import javax.swing.ListSelectionModel;
069    import javax.swing.RowSorter;
070    import javax.swing.ScrollPaneConstants;
071    import javax.swing.SortOrder;
072    import javax.swing.SwingUtilities;
073    import javax.swing.UIDefaults;
074    import javax.swing.UIManager;
075    import javax.swing.RowSorter.SortKey;
076    import javax.swing.border.LineBorder;
077    import javax.swing.event.ChangeEvent;
078    import javax.swing.event.ChangeListener;
079    import javax.swing.event.ListSelectionEvent;
080    import javax.swing.event.TableColumnModelEvent;
081    import javax.swing.event.TableModelEvent;
082    import javax.swing.table.JTableHeader;
083    import javax.swing.table.TableCellEditor;
084    import javax.swing.table.TableCellRenderer;
085    import javax.swing.table.TableColumn;
086    import javax.swing.table.TableColumnModel;
087    import javax.swing.table.TableModel;
088    
089    import org.jdesktop.swingx.action.AbstractActionExt;
090    import org.jdesktop.swingx.action.BoundAction;
091    import org.jdesktop.swingx.decorator.ComponentAdapter;
092    import org.jdesktop.swingx.decorator.CompoundHighlighter;
093    import org.jdesktop.swingx.decorator.Highlighter;
094    import org.jdesktop.swingx.decorator.ResetDTCRColorHighlighter;
095    import org.jdesktop.swingx.decorator.UIDependent;
096    import org.jdesktop.swingx.event.TableColumnModelExtListener;
097    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
098    import org.jdesktop.swingx.plaf.UIManagerExt;
099    import org.jdesktop.swingx.renderer.AbstractRenderer;
100    import org.jdesktop.swingx.renderer.CheckBoxProvider;
101    import org.jdesktop.swingx.renderer.DefaultTableRenderer;
102    import org.jdesktop.swingx.renderer.IconValues;
103    import org.jdesktop.swingx.renderer.MappedValue;
104    import org.jdesktop.swingx.renderer.StringValue;
105    import org.jdesktop.swingx.renderer.StringValues;
106    import org.jdesktop.swingx.rollover.RolloverProducer;
107    import org.jdesktop.swingx.rollover.TableRolloverController;
108    import org.jdesktop.swingx.rollover.TableRolloverProducer;
109    import org.jdesktop.swingx.search.AbstractSearchable;
110    import org.jdesktop.swingx.search.SearchFactory;
111    import org.jdesktop.swingx.search.Searchable;
112    import org.jdesktop.swingx.search.TableSearchable;
113    import org.jdesktop.swingx.sort.SortController;
114    import org.jdesktop.swingx.sort.SortUtils;
115    import org.jdesktop.swingx.sort.TableSortController;
116    import org.jdesktop.swingx.table.ColumnControlButton;
117    import org.jdesktop.swingx.table.ColumnFactory;
118    import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
119    import org.jdesktop.swingx.table.TableColumnExt;
120    import org.jdesktop.swingx.table.TableColumnModelExt;
121    
122    /**
123     * Enhanced Table component with support for general SwingX sorting/filtering,
124     * rendering, highlighting, rollover and search functionality. Table specific
125     * enhancements include runtime configuration options like toggle column
126     * visibility, column sizing, PENDING JW ...
127     * 
128     * <h2>Sorting and Filtering</h2>
129     * 
130     * JXTable supports sorting and filtering of rows (switched to core sorting). 
131     * 
132     * Additionally, it provides api to apply
133     * a specific sort order, to toggle the sort order of columns identified 
134     * by view index or column identifier and to reset all sorts. F.i: 
135     * 
136     * <pre><code>
137     * table.setSortOrder("PERSON_ID", SortOrder.DESCENDING);
138     * table.toggleSortOder(4);
139     * table.resetSortOrder();
140     * </code></pre>
141     * 
142     * Sorting sequence can be configured per column by setting the TableColumnExt's
143     * <code>comparator</code> property. Sorting can be disabled per column - setting the TableColumnExt's
144     * <code>sortable</code> or per table by {@link #setSortable(boolean)}. 
145     * The table takes responsibility to propagate these
146     * properties to the current sorter, if available <p>
147     * 
148     * Note that the enhanced sorting controls are effective only if the RowSorter is 
149     * of type SortController, which it is by default. Different from core JTable, the 
150     * autoCreateRowSorter property is enabled by default. If on, the JXTable creates and
151     * uses a default row sorter as returned by the createDefaultRowSorter method.
152     * 
153     * <p>
154     * Typically, a JXTable is sortable by left clicking on column headers. By default, each
155     * subsequent click on a header reverses the order of the sort, and a sort arrow
156     * icon is automatically drawn on the header. 
157     * 
158     * <p>
159     * 
160     * <h2>Rendering and Highlighting</h2>
161     * 
162     * As all SwingX collection views, a JXTable is a HighlighterClient (PENDING JW:
163     * formally define and implement, like in AbstractTestHighlighter), that is it
164     * provides consistent api to add and remove Highlighters which can visually
165     * decorate the rendering component.
166     * 
167     * <p>
168     * An example multiple highlighting (default striping as appropriate for the
169     * current LookAndFeel, cell foreground on matching pattern, and shading a
170     * column):
171     * 
172     * <pre><code>
173     * 
174     * Highlighter simpleStriping = HighlighterFactory.createSimpleStriping();
175     * PatternPredicate patternPredicate = new PatternPredicate(&quot;&circ;M&quot;, 1);
176     * ColorHighlighter magenta = new ColorHighlighter(patternPredicate, null,
177     *       Color.MAGENTA, null, Color.MAGENTA);
178     * Highlighter shading = new ShadingColorHighlighter(
179     *       new HighlightPredicate.ColumnHighlightPredicate(1));
180     * 
181     * table.setHighlighters(simpleStriping,
182     *        magenta,
183     *        shading);
184     * </code></pre>
185     * 
186     * <p>
187     * To fully support, JXTable registers SwingX default table renderers instead of
188     * core defaults (see {@link DefaultTableRenderer}) The recommended approach for
189     * customizing rendered content it to intall a DefaultTableRenderer configured
190     * with a custom String- and/or IconValue. F.i. assuming the cell value is a
191     * File and should be rendered by showing its name followed and date of last
192     * change:
193     * 
194     * <pre><code>
195     * StringValue sv = new StringValue() {
196     *      public String getString(Object value) {
197     *        if (!(value instanceof File)) return StringValues.TO_STRING.getString(value);
198     *        return StringValues.FILE_NAME.getString(value) + &quot;, &quot; 
199     *           + StringValues.DATE_TO_STRING.getString(((File) value).lastModified());
200     * }};
201     * table.setCellRenderer(File.class, new DefaultTableRenderer(sv));
202     * </code></pre>
203     * 
204     * <p>
205     * <b>Note</b>: DefaultTableCellRenderer and subclasses require a hack to play
206     * nicely with Highlighters because it has an internal "color memory" in
207     * setForeground/setBackground. The hack is applied by default which might lead
208     * to unexpected side-effects in custom renderers subclassing DTCR. See
209     * {@link #resetDefaultTableCellRendererHighlighter} for details.
210     * 
211     * 
212     * <h2>Rollover</h2>
213     * 
214     * As all SwingX collection views, a JXTable supports per-cell rollover which is
215     * enabled by default. If enabled, the component fires rollover events on
216     * enter/exit of a cell which by default is promoted to the renderer if it
217     * implements RolloverRenderer, that is simulates live behaviour. The rollover
218     * events can be used by client code as well, f.i. to decorate the rollover row
219     * using a Highlighter.
220     * 
221     * <pre><code>
222     * JXTable table = new JXTable();
223     * table.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, 
224     *      null, Color.RED);      
225     * </code></pre>
226     * 
227     * <h2>Search</h2>
228     * 
229     * As all SwingX collection views, a JXTable is searchable. A search action is
230     * registered in its ActionMap under the key "find". The default behaviour is to
231     * ask the SearchFactory to open a search component on this component. The
232     * default keybinding is retrieved from the SearchFactory, typically ctrl-f (or
233     * cmd-f for Mac). Client code can register custom actions and/or bindings as
234     * appropriate.
235     * <p>
236     * 
237     * JXTable provides api to vend a renderer-controlled String representation of
238     * cell content. This allows the Searchable and Highlighters to use WYSIWYM
239     * (What-You-See-Is-What-You-Match), that is pattern matching against the actual
240     * string as seen by the user.
241     * 
242     * <h2>Column Configuration</h2>
243     * 
244     * JXTable's default column model
245     * is of type TableColumnModelExt which allows management of hidden columns. 
246     * Furthermore, it guarantees to delegate creation and configuration of table columns
247     * to its ColumnFactory. The factory is meant as the central place to 
248     * customize column configuration.
249     * 
250     * <p>
251     * Columns can be hidden or shown by setting the visible property on the
252     * TableColumnExt using {@link TableColumnExt#setVisible(boolean)}. Columns can
253     * also be shown or hidden from the column control popup.
254     * 
255     * <p>
256     * The column control popup is triggered by an icon drawn to the far right of
257     * the column headers, above the table's scrollbar (when installed in a
258     * JScrollPane). The popup allows the user to select which columns should be
259     * shown or hidden, as well as to pack columns and turn on horizontal scrolling.
260     * To show or hide the column control, use the
261     * {@link #setColumnControlVisible(boolean show)}method.
262     * 
263     * <p>
264     * You can resize all columns, selected columns, or a single column using the
265     * methods like {@link #packAll()}. Packing combines several other aspects of a
266     * JXTable. If horizontal scrolling is enabled using
267     * {@link #setHorizontalScrollEnabled(boolean)}, then the scrollpane will allow
268     * the table to scroll right-left, and columns will be sized to their preferred
269     * size. To control the preferred sizing of a column, you can provide a
270     * prototype value for the column in the TableColumnExt using
271     * {@link TableColumnExt#setPrototypeValue(Object)}. The prototype is used as an
272     * indicator of the preferred size of the column. This can be useful if some
273     * data in a given column is very long, but where the resize algorithm would
274     * normally not pick this up.
275     * 
276     * <p>
277     * 
278     * 
279     * <p>
280     * Keys/Actions registered with this component:
281     * 
282     * <ul>
283     * <li>"find" - open an appropriate search widget for searching cell content.
284     * The default action registeres itself with the SearchFactory as search target.
285     * <li>"print" - print the table
286     * <li> {@link JXTable#HORIZONTALSCROLL_ACTION_COMMAND} - toggle the horizontal
287     * scrollbar
288     * <li> {@link JXTable#PACKSELECTED_ACTION_COMMAND} - resize the selected column
289     * to fit the widest cell content
290     * <li> {@link JXTable#PACKALL_ACTION_COMMAND} - resize all columns to fit the
291     * widest cell content in each column
292     * 
293     * </ul>
294     * 
295     * <p>
296     * Key bindings.
297     * 
298     * <ul>
299     * <li>"control F" - bound to actionKey "find".
300     * </ul>
301     * 
302     * <p>
303     * Client Properties.
304     * 
305     * <ul>
306     * <li> {@link JXTable#MATCH_HIGHLIGHTER} - set to Boolean.TRUE to use a
307     * SearchHighlighter to mark a cell as matching.
308     * </ul>
309     * 
310     * @author Ramesh Gupta
311     * @author Amy Fowler
312     * @author Mark Davidson
313     * @author Jeanette Winzenburg
314     * 
315     */
316    public class JXTable extends JTable implements TableColumnModelExtListener {
317    
318        /**
319         * 
320         */
321        public static final String FOCUS_PREVIOUS_COMPONENT = "focusPreviousComponent";
322    
323        /**
324         * 
325         */
326        public static final String FOCUS_NEXT_COMPONENT = "focusNextComponent";
327    
328        private static final Logger LOG = Logger.getLogger(JXTable.class.getName());
329    
330        /**
331         * Identifier of show horizontal scroll action, used in JXTable's
332         * <code>ActionMap</code>.
333         * 
334         */
335        public static final String HORIZONTALSCROLL_ACTION_COMMAND = ColumnControlButton.COLUMN_CONTROL_MARKER
336                + "horizontalScroll";
337    
338        /**
339         * Identifier of pack table action, used in JXTable's <code>ActionMap</code>
340         * .
341         */
342        public static final String PACKALL_ACTION_COMMAND = ColumnControlButton.COLUMN_CONTROL_MARKER
343                + "packAll";
344    
345        /**
346         * Identifier of pack selected column action, used in JXTable's
347         * <code>ActionMap</code>.
348         */
349        public static final String PACKSELECTED_ACTION_COMMAND = ColumnControlButton.COLUMN_CONTROL_MARKER
350                + "packSelected";
351    
352        /**
353         * The prefix marker to find table related properties in the
354         * <code>ResourceBundle</code>.
355         */
356        public static final String UIPREFIX = "JXTable.";
357    
358        /** key for client property to use SearchHighlighter as match marker. */
359        public static final String MATCH_HIGHLIGHTER = AbstractSearchable.MATCH_HIGHLIGHTER;
360    
361        static {
362            // Hack: make sure the resource bundle is loaded
363            LookAndFeelAddons.getAddon();
364        }
365    
366        /** The CompoundHighlighter for the table. */
367        protected CompoundHighlighter compoundHighlighter;
368    
369        /**
370         * The key for the client property deciding about whether the color memory
371         * hack for DefaultTableCellRenderer should be used.
372         * 
373         * @see #resetDefaultTableCellRendererHighlighter
374         */
375        public static final String USE_DTCR_COLORMEMORY_HACK = "useDTCRColorMemoryHack";
376    
377        /**
378         * The Highlighter used to hack around DefaultTableCellRenderer's color
379         * memory.
380         */
381        protected Highlighter resetDefaultTableCellRendererHighlighter;
382    
383        /** The ComponentAdapter for model data access. */
384        protected ComponentAdapter dataAdapter;
385    
386    
387        /** flag to indicate if table is interactively sortable. */
388        private boolean sortable;
389    
390        /** Listens for changes from the highlighters. */
391        private ChangeListener highlighterChangeListener;
392    
393        /** the factory to use for column creation and configuration. */
394        private ColumnFactory columnFactory;
395    
396        /** The default number of visible rows (in a ScrollPane). */
397        private int visibleRowCount = 20;
398    
399        /** The default number of visible columns (in a ScrollPane). */
400        private int visibleColumnCount = -1;
401    
402    
403        /**
404         * Flag to indicate if the column control is visible.
405         */
406        private boolean columnControlVisible;
407    
408        /**
409         * ScrollPane's original vertical scroll policy. If the column control is
410         * visible the policy is set to ALWAYS.
411         */
412        private int verticalScrollPolicy;
413    
414        /**
415         * The component used a column control in the upper trailing corner of an
416         * enclosing <code>JScrollPane</code>.
417         */
418        private JComponent columnControlButton;
419    
420        /**
421         * Mouse/Motion/Listener keeping track of mouse moved in cell coordinates.
422         */
423        private RolloverProducer rolloverProducer;
424    
425        /**
426         * RolloverController: listens to cell over events and repaints
427         * entered/exited rows.
428         */
429        private TableRolloverController<JXTable> linkController;
430    
431        /**
432         * field to store the autoResizeMode while interactively setting horizontal
433         * scrollbar to visible.
434         */
435        private int oldAutoResizeMode;
436    
437        /**
438         * flag to indicate enhanced auto-resize-off behaviour is on. This is
439         * set/reset in setHorizontalScrollEnabled.
440         */
441        private boolean intelliMode;
442    
443        /**
444         * internal flag indicating that we are in super.doLayout(). (used in
445         * columnMarginChanged to not update the resizingCol's prefWidth).
446         */
447        private boolean inLayout;
448    
449        /**
450         * Flag to distinguish internal settings of row height from client code
451         * settings. The rowHeight will be internally adjusted to font size on
452         * instantiation and in updateUI if the height has not been set explicitly
453         * by the application.
454         * 
455         * @see #adminSetRowHeight(int)
456         * @see #setRowHeight(int)
457         */
458        protected boolean isXTableRowHeightSet;
459    
460        /** property to control search behaviour. */
461        protected Searchable searchable;
462    
463        /** property to control table's editability as a whole. */
464        private boolean editable;
465    
466        private Dimension calculatedPrefScrollableViewportSize;
467        /** flag to indicate whether the rowSorter is auto-created. */
468        private boolean autoCreateRowSorter;
469        /** flag to indicate whether model update events should trigger resorts. */
470        private boolean sortsOnUpdates;
471        /** flag to indicate that it's unsafe to update sortable-related sorter properties. */
472        private boolean ignoreAddColumn;
473        
474    
475        /** Instantiates a JXTable with a default table model, no data. */
476        public JXTable() {
477            init();
478        }
479    
480        /**
481         * Instantiates a JXTable with a specific table model.
482         * 
483         * @param dm The model to use.
484         */
485        public JXTable(TableModel dm) {
486            super(dm);
487            init();
488        }
489    
490        /**
491         * Instantiates a JXTable with a specific table model.
492         * 
493         * @param dm The model to use.
494         */
495        public JXTable(TableModel dm, TableColumnModel cm) {
496            super(dm, cm);
497            init();
498        }
499    
500        /**
501         * Instantiates a JXTable with a specific table model, column model, and
502         * selection model.
503         * 
504         * @param dm The table model to use.
505         * @param cm The column model to use.
506         * @param sm The list selection model to use.
507         */
508        public JXTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) {
509            super(dm, cm, sm);
510            init();
511        }
512    
513        /**
514         * Instantiates a JXTable for a given number of columns and rows.
515         * 
516         * @param numRows Count of rows to accommodate.
517         * @param numColumns Count of columns to accommodate.
518         */
519        public JXTable(int numRows, int numColumns) {
520            super(numRows, numColumns);
521            init();
522        }
523    
524        /**
525         * Instantiates a JXTable with data in a vector or rows and column names.
526         * 
527         * @param rowData Row data, as a Vector of Objects.
528         * @param columnNames Column names, as a Vector of Strings.
529         */
530        public JXTable(Vector rowData, Vector columnNames) {
531            super(rowData, columnNames);
532            init();
533        }
534    
535        /**
536         * Instantiates a JXTable with data in a array or rows and column names.
537         * 
538         * @param rowData Row data, as a two-dimensional Array of Objects (by row,
539         *        for column).
540         * @param columnNames Column names, as a Array of Strings.
541         */
542        public JXTable(Object[][] rowData, Object[] columnNames) {
543            super(rowData, columnNames);
544            init();
545        }
546    
547        /**
548         * Initializes the table for use.
549         * 
550         */
551        private void init() {
552            putClientProperty(USE_DTCR_COLORMEMORY_HACK, Boolean.TRUE);
553            setEditable(true);
554            setAutoCreateRowSorter(true);
555            setSortsOnUpdates(true);
556            // PENDING JW: how to relate to auto-createRowSorter?
557            setSortable(true);
558            setRolloverEnabled(true);
559            setTerminateEditOnFocusLost(true);
560            initActionsAndBindings();
561            initFocusBindings();
562            // instantiate row height depending ui setting or font size.
563            updateRowHeightUI(false);
564            // set to null - don't want hard-coded pixel sizes.
565            setPreferredScrollableViewportSize(null);
566            // PENDING: need to duplicate here..
567            // why doesn't the call in tableChanged work?
568            initializeColumnWidths();
569            setFillsViewportHeight(true);
570            updateLocaleState(getLocale());
571        }
572    
573    //--------------- Rollover support    
574        /**
575         * Sets the property to enable/disable rollover support. If enabled, this component
576         * fires property changes on per-cell mouse rollover state, i.e. 
577         * when the mouse enters/leaves a list cell. <p>
578         * 
579         * This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell 
580         * rendered by a JXHyperlink.<p>
581         * 
582         * The default value is true.
583         * 
584         * @param rolloverEnabled a boolean indicating whether or not the rollover
585         *   functionality should be enabled.
586         * 
587         * @see #isRolloverEnabled()
588         * @see #getLinkController()
589         * @see #createRolloverProducer()
590         * @see org.jdesktop.swingx.rollover.RolloverRenderer  
591         */
592        public void setRolloverEnabled(boolean rolloverEnabled) {
593            boolean old = isRolloverEnabled();
594            if (rolloverEnabled == old)
595                return;
596            if (rolloverEnabled) {
597                rolloverProducer = createRolloverProducer();
598                addMouseListener(rolloverProducer);
599                addMouseMotionListener(rolloverProducer);
600                getLinkController().install(this);
601    
602            } else {
603                removeMouseListener(rolloverProducer);
604                removeMouseMotionListener(rolloverProducer);
605                rolloverProducer = null;
606                getLinkController().release();
607            }
608            firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
609        }
610    
611        /**
612         * Returns a boolean indicating whether or not rollover support is enabled. 
613         *
614         * @return a boolean indicating whether or not rollover support is enabled. 
615         * 
616         * @see #setRolloverEnabled(boolean)
617         */
618        public boolean isRolloverEnabled() {
619            return rolloverProducer != null;
620        }
621    
622        /**
623         * Returns the RolloverController for this component. Lazyly creates the 
624         * controller if necessary, that is the return value is guaranteed to be 
625         * not null. <p>
626         * 
627         * PENDING JW: rename to getRolloverController
628         * 
629         * @return the RolloverController for this tree, guaranteed to be not null.
630         * 
631         * @see #setRolloverEnabled(boolean)
632         * @see #createLinkController()
633         * @see org.jdesktop.swingx.rollover.RolloverController
634         */
635        protected TableRolloverController<JXTable> getLinkController() {
636            if (linkController == null) {
637                linkController = createLinkController();
638            }
639            return linkController;
640        }
641    
642        /**
643         * Creates and returns a RolloverController appropriate for this component.
644         * 
645         * @return a RolloverController appropriate for this component.
646         * 
647         * @see #getLinkController()
648         * @see org.jdesktop.swingx.rollover.RolloverController
649         */
650        protected TableRolloverController<JXTable> createLinkController() {
651            return new TableRolloverController<JXTable>();
652        }
653    
654        /**
655         * Creates and returns the RolloverProducer to use with this component.
656         * <p>
657         * 
658         * @return <code>RolloverProducer</code> to use with this component
659         * 
660         * @see #setRolloverEnabled(boolean)
661         */
662        protected RolloverProducer createRolloverProducer() {
663            return new TableRolloverProducer();
664        }
665    
666    
667        /**
668         * Returns the column control visible property.
669         * <p>
670         * 
671         * @return boolean to indicate whether the column control is visible.
672         * @see #setColumnControlVisible(boolean)
673         * @see #setColumnControl(JComponent)
674         */
675        public boolean isColumnControlVisible() {
676            return columnControlVisible;
677        }
678    
679        /**
680         * Sets the column control visible property. If true and
681         * <code>JXTable</code> is contained in a <code>JScrollPane</code>, the
682         * table adds the column control to the trailing corner of the scroll pane.
683         * <p>
684         * 
685         * Note: if the table is not inside a <code>JScrollPane</code> the column
686         * control is not shown even if this returns true. In this case it's the
687         * responsibility of the client code to actually show it.
688         * <p>
689         * 
690         * The default value is <code>false</code>.
691         * 
692         * @param visible boolean to indicate if the column control should be shown
693         * @see #isColumnControlVisible()
694         * @see #setColumnControl(JComponent)
695         * 
696         */
697        public void setColumnControlVisible(boolean visible) {
698            if (isColumnControlVisible() == visible)
699                return;
700            boolean old = isColumnControlVisible();
701            if (old) {
702                unconfigureColumnControl();
703            }
704            this.columnControlVisible = visible;
705            if (isColumnControlVisible()) {
706                configureColumnControl();
707            }
708            firePropertyChange("columnControlVisible", old, !old);
709    
710        }
711    
712        /**
713         * Returns the component used as column control. Lazily creates the control
714         * to the default if it is <code>null</code>.
715         * 
716         * @return component for column control, guaranteed to be != null.
717         * @see #setColumnControl(JComponent)
718         * @see #createDefaultColumnControl()
719         */
720        public JComponent getColumnControl() {
721            if (columnControlButton == null) {
722                columnControlButton = createDefaultColumnControl();
723            }
724            return columnControlButton;
725        }
726    
727        /**
728         * Sets the component used as column control. Updates the enclosing
729         * <code>JScrollPane</code> if appropriate. Passing a <code>null</code>
730         * parameter restores the column control to the default.
731         * <p>
732         * The component is automatically visible only if the
733         * <code>columnControlVisible</code> property is <code>true</code> and the
734         * table is contained in a <code>JScrollPane</code>.
735         * 
736         * <p>
737         * NOTE: from the table's perspective, the column control is simply a
738         * <code>JComponent</code> to add to and keep in the trailing corner of the
739         * scrollpane. (if any). It's up the concrete control to configure itself
740         * from and keep synchronized to the columns' states.
741         * <p>
742         * 
743         * @param columnControl the <code>JComponent</code> to use as columnControl.
744         * @see #getColumnControl()
745         * @see #createDefaultColumnControl()
746         * @see #setColumnControlVisible(boolean)
747         * 
748         */
749        public void setColumnControl(JComponent columnControl) {
750            // PENDING JW: release old column control? who's responsible?
751            // Could implement CCB.autoRelease()?
752            JComponent old = columnControlButton;
753            this.columnControlButton = columnControl;
754            configureColumnControl();
755            firePropertyChange("columnControl", old, getColumnControl());
756        }
757    
758        /**
759         * Creates the default column control used by this table. This
760         * implementation returns a <code>ColumnControlButton</code> configured with
761         * default <code>ColumnControlIcon</code>.
762         * 
763         * @return the default component used as column control.
764         * @see #setColumnControl(JComponent)
765         * @see org.jdesktop.swingx.table.ColumnControlButton
766         * @see org.jdesktop.swingx.icon.ColumnControlIcon
767         */
768        protected JComponent createDefaultColumnControl() {
769            return new ColumnControlButton(this);
770        }
771    
772        /**
773         * Sets the language-sensitive orientation that is to be used to order the
774         * elements or text within this component.
775         * <p>
776         * 
777         * Overridden to work around a core bug: <code>JScrollPane</code> can't cope
778         * with corners when changing component orientation at runtime. This method
779         * explicitly re-configures the column control.
780         * <p>
781         * 
782         * @param o the ComponentOrientation for this table.
783         * @see java.awt.Component#setComponentOrientation(ComponentOrientation)
784         */
785        @Override
786        public void setComponentOrientation(ComponentOrientation o) {
787            super.setComponentOrientation(o);
788            configureColumnControl();
789        }
790    
791        /**
792         * Configures the enclosing <code>JScrollPane</code>.
793         * <p>
794         * 
795         * Overridden to addionally configure the upper trailing corner with the
796         * column control.
797         * 
798         * @see #configureColumnControl()
799         * 
800         */
801        @Override
802        protected void configureEnclosingScrollPane() {
803            super.configureEnclosingScrollPane();
804            configureColumnControl();
805        }
806    
807        /**
808         * Unconfigures the enclosing <code>JScrollPane</code>.
809         * <p>
810         * 
811         * Overridden to addionally unconfigure the upper trailing corner with the
812         * column control.
813         * 
814         * @see #unconfigureColumnControl()
815         * 
816         */
817        @Override
818        protected void unconfigureEnclosingScrollPane() {
819            unconfigureColumnControl();
820            super.unconfigureEnclosingScrollPane();
821        }
822    
823        /**
824         * /** Unconfigures the upper trailing corner of an enclosing
825         * <code>JScrollPane</code>.
826         * 
827         * Here: removes the upper trailing corner and resets.
828         * 
829         * @see #setColumnControlVisible(boolean)
830         * @see #setColumnControl(JComponent)
831         */
832        protected void unconfigureColumnControl() {
833            Container p = getParent();
834            if (p instanceof JViewport) {
835                Container gp = p.getParent();
836                if (gp instanceof JScrollPane) {
837                    JScrollPane scrollPane = (JScrollPane) gp;
838                    // Make certain we are the viewPort's view and not, for
839                    // example, the rowHeaderView of the scrollPane -
840                    // an implementor of fixed columns might do this.
841                    JViewport viewport = scrollPane.getViewport();
842                    if (viewport == null || viewport.getView() != this) {
843                        return;
844                    }
845                    if (verticalScrollPolicy != 0) {
846                        // Fix #155-swingx: reset only if we had force always before
847                        // PENDING: JW - doesn't cope with dynamically changing the
848                        // policy
849                        // shouldn't be much of a problem because doesn't happen too
850                        // often??
851                        scrollPane.setVerticalScrollBarPolicy(verticalScrollPolicy);
852                        verticalScrollPolicy = 0;
853                    }
854                    if (isColumnControlVisible()) {
855                        scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
856                                null);
857                    }
858                }
859            }
860    
861        }
862    
863        /**
864         * Configures the upper trailing corner of an enclosing
865         * <code>JScrollPane</code>.
866         * 
867         * Adds the <code>ColumnControl</code> if the
868         * <code>columnControlVisible</code> property is true.
869         * <p>
870         * 
871         * @see #setColumnControlVisible(boolean)
872         * @see #setColumnControl(JComponent)
873         */
874        protected void configureColumnControl() {
875            Container p = getParent();
876            if (p instanceof JViewport) {
877                Container gp = p.getParent();
878                if (gp instanceof JScrollPane) {
879                    JScrollPane scrollPane = (JScrollPane) gp;
880                    // Make certain we are the viewPort's view and not, for
881                    // example, the rowHeaderView of the scrollPane -
882                    // an implementor of fixed columns might do this.
883                    JViewport viewport = scrollPane.getViewport();
884                    if (viewport == null || viewport.getView() != this) {
885                        return;
886                    }
887                    if (isColumnControlVisible()) {
888                        if (verticalScrollPolicy == 0) {
889                            verticalScrollPolicy = scrollPane
890                                    .getVerticalScrollBarPolicy();
891                        }
892                        scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
893                                getColumnControl());
894    
895                        scrollPane
896                                .setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
897                    }
898                    // else {
899                    // if (verticalScrollPolicy != 0) {
900                    // // Fix #155-swingx: reset only if we had force always before
901                    // // PENDING: JW - doesn't cope with dynamically changing the
902                    // policy
903                    // // shouldn't be much of a problem because doesn't happen too
904                    // often??
905                    // scrollPane.setVerticalScrollBarPolicy(verticalScrollPolicy);
906                    // }
907                    // try {
908                    // scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER,
909                    // null);
910                    // } catch (Exception ex) {
911                    // // Ignore spurious exception thrown by JScrollPane. This
912                    // // is a Swing bug!
913                    // }
914                    //
915                    // }
916                }
917            }
918        }
919    
920        // --------------------- actions
921        /**
922         * Take over ctrl-tab.
923         * 
924         */
925        private void initFocusBindings() {
926            setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
927                    new TreeSet<KeyStroke>());
928            setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
929                    new TreeSet<KeyStroke>());
930            getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
931                    KeyStroke.getKeyStroke("ctrl TAB"), FOCUS_NEXT_COMPONENT);
932            getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
933                    KeyStroke.getKeyStroke("shift ctrl TAB"),
934                    FOCUS_PREVIOUS_COMPONENT);
935            getActionMap().put(FOCUS_NEXT_COMPONENT,
936                    createFocusTransferAction(true));
937            getActionMap().put(FOCUS_PREVIOUS_COMPONENT,
938                    createFocusTransferAction(false));
939        }
940    
941        /**
942         * Creates and returns an action for forward/backward focus transfer,
943         * depending on the given flag.
944         * 
945         * @param forward a boolean indicating the direction of the required focus
946         *        transfer
947         * @return the action bound to focusTraversal.
948         */
949        private Action createFocusTransferAction(final boolean forward) {
950            BoundAction action = new BoundAction(null,
951                    forward ? FOCUS_NEXT_COMPONENT : FOCUS_PREVIOUS_COMPONENT);
952            action.registerCallback(this, forward ? "transferFocus"
953                    : "transferFocusBackward");
954            return action;
955        }
956    
957        /**
958         * A small class which dispatches actions.
959         * <p>
960         * TODO (?): Is there a way that we can make this static?
961         * <p>
962         * 
963         * PENDING JW: don't use UIAction ... we are in OO-land!
964         */
965        private class Actions extends UIAction {
966            Actions(String name) {
967                super(name);
968            }
969    
970            public void actionPerformed(ActionEvent evt) {
971                if ("print".equals(getName())) {
972                    try {
973                        print();
974                    } catch (PrinterException ex) {
975                        // REMIND(aim): should invoke pluggable application error
976                        // handler
977                        LOG.log(Level.WARNING, "", ex);
978                    }
979                } else if ("find".equals(getName())) {
980                    doFind();
981                }
982            }
983    
984        }
985    
986        /**
987         * Registers additional, per-instance <code>Action</code>s to the this
988         * table's ActionMap. Binds the search accelerator (as returned by the
989         * SearchFactory) to the find action.
990         * 
991         * 
992         */
993        private void initActionsAndBindings() {
994            // Register the actions that this class can handle.
995            ActionMap map = getActionMap();
996            map.put("print", new Actions("print"));
997            map.put("find", new Actions("find"));
998            // hack around core bug: cancel editing doesn't fire
999            // reported against SwingX as of #610-swingx
1000            map.put("cancel", createCancelAction());
1001            map.put(PACKALL_ACTION_COMMAND, createPackAllAction());
1002            map.put(PACKSELECTED_ACTION_COMMAND, createPackSelectedAction());
1003            map
1004                    .put(HORIZONTALSCROLL_ACTION_COMMAND,
1005                            createHorizontalScrollAction());
1006    
1007            KeyStroke findStroke = SearchFactory.getInstance()
1008                    .getSearchAccelerator();
1009            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
1010                    findStroke, "find");
1011        }
1012    
1013        /**
1014         * Creates and returns an Action which cancels an ongoing edit correctly.
1015         * Note: the correct thing to do is to call the editor's cancelEditing, the
1016         * wrong thing to do is to call table removeEditor (as core JTable does...).
1017         * So this is a quick hack around a core bug, reported against SwingX in
1018         * #610-swingx.
1019         * 
1020         * @return an Action which cancels an edit.
1021         */
1022        private Action createCancelAction() {
1023            Action action = new AbstractActionExt() {
1024    
1025                public void actionPerformed(ActionEvent e) {
1026                    if (!isEditing())
1027                        return;
1028                    getCellEditor().cancelCellEditing();
1029                }
1030    
1031                @Override
1032                public boolean isEnabled() {
1033                    return isEditing();
1034                }
1035    
1036            };
1037            return action;
1038        }
1039    
1040        /**
1041         * Creates and returns the default <code>Action</code> for toggling the
1042         * horizontal scrollBar.
1043         */
1044        private Action createHorizontalScrollAction() {
1045            BoundAction action = new BoundAction(null,
1046                    HORIZONTALSCROLL_ACTION_COMMAND);
1047            action.setStateAction();
1048            action.registerCallback(this, "setHorizontalScrollEnabled");
1049            action.setSelected(isHorizontalScrollEnabled());
1050            return action;
1051        }
1052    
1053        /**
1054         * Returns a potentially localized value from the UIManager. The given key
1055         * is prefixed by this table's <code>UIPREFIX</code> before doing the
1056         * lookup. The lookup respects this table's current <code>locale</code>
1057         * property. Returns the key, if no value is found.
1058         * 
1059         * @param key the bare key to look up in the UIManager.
1060         * @return the value mapped to UIPREFIX + key or key if no value is found.
1061         */
1062        protected String getUIString(String key) {
1063            return getUIString(key, getLocale());
1064        }
1065    
1066        /**
1067         * Returns a potentially localized value from the UIManager for the given
1068         * locale. The given key is prefixed by this table's <code>UIPREFIX</code>
1069         * before doing the lookup. Returns the key, if no value is found.
1070         * 
1071         * @param key the bare key to look up in the UIManager.
1072         * @param locale the locale use for lookup
1073         * @return the value mapped to UIPREFIX + key in the given locale, or key if
1074         *         no value is found.
1075         */
1076        protected String getUIString(String key, Locale locale) {
1077            String text = UIManagerExt.getString(UIPREFIX + key, locale);
1078            return text != null ? text : key;
1079        }
1080    
1081        /**
1082         * Creates and returns the default <code>Action</code> for packing the
1083         * selected column.
1084         */
1085        private Action createPackSelectedAction() {
1086            BoundAction action = new BoundAction(null, PACKSELECTED_ACTION_COMMAND);
1087            action.registerCallback(this, "packSelected");
1088            action.setEnabled(getSelectedColumnCount() > 0);
1089            return action;
1090        }
1091    
1092        /**
1093         * Creates and returns the default <b>Action </b> for packing all columns.
1094         */
1095        private Action createPackAllAction() {
1096            BoundAction action = new BoundAction(null, PACKALL_ACTION_COMMAND);
1097            action.registerCallback(this, "packAll");
1098            return action;
1099        }
1100    
1101        /**
1102         * {@inheritDoc}
1103         * <p>
1104         * Overridden to update locale-dependent properties.
1105         * 
1106         * @see #updateLocaleState(Locale)
1107         */
1108        @Override
1109        public void setLocale(Locale locale) {
1110            updateLocaleState(locale);
1111            super.setLocale(locale);
1112        }
1113    
1114        /**
1115         * Updates locale-dependent state to the given <code>Locale</code>.
1116         * 
1117         * Here: updates registered column actions' locale-dependent state.
1118         * <p>
1119         * 
1120         * PENDING: Try better to find all column actions including custom
1121         * additions? Or move to columnControl?
1122         * 
1123         * @param locale the Locale to use for value lookup
1124         * @see #setLocale(Locale)
1125         * @see #updateLocaleActionState(String, Locale)
1126         */
1127        protected void updateLocaleState(Locale locale) {
1128            updateLocaleActionState(HORIZONTALSCROLL_ACTION_COMMAND, locale);
1129            updateLocaleActionState(PACKALL_ACTION_COMMAND, locale);
1130            updateLocaleActionState(PACKSELECTED_ACTION_COMMAND, locale);
1131        }
1132    
1133        /**
1134         * Updates locale-dependent state of action registered with key in
1135         * <code>ActionMap</code>. Does nothing if no action with key is found.
1136         * <p>
1137         * 
1138         * Here: updates the <code>Action</code>'s name property.
1139         * 
1140         * @param key the string for lookup in this table's ActionMap
1141         * @see #updateLocaleState(Locale)
1142         */
1143        protected void updateLocaleActionState(String key, Locale locale) {
1144            Action action = getActionMap().get(key);
1145            if (action == null)
1146                return;
1147            action.putValue(Action.NAME, getUIString(key, locale));
1148        }
1149    
1150        // ------------------ bound action callback methods
1151    
1152        /**
1153         * Resizes all columns to fit their content.
1154         * <p>
1155         * 
1156         * By default this method is bound to the pack all columns
1157         * <code>Action</code> and registered in the table's <code>ActionMap</code>.
1158         * 
1159         */
1160        public void packAll() {
1161            packTable(-1);
1162        }
1163    
1164        /**
1165         * Resizes the lead column to fit its content.
1166         * <p>
1167         * 
1168         * By default this method is bound to the pack selected column
1169         * <code>Action</code> and registered in the table's <code>ActionMap</code>.
1170         */
1171        public void packSelected() {
1172            int selected = getColumnModel().getSelectionModel()
1173                    .getLeadSelectionIndex();
1174            if (selected >= 0) {
1175                packColumn(selected, -1);
1176            }
1177        }
1178    
1179        /**
1180         * {@inheritDoc}
1181         * <p>
1182         * 
1183         * Overridden to update the enabled state of the pack selected column
1184         * <code>Action</code>.
1185         */
1186        @Override
1187        public void columnSelectionChanged(ListSelectionEvent e) {
1188            super.columnSelectionChanged(e);
1189            if (e.getValueIsAdjusting())
1190                return;
1191            Action packSelected = getActionMap().get(PACKSELECTED_ACTION_COMMAND);
1192            if ((packSelected != null)) {
1193                packSelected.setEnabled(!((ListSelectionModel) e.getSource())
1194                        .isSelectionEmpty());
1195            }
1196        }
1197    
1198        // ----------------------- scrollable control
1199    
1200        /**
1201         * Sets the enablement of enhanced horizontal scrolling. If enabled, it
1202         * toggles an auto-resize mode which always fills the <code>JViewport</code>
1203         * horizontally and shows the horizontal scrollbar if necessary.
1204         * <p>
1205         * 
1206         * The default value is <code>false</code>.
1207         * <p>
1208         * 
1209         * Note: this is <strong>not</strong> a bound property, though it follows
1210         * bean naming conventions.
1211         * 
1212         * PENDING: Probably should be... If so, could be taken by a listening
1213         * Action as in the app-framework.
1214         * <p>
1215         * PENDING JW: the name is mis-leading?
1216         * 
1217         * @param enabled a boolean indicating whether enhanced auto-resize mode is
1218         *        enabled.
1219         * @see #isHorizontalScrollEnabled()
1220         */
1221        public void setHorizontalScrollEnabled(boolean enabled) {
1222            /*
1223             * PENDING JW: add a "real" mode? Problematic because there are several
1224             * places in core which check for #AUTO_RESIZE_OFF, can't use different
1225             * value without unwanted side-effects. The current solution with
1226             * tagging the #AUTO_RESIZE_OFF by a boolean flag #intelliMode is
1227             * brittle - need to be very careful to turn off again ... Another
1228             * problem is to keep the horizontalScrollEnabled toggling action in
1229             * synch with this property. Yet another problem is the change
1230             * notification: currently this is _not_ a bound property.
1231             */
1232            if (enabled == (isHorizontalScrollEnabled())) {
1233                return;
1234            }
1235            boolean old = isHorizontalScrollEnabled();
1236            if (enabled) {
1237                // remember the resizeOn mode if any
1238                if (getAutoResizeMode() != AUTO_RESIZE_OFF) {
1239                    oldAutoResizeMode = getAutoResizeMode();
1240                }
1241                setAutoResizeMode(AUTO_RESIZE_OFF);
1242                // setAutoResizeModel always disables the intelliMode
1243                // must set after calling and update the action again
1244                intelliMode = true;
1245                updateHorizontalAction();
1246            } else {
1247                setAutoResizeMode(oldAutoResizeMode);
1248            }
1249            firePropertyChange("horizontalScrollEnabled", old,
1250                    isHorizontalScrollEnabled());
1251        }
1252    
1253        /**
1254         * Returns the current setting for horizontal scrolling.
1255         * 
1256         * @return the enablement of enhanced horizontal scrolling.
1257         * @see #setHorizontalScrollEnabled(boolean)
1258         */
1259        public boolean isHorizontalScrollEnabled() {
1260            return intelliMode && getAutoResizeMode() == AUTO_RESIZE_OFF;
1261        }
1262    
1263        /**
1264         * {@inheritDoc}
1265         * <p>
1266         * 
1267         * Overridden for internal bookkeeping related to the enhanced auto-resize
1268         * behaviour.
1269         * <p>
1270         * 
1271         * Note: to enable/disable the enhanced auto-resize mode use exclusively
1272         * <code>setHorizontalScrollEnabled</code>, this method can't cope with it.
1273         * 
1274         * @see #setHorizontalScrollEnabled(boolean)
1275         * 
1276         */
1277        @Override
1278        public void setAutoResizeMode(int mode) {
1279            if (mode != AUTO_RESIZE_OFF) {
1280                oldAutoResizeMode = mode;
1281            }
1282            intelliMode = false;
1283            super.setAutoResizeMode(mode);
1284            updateHorizontalAction();
1285        }
1286    
1287        /**
1288         * Synchs selected state of horizontal scrolling <code>Action</code> to
1289         * enablement of enhanced auto-resize behaviour.
1290         */
1291        protected void updateHorizontalAction() {
1292            Action showHorizontal = getActionMap().get(
1293                    HORIZONTALSCROLL_ACTION_COMMAND);
1294            if (showHorizontal instanceof BoundAction) {
1295                ((BoundAction) showHorizontal)
1296                        .setSelected(isHorizontalScrollEnabled());
1297            }
1298        }
1299    
1300        /**
1301         *{@inheritDoc}
1302         * <p>
1303         * 
1304         * Overridden to support enhanced auto-resize behaviour enabled and
1305         * necessary.
1306         * 
1307         * @see #setHorizontalScrollEnabled(boolean)
1308         */
1309        @Override
1310        public boolean getScrollableTracksViewportWidth() {
1311            boolean shouldTrack = super.getScrollableTracksViewportWidth();
1312            if (isHorizontalScrollEnabled()) {
1313                return hasExcessWidth();
1314            }
1315            return shouldTrack;
1316        }
1317    
1318        /**
1319         * Layouts column width. The exact behaviour depends on the
1320         * <code>autoResizeMode</code> property.
1321         * <p>
1322         * Overridden to support enhanced auto-resize behaviour enabled and
1323         * necessary.
1324         * 
1325         * @see #setAutoResizeMode(int)
1326         * @see #setHorizontalScrollEnabled(boolean)
1327         */
1328        @Override
1329        public void doLayout() {
1330            int resizeMode = getAutoResizeMode();
1331            // fool super...
1332            if (isHorizontalScrollEnabled() && hasRealizedParent()
1333                    && hasExcessWidth()) {
1334                autoResizeMode = oldAutoResizeMode;
1335            }
1336            inLayout = true;
1337            super.doLayout();
1338            inLayout = false;
1339            autoResizeMode = resizeMode;
1340        }
1341    
1342        /**
1343         * 
1344         * @return boolean to indicate whether the table has a realized parent.
1345         */
1346        private boolean hasRealizedParent() {
1347            return (getWidth() > 0) && (getParent() != null)
1348                    && (getParent().getWidth() > 0);
1349        }
1350    
1351        /**
1352         * PRE: hasRealizedParent()
1353         * 
1354         * @return boolean to indicate whether the table has widths excessing
1355         *         parent's width
1356         */
1357        private boolean hasExcessWidth() {
1358            return getPreferredSize().width < getParent().getWidth();
1359        }
1360    
1361        /**
1362         * {@inheritDoc}
1363         * <p>
1364         * 
1365         * Overridden to support enhanced auto-resize behaviour enabled and
1366         * necessary.
1367         * 
1368         * @see #setHorizontalScrollEnabled(boolean)
1369         */
1370        @Override
1371        public void columnMarginChanged(ChangeEvent e) {
1372            if (isEditing()) {
1373                removeEditor();
1374            }
1375            TableColumn resizingColumn = getResizingColumn();
1376            // Need to do this here, before the parent's
1377            // layout manager calls getPreferredSize().
1378            if (resizingColumn != null && autoResizeMode == AUTO_RESIZE_OFF
1379                    && !inLayout) {
1380                resizingColumn.setPreferredWidth(resizingColumn.getWidth());
1381            }
1382            resizeAndRepaint();
1383        }
1384    
1385        /**
1386         * Returns the column which is interactively resized. The return value is
1387         * null if the header is null or has no resizing column.
1388         * 
1389         * @return the resizing column.
1390         */
1391        private TableColumn getResizingColumn() {
1392            return (tableHeader == null) ? null : tableHeader.getResizingColumn();
1393        }
1394    
1395        /**
1396         * {@inheritDoc} <p>
1397         * 
1398         * Overridden for documentation reasons only: same behaviour but different default value.
1399         * <p>
1400         * 
1401         * The default value is <code>true</code>.
1402         * <p>
1403         */
1404        @Override
1405        public void setFillsViewportHeight(boolean fillsViewportHeight) {
1406            if (fillsViewportHeight == getFillsViewportHeight())
1407                return;
1408            super.setFillsViewportHeight(fillsViewportHeight);
1409        }
1410    
1411        // ------------------------ override super because of filter-awareness
1412    
1413    
1414        /**
1415         * Overridden to account for row index mapping. This implementation respects
1416         * the cell's editability, that is it has no effect if
1417         * <code>!isCellEditable(row, column)</code>.
1418         * 
1419         * {@inheritDoc}
1420         * 
1421         * @see #isCellEditable(int, int)
1422         */
1423        @Override
1424        public void setValueAt(Object aValue, int row, int column) {
1425            if (!isCellEditable(row, column))
1426                return;
1427            super.setValueAt(aValue, row, column);
1428        }
1429    
1430        /**
1431         * Returns true if the cell at <code>row</code> and <code>column</code> is
1432         * editable. Otherwise, invoking <code>setValueAt</code> on the cell will
1433         * have no effect.
1434         * <p>
1435         * Overridden to account for row index mapping and to support a layered
1436         * editability control:
1437         * <ul>
1438         * <li>per-table: <code>JXTable.isEditable()</code>
1439         * <li>per-column: <code>TableColumnExt.isEditable()</code>
1440         * <li>per-cell: controlled by the model
1441         * <code>TableModel.isCellEditable()</code>
1442         * </ul>
1443         * The view cell is considered editable only if all three layers are
1444         * enabled.
1445         * 
1446         * @param row the row index in view coordinates
1447         * @param column the column index in view coordinates
1448         * @return true if the cell is editable
1449         * 
1450         * @see #setValueAt(Object, int, int)
1451         * @see #isEditable()
1452         * @see TableColumnExt#isEditable
1453         * @see TableModel#isCellEditable
1454         */
1455        @Override
1456        public boolean isCellEditable(int row, int column) {
1457            if (!isEditable())
1458                return false;
1459            boolean editable = super.isCellEditable(row, column);
1460    //                convertRowIndexToModel(row), convertColumnIndexToModel(column));
1461            if (editable) {
1462                TableColumnExt tableColumn = getColumnExt(column);
1463                if (tableColumn != null) {
1464                    editable = tableColumn.isEditable();
1465                }
1466            }
1467            return editable;
1468        }
1469    
1470    
1471    
1472        /**
1473         * {@inheritDoc}
1474         * <p>
1475         * 
1476         * Overridden for documentation clarification. The property has the same
1477         * meaning as super, that is if true to re-create all table columns on
1478         * either setting a new TableModel or receiving a structureChanged from the
1479         * existing. The most obvious visual effect is that custom column properties
1480         * appear to be "lost".
1481         * <p>
1482         * 
1483         * JXTable does support additonal custom configuration (via a custom
1484         * ColumnFactory) which can (and incorrectly was) called independently from
1485         * the creation. Setting this property to false guarantees that no column
1486         * configuration is applied.
1487         * 
1488         * @see #tableChanged(TableModelEvent)
1489         * @see org.jdesktop.swingx.table.ColumnFactory
1490         * 
1491         */
1492        @Override
1493        public boolean getAutoCreateColumnsFromModel() {
1494            return super.getAutoCreateColumnsFromModel();
1495        }
1496    
1497        /**
1498         * {@inheritDoc}
1499         * <p>
1500         * 
1501         * Overridden to re-calculate intialize column width and preferred
1502         * scrollable size after a structureChanged if autocreateColumnsFromModel is
1503         * true.
1504         */
1505        @Override
1506        public void tableChanged(TableModelEvent e) {
1507            super.tableChanged(e);
1508            if (isStructureChanged(e) && getAutoCreateColumnsFromModel()) {
1509                initializeColumnWidths();
1510                resetCalculatedScrollableSize(true);
1511            }
1512            // JW: handled by addColumn
1513    //        if ((isStructureChanged(e))) {
1514    //            configureSorterProperties();
1515    //        }
1516        }
1517    
1518        /**
1519         * {@inheritDoc} <p>
1520         * 
1521         * Overridden to prevent super from creating RowSorter.
1522         */
1523        @Override
1524        public void setModel(TableModel dataModel) {
1525            boolean old = getAutoCreateRowSorter();
1526            try {
1527                this.autoCreateRowSorter = false;
1528                this.ignoreAddColumn = true;
1529                super.setModel(dataModel);
1530            } finally {
1531                this.autoCreateRowSorter = old;
1532                this.ignoreAddColumn = false;
1533            }
1534            if (getAutoCreateRowSorter()) {
1535                setRowSorter(createDefaultRowSorter());
1536            }
1537            
1538        }
1539    
1540        /**
1541         * {@inheritDoc} <p>
1542         * 
1543         * Overridden to synch sorter state from columns.
1544         */
1545        @Override
1546        public void setColumnModel(TableColumnModel columnModel) {
1547            super.setColumnModel(columnModel);
1548            configureSorterProperties();
1549        }
1550    
1551        /**
1552         * {@inheritDoc} <p>
1553         * 
1554         * Overridden to 
1555         * <ul>
1556         * <li> fix core bug: replaces sorter even if flag doesn't change.
1557         * <li> use xflag (need because super's RowSorter creation is hard-coded.
1558         */
1559        @Override
1560        public void setAutoCreateRowSorter(boolean autoCreateRowSorter) {
1561            if (getAutoCreateRowSorter() == autoCreateRowSorter) return;
1562            boolean oldValue = getAutoCreateRowSorter();
1563            this.autoCreateRowSorter = autoCreateRowSorter;
1564            if (autoCreateRowSorter) {
1565                setRowSorter(createDefaultRowSorter());
1566            }
1567            firePropertyChange("autoCreateRowSorter", oldValue,
1568                               getAutoCreateRowSorter());
1569        }
1570    
1571        /**
1572         * {@inheritDoc} <p>
1573         * 
1574         * Overridden to return xflag
1575         */
1576        @Override
1577        public boolean getAutoCreateRowSorter() {
1578            return autoCreateRowSorter;
1579        }
1580    
1581        /**
1582         * {@inheritDoc} <p>
1583         * 
1584         * Overridden propagate sort-related properties to the sorter after calling super,
1585         * if the given RowSorter is of type SortController. Does nothing additional otherwise.
1586         */
1587        @Override
1588        public void setRowSorter(RowSorter<? extends TableModel> sorter) {
1589            super.setRowSorter(sorter);
1590            configureSorterProperties();
1591        }
1592    
1593        /**
1594         * Propagates sort-related properties from table/columns to the sorter if it
1595         * is of type SortController, does nothing otherwise.
1596         * 
1597         */
1598        protected void configureSorterProperties() {
1599            // need to hack: if a structureChange is the result of a setModel
1600            // the rowsorter is not yet updated
1601            if (ignoreAddColumn || (getSortController() == null))  return;
1602            // configure from table properties
1603            getSortController().setSortable(sortable);
1604            getSortController().setSortsOnUpdates(sortsOnUpdates);
1605            // configure from column properties
1606            List<TableColumn> columns = getColumns(true);
1607            for (TableColumn tableColumn : columns) {
1608                int modelIndex = tableColumn.getModelIndex();
1609                getSortController().setSortable(modelIndex, 
1610                        tableColumn instanceof TableColumnExt ? 
1611                                ((TableColumnExt) tableColumn).isSortable() : true);
1612                getSortController().setComparator(modelIndex, 
1613                        tableColumn instanceof TableColumnExt ? 
1614                                ((TableColumnExt) tableColumn).getComparator() : null);
1615            }
1616        }
1617    
1618        /**
1619         * Creates and returns the default RowSorter. Note that this is already
1620         * configured to the current TableModel - no api in the base class to set
1621         * the model? <p>
1622         * 
1623         * PENDING JW: review method signature - better expose the need for the
1624         * model by adding a parameter? 
1625         * 
1626         * @return the default RowSorter.
1627         */
1628        protected RowSorter<? extends TableModel> createDefaultRowSorter() {
1629    //        return new TableRowSorter<TableModel>(getModel());
1630            return new TableSortController<TableModel>(getModel());
1631        }
1632    
1633    
1634    
1635        /**
1636         * Convenience method to detect dataChanged table event type.
1637         * 
1638         * @param e the event to examine.
1639         * @return true if the event is of type dataChanged, false else.
1640         */
1641        protected boolean isDataChanged(TableModelEvent e) {
1642            if (e == null)
1643                return false;
1644            return e.getType() == TableModelEvent.UPDATE && e.getFirstRow() == 0
1645                    && e.getLastRow() == Integer.MAX_VALUE;
1646        }
1647    
1648        /**
1649         * Convenience method to detect update table event type.
1650         * 
1651         * @param e the event to examine.
1652         * @return true if the event is of type update and not dataChanged, false
1653         *         else.
1654         */
1655        protected boolean isUpdate(TableModelEvent e) {
1656            if (isStructureChanged(e))
1657                return false;
1658            return e.getType() == TableModelEvent.UPDATE
1659                    && e.getLastRow() < Integer.MAX_VALUE;
1660        }
1661    
1662        /**
1663         * Convenience method to detect a structureChanged table event type.
1664         * 
1665         * @param e the event to examine.
1666         * @return true if the event is of type structureChanged or null, false
1667         *         else.
1668         */
1669        protected boolean isStructureChanged(TableModelEvent e) {
1670            return e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW;
1671        }
1672    
1673        /**
1674         * Trying to hack around #172-swingx: lead/anchor of row selection model is
1675         * not adjusted to valid (not even model indices!) in the usual
1676         * clearSelection after dataChanged/structureChanged.
1677         * 
1678         * Note: as of jdk1.5U6 the anchor/lead of the view selectionModel is
1679         * unconditionally set to -1 after data/structureChanged.
1680         * 
1681         * @param e
1682         */
1683        private void hackLeadAnchor(TableModelEvent e) {
1684            int lead = getSelectionModel().getLeadSelectionIndex();
1685            int anchor = getSelectionModel().getAnchorSelectionIndex();
1686            int lastRow = getModel().getRowCount() - 1;
1687            if ((lead > lastRow) || (anchor > lastRow)) {
1688                lead = lastRow;
1689                getSelectionModel().setAnchorSelectionIndex(lead);
1690                getSelectionModel().setLeadSelectionIndex(lead);
1691            }
1692        }
1693    
1694        // -------------------------------- sorting
1695    
1696        
1697        /**
1698         * Sets &quot;sortable&quot; property indicating whether or not this table
1699         * supports sortable columns. If <code>sortable</code> is <code>true</code>
1700         * then sorting will be enabled on all columns whose <code>sortable</code>
1701         * property is <code>true</code>. If <code>sortable</code> is
1702         * <code>false</code> then sorting will be disabled for all columns,
1703         * regardless of each column's individual <code>sorting</code> property. The
1704         * default is <code>true</code>. <p>
1705         * 
1706         * <b>Note</b>: as of post-1.0 this property is propagated to the SortController. 
1707         * Whether or not a change triggers a re-sort is up to either the concrete controller 
1708         * implementation (the default doesn't) or client code. This behaviour is
1709         * different from old SwingX style sorting.
1710         * 
1711         * @see TableColumnExt#isSortable()
1712         * @param sortable boolean indicating whether or not this table supports
1713         *        sortable columns
1714         */
1715        public void setSortable(boolean sortable) {
1716            boolean old = isSortable();
1717            this.sortable = sortable;
1718            if (getSortController() != null) {
1719                getSortController().setSortable(sortable);
1720            }
1721            firePropertyChange("sortable", old, isSortable());
1722        }
1723    
1724        /**
1725         * Returns the table's sortable property.<p>
1726         * 
1727         * Note: as of post-1.0 this property is propagated to the SortController. 
1728         * 
1729         * @return true if the table is sortable.
1730         */
1731        public boolean isSortable() {
1732            return getSortController() != null ? getSortController().isSortable() : sortable;
1733        }
1734    
1735        /**
1736         * If true, specifies that a sort should happen when the underlying
1737         * model is updated (<code>rowsUpdated</code> is invoked).  For
1738         * example, if this is true and the user edits an entry the
1739         * location of that item in the view may change.  The default is
1740         * true.
1741         *
1742         * @param sortsOnUpdates whether or not to sort on update events
1743         */
1744        public void setSortsOnUpdates(boolean sortsOnUpdates) {
1745            boolean old = getSortsOnUpdates();
1746            this.sortsOnUpdates = sortsOnUpdates;
1747            if (getSortController() != null) {
1748                getSortController().setSortsOnUpdates(sortsOnUpdates);
1749            }
1750            firePropertyChange("sortsOnUpdates", old, getSortsOnUpdates());
1751        }
1752        
1753        /**
1754         * Returns true if  a sort should happen when the underlying
1755         * model is updated; otherwise, returns false.
1756         *
1757         * @return whether or not to sort when the model is updated
1758         */
1759        public boolean getSortsOnUpdates() {
1760            return getSortController() != null ? getSortController().getSortsOnUpdates() : sortsOnUpdates;
1761        }
1762        /**
1763         * Resets sorting of all columns.
1764         * Delegates to the SortController if available, or does nothing if not.<p>
1765         * 
1766         * PENDING JW: method name - consistent in SortController and here.
1767         * 
1768         */
1769        public void resetSortOrder() {
1770            if (getSortController() == null)
1771                return;
1772            getSortController().resetSortOrders();
1773            // JW PENDING: think about notification instead of manual repaint.
1774            if (getTableHeader() != null) {
1775                getTableHeader().repaint();
1776            }
1777        }
1778    
1779        /**
1780         * 
1781         * Toggles the sort order of the column at columnIndex.
1782         * Delegates to the SortController if available, or does nothing if not.<p>
1783         * 
1784         * <p>
1785         * The exact behaviour is defined by the SortController's toggleSortOrder
1786         * implementation. Typically a unsorted column is sorted in ascending order,
1787         * a sorted column's order is reversed.
1788         * <p>
1789         * 
1790         * PRE: 0 <= columnIndex < getColumnCount()
1791         * 
1792         * @param columnIndex the columnIndex in view coordinates.
1793         * 
1794         */
1795        public void toggleSortOrder(int columnIndex) {
1796            if (getSortController() == null)
1797                return;
1798            getSortController().toggleSortOrder(
1799                    convertColumnIndexToModel(columnIndex));
1800        }
1801    
1802        /**
1803         * Sorts the table by the given column using SortOrder.
1804         * Delegates to the SortController if available, or does nothing if not.<p>
1805         * 
1806         * PRE: 0 <= columnIndex < getColumnCount()
1807         * <p>
1808         * 
1809         * 
1810         * @param columnIndex the column index in view coordinates.
1811         * @param sortOrder the sort order to use.
1812         * 
1813         */
1814        public void setSortOrder(int columnIndex, SortOrder sortOrder) {
1815            if (getSortController() == null)
1816                return;
1817            getSortController().setSortOrder(
1818                    convertColumnIndexToModel(columnIndex), sortOrder);
1819    
1820        }
1821    
1822        /**
1823         * Returns the SortOrder of the given column.
1824         * Delegates to the SortController if available, or returns SortOrder.UNSORTED if not.<p>
1825         * 
1826         * @param columnIndex the column index in view coordinates.
1827         * @return the interactive sorter's SortOrder if matches the column or
1828         *         SortOrder.UNSORTED
1829         */
1830        public SortOrder getSortOrder(int columnIndex) {
1831            if (getSortController() == null)
1832                return SortOrder.UNSORTED;
1833            return getSortController().getSortOrder(convertColumnIndexToModel(columnIndex));
1834        }
1835    
1836        /**
1837         * 
1838         * Toggles the sort order of the column with identifier. 
1839         * Delegates to the SortController if available, or does nothing if not.<p>
1840         * 
1841         * The exact behaviour of a toggle is defined by the SortController's toggleSortOrder
1842         * implementation. Typically a unsorted column is sorted in ascending order,
1843         * a sorted column's order is reversed.
1844         * <p>
1845         * 
1846         * PENDING: JW - define the behaviour if the identifier is not found. This
1847         * can happen if either there's no column at all with the identifier or if
1848         * there's no column of type TableColumnExt. Currently does nothing, that is
1849         * does not change sort state.
1850         * 
1851         * @param identifier the column identifier.
1852         * 
1853         */
1854        public void toggleSortOrder(Object identifier) {
1855            if (getSortController() == null)
1856                return;
1857            TableColumn columnExt = null;
1858            try {
1859                columnExt = getColumn(identifier);
1860            } catch (IllegalArgumentException e) {
1861                // hacking around weird getColumn(Object) behaviour -
1862                // PENDING JW: revisit and override
1863                columnExt = getColumnExt(identifier);
1864            }
1865            if (columnExt == null)
1866                return;
1867            getSortController().toggleSortOrder(columnExt.getModelIndex());
1868        }
1869    
1870        /**
1871         * Sorts the table by the given column using the SortOrder.
1872         * Delegates to the SortController, if available or does nothing if not.
1873         * <p>
1874         * 
1875         * PENDING: JW - define the behaviour if the identifier is not found. This
1876         * can happen if either there's no column at all with the identifier or if
1877         * there's no column of type TableColumnExt. Currently does nothing, that is
1878         * does not change sort state.
1879         * 
1880         * @param identifier the column's identifier.
1881         * @param sortOrder the sort order to use. If null or SortOrder.UNSORTED,
1882         *        this method has the same effect as resetSortOrder();
1883         * 
1884         */
1885        public void setSortOrder(Object identifier, SortOrder sortOrder) {
1886            if (getSortController() == null)
1887                return;
1888            TableColumn columnExt = null;
1889            try {
1890                columnExt = getColumn(identifier);
1891            } catch (IllegalArgumentException e) {
1892                // hacking around weird getColumn(Object) behaviour -
1893                // PENDING JW: revisit and override
1894                columnExt = getColumnExt(identifier);
1895            }
1896            if (columnExt == null)
1897                return;
1898            getSortController().setSortOrder(columnExt.getModelIndex(), sortOrder);
1899        }
1900    
1901        /**
1902         * Returns the SortOrder of the given column.
1903         * Delegates to the SortController if available, or returns SortOrder.UNSORTED if not.<p>
1904         * 
1905         * PENDING: JW - define the behaviour if the identifier is not found. This
1906         * can happen if either there's no column at all with the identifier or if
1907         * there's no column of type TableColumnExt. Currently returns
1908         * SortOrder.UNSORTED.
1909         * 
1910         * @param identifier the column's identifier.
1911         * @return the interactive sorter's SortOrder if matches the column or
1912         *         SortOrder.UNSORTED
1913         */
1914        public SortOrder getSortOrder(Object identifier) {
1915            if (getSortController() == null)
1916                return SortOrder.UNSORTED;
1917            TableColumn columnExt = null; 
1918            try {
1919                columnExt = getColumn(identifier);
1920            } catch (IllegalArgumentException e) {
1921                // hacking around weird getColumn(Object) behaviour - 
1922                // PENDING JW: revisit and override
1923                columnExt = getColumnExt(identifier);
1924            }
1925            if (columnExt == null)
1926                return SortOrder.UNSORTED;
1927            int modelIndex = columnExt.getModelIndex();
1928            return getSortController().getSortOrder(modelIndex);
1929        }
1930    
1931        /**
1932         * Decides if the column at columnIndex can be interactively sorted.
1933         * <p>
1934         * 
1935         * Here: delegates to the SortController, if available. If not, returns 
1936         * true if both this table and the column sortable property is
1937         * enabled, false otherwise.<p>
1938         * 
1939         * Note: as of post-1.0 this method is no longer used internally, as the 
1940         * responsibility to respect per-column/per-table sortability is moved 
1941         * to the SortController. 
1942         * 
1943         * 
1944         * @param columnIndex column in view coordinates
1945         * @return boolean indicating whether or not the column is sortable in this
1946         *         table.
1947         */
1948        protected boolean isSortable(int columnIndex) {
1949            if (getSortController() != null) {
1950                return getSortController().isSortable(convertColumnIndexToModel(columnIndex));
1951            }
1952            //PENDING JW: the fall-back implementation (== no sortController) is rather meaningless. 
1953            // Remove?
1954            boolean sortable = isSortable();
1955            TableColumnExt tableColumnExt = getColumnExt(columnIndex);
1956            if (tableColumnExt != null) {
1957                sortable = sortable && tableColumnExt.isSortable();
1958            }
1959            return sortable;
1960        }
1961    
1962    
1963        /**
1964         * Decides if the column with identifier can be interactively sorted.
1965         * <p>
1966         * Here: delegates to the SortController, if available. If not, returns 
1967         * true if both this table and the column sortable property is
1968         * enabled, false otherwise.<p>
1969         * 
1970         * Note: as of post-1.0 this method is no longer used internally, as the 
1971         * responsibility to respect per-column/per-table sortability is moved 
1972         * to the SortController. 
1973         * 
1974         * 
1975         * @param identifier the column's identifier
1976         * @return boolean indicating whether or not the column is sortable in this
1977         *         table.
1978         */
1979        protected boolean isSortable(Object identifier) {
1980            if (getSortController() != null) {
1981                TableColumn columnExt = null; 
1982                try {
1983                    columnExt = getColumn(identifier);
1984                } catch (IllegalArgumentException e) {
1985                    // hacking around weird getColumn(Object) behaviour - 
1986                    // PENDING JW: revisit and override
1987                    columnExt = getColumnExt(identifier);
1988                }
1989                if (columnExt != null) {
1990                    return getSortController().isSortable(columnExt.getModelIndex());
1991                }
1992                return getSortController().isSortable();
1993            }
1994            //PENDING JW: the fall-back implementation (== no sortController) is rather meaningless. 
1995            // Remove?
1996            boolean sortable = isSortable();
1997            TableColumnExt tableColumnExt = getColumnExt(identifier);
1998            if (tableColumnExt != null) {
1999                sortable = sortable && tableColumnExt.isSortable();
2000            }
2001            return sortable;
2002        }
2003    
2004        /**
2005         * Returns the currently active SortController. May be null, if the current RowSorter
2006         * is not an instance of SortController.
2007         * 
2008         * @return the currently active <code>SortController</code> may be null
2009         */
2010        protected SortController getSortController() {
2011            if (getRowSorter() instanceof SortController) {
2012                return (SortController) getRowSorter();
2013            }
2014            return null;
2015        }
2016    
2017        /**
2018         * Returns the primary sort column, or null if nothing sorted or no sortKey
2019         *   corresponds to a TableColumn currently contained in the TableColumnModel.
2020         * 
2021         * @return the currently interactively sorted TableColumn or null if there
2022         *         is not sorter active or if the sorted column index does not
2023         *         correspond to any column in the TableColumnModel.
2024         */
2025        public TableColumn getSortedColumn() {
2026            // bloody hack: get primary SortKey and
2027            // check if there's a column with it available
2028    //        SortController controller = getSortController();
2029            RowSorter<?> controller = getRowSorter();
2030            if (controller != null) {
2031                // PENDING JW: must use RowSorter?
2032                SortKey sortKey = SortUtils.getFirstSortingKey(controller
2033                        .getSortKeys());
2034                if (sortKey != null) {
2035                    int sorterColumn = sortKey.getColumn();
2036                    List<TableColumn> columns = getColumns(true);
2037                    for (Iterator<TableColumn> iter = columns.iterator(); iter
2038                            .hasNext();) {
2039                        TableColumn column = iter.next();
2040                        if (column.getModelIndex() == sorterColumn) {
2041                            return column;
2042                        }
2043                    }
2044    
2045                }
2046            }
2047            return null;
2048        }
2049    
2050        /**
2051         * {@inheritDoc} <p>
2052         * 
2053         * Overridden to propagate sort-related column properties to the SortController.<p>
2054         *  
2055         *  PENDING JW: check correct update on visibility change!
2056         * 
2057         */
2058        @Override
2059        public void columnAdded(TableColumnModelEvent e) {
2060            super.columnAdded(e);
2061            if (ignoreAddColumn) return;
2062            TableColumn column = getColumn(e.getToIndex());
2063            updateSortableAfterColumnChanged(column, column instanceof TableColumnExt ? ((TableColumnExt) column).isSortable() : true);
2064            updateComparatorAfterColumnChanged(column, column instanceof TableColumnExt ? ((TableColumnExt) column).getComparator() : null);
2065        }
2066    
2067    
2068        // ----------------- enhanced column support: delegation to TableColumnModel
2069        /**
2070         * Returns the <code>TableColumn</code> at view position
2071         * <code>columnIndex</code>. The return value is not <code>null</code>.
2072         * 
2073         * <p>
2074         * NOTE: This delegate method is added to protect developer's from
2075         * unexpected exceptions in jdk1.5+. Super does not expose the
2076         * <code>TableColumn</code> access by index which may lead to unexpected
2077         * <code>IllegalArgumentException</code>: If client code assumes the
2078         * delegate method is available, autoboxing will convert the given int to an
2079         * Integer which will call the getColumn(Object) method.
2080         * 
2081         * 
2082         * @param viewColumnIndex index of the column with the object in question
2083         * 
2084         * @return the <code>TableColumn</code> object that matches the column index
2085         * @throws ArrayIndexOutOfBoundsException if viewColumnIndex out of allowed
2086         *         range.
2087         * 
2088         * @see #getColumn(Object)
2089         * @see #getColumnExt(int)
2090         * @see TableColumnModel#getColumn(int)
2091         */
2092        public TableColumn getColumn(int viewColumnIndex) {
2093            return getColumnModel().getColumn(viewColumnIndex);
2094        }
2095    
2096        /**
2097         * Returns a <code>List</code> of visible <code>TableColumn</code>s.
2098         * 
2099         * @return a <code>List</code> of visible columns.
2100         * @see #getColumns(boolean)
2101         */
2102        public List<TableColumn> getColumns() {
2103            return Collections.list(getColumnModel().getColumns());
2104        }
2105    
2106        /**
2107         * Returns the margin between columns.
2108         * <p>
2109         * 
2110         * Convenience to expose column model properties through
2111         * <code>JXTable</code> api.
2112         * 
2113         * @return the margin between columns
2114         * 
2115         * @see #setColumnMargin(int)
2116         * @see TableColumnModel#getColumnMargin()
2117         */
2118        public int getColumnMargin() {
2119            return getColumnModel().getColumnMargin();
2120        }
2121    
2122        /**
2123         * Sets the margin between columns.
2124         * 
2125         * Convenience to expose column model properties through
2126         * <code>JXTable</code> api.
2127         * 
2128         * @param value margin between columns; must be greater than or equal to
2129         *        zero.
2130         * @see #getColumnMargin()
2131         * @see TableColumnModel#setColumnMargin(int)
2132         */
2133        public void setColumnMargin(int value) {
2134            getColumnModel().setColumnMargin(value);
2135        }
2136    
2137        // ----------------- enhanced column support: delegation to
2138        // TableColumnModelExt
2139    
2140        /**
2141         * Returns the number of contained columns. The count includes or excludes
2142         * invisible columns, depending on whether the <code>includeHidden</code> is
2143         * true or false, respectively. If false, this method returns the same count
2144         * as <code>getColumnCount()</code>. If the columnModel is not of type
2145         * <code>TableColumnModelExt</code>, the parameter value has no effect.
2146         * 
2147         * @param includeHidden a boolean to indicate whether invisible columns
2148         *        should be included
2149         * @return the number of contained columns, including or excluding the
2150         *         invisible as specified.
2151         * @see #getColumnCount()
2152         * @see TableColumnModelExt#getColumnCount(boolean)
2153         */
2154        public int getColumnCount(boolean includeHidden) {
2155            if (getColumnModel() instanceof TableColumnModelExt) {
2156                return ((TableColumnModelExt) getColumnModel())
2157                        .getColumnCount(includeHidden);
2158            }
2159            return getColumnCount();
2160        }
2161    
2162        /**
2163         * Returns a <code>List</code> of contained <code>TableColumn</code>s.
2164         * Includes or excludes invisible columns, depending on whether the
2165         * <code>includeHidden</code> is true or false, respectively. If false, an
2166         * <code>Iterator</code> over the List is equivalent to the
2167         * <code>Enumeration</code> returned by <code>getColumns()</code>. If the
2168         * columnModel is not of type <code>TableColumnModelExt</code>, the
2169         * parameter value has no effect.
2170         * <p>
2171         * 
2172         * NOTE: the order of columns in the List depends on whether or not the
2173         * invisible columns are included, in the former case it's the insertion
2174         * order in the latter it's the current order of the visible columns.
2175         * 
2176         * @param includeHidden a boolean to indicate whether invisible columns
2177         *        should be included
2178         * @return a <code>List</code> of contained columns.
2179         * 
2180         * @see #getColumns()
2181         * @see TableColumnModelExt#getColumns(boolean)
2182         */
2183        public List<TableColumn> getColumns(boolean includeHidden) {
2184            if (getColumnModel() instanceof TableColumnModelExt) {
2185                return ((TableColumnModelExt) getColumnModel())
2186                        .getColumns(includeHidden);
2187            }
2188            return getColumns();
2189        }
2190    
2191        /**
2192         * Returns the first <code>TableColumnExt</code> with the given
2193         * <code>identifier</code>. The return value is null if there is no
2194         * contained column with <b>identifier</b> or if the column with
2195         * <code>identifier</code> is not of type <code>TableColumnExt</code>. The
2196         * returned column may be visible or hidden.
2197         * 
2198         * @param identifier the object used as column identifier
2199         * @return first <code>TableColumnExt</code> with the given identifier or
2200         *         null if none is found
2201         * 
2202         * @see #getColumnExt(int)
2203         * @see #getColumn(Object)
2204         * @see TableColumnModelExt#getColumnExt(Object)
2205         */
2206        public TableColumnExt getColumnExt(Object identifier) {
2207            if (getColumnModel() instanceof TableColumnModelExt) {
2208                return ((TableColumnModelExt) getColumnModel())
2209                        .getColumnExt(identifier);
2210            } else {
2211                // PENDING: not tested!
2212                try {
2213                    TableColumn column = getColumn(identifier);
2214                    if (column instanceof TableColumnExt) {
2215                        return (TableColumnExt) column;
2216                    }
2217                } catch (Exception e) {
2218                    // TODO: handle exception
2219                }
2220            }
2221            return null;
2222        }
2223    
2224        /**
2225         * Returns the <code>TableColumnExt</code> at view position
2226         * <code>columnIndex</code>. The return value is null, if the column at
2227         * position <code>columnIndex</code> is not of type
2228         * <code>TableColumnExt</code>. The returned column is visible.
2229         * 
2230         * @param viewColumnIndex the index of the column desired
2231         * @return the <code>TableColumnExt</code> object that matches the column
2232         *         index
2233         * @throws ArrayIndexOutOfBoundsException if columnIndex out of allowed
2234         *         range, that is if
2235         *         <code> (columnIndex < 0) || (columnIndex >= getColumnCount())</code>
2236         *         .
2237         * 
2238         * @see #getColumnExt(Object)
2239         * @see #getColumn(int)
2240         * @see TableColumnModelExt#getColumnExt(int)
2241         */
2242        public TableColumnExt getColumnExt(int viewColumnIndex) {
2243            TableColumn column = getColumn(viewColumnIndex);
2244            if (column instanceof TableColumnExt) {
2245                return (TableColumnExt) column;
2246            }
2247            return null;
2248        }
2249    
2250        // ---------------------- enhanced TableColumn/Model support: convenience
2251    
2252        /**
2253         * Reorders the columns in the sequence given array. Logical names that do
2254         * not correspond to any column in the model will be ignored. Columns with
2255         * logical names not contained are added at the end.
2256         * 
2257         * PENDING JW - do we want this? It's used by JNTable.
2258         * 
2259         * @param identifiers array of logical column names
2260         * 
2261         * @see #getColumns(boolean)
2262         */
2263        public void setColumnSequence(Object[] identifiers) {
2264            /*
2265             * JW: not properly tested (not in all in fact) ...
2266             */
2267            List<TableColumn> columns = getColumns(true);
2268            Map<Object, TableColumn> map = new HashMap<Object, TableColumn>();
2269            for (Iterator<TableColumn> iter = columns.iterator(); iter.hasNext();) {
2270                // PENDING: handle duplicate identifiers ...
2271                TableColumn column = iter.next();
2272                map.put(column.getIdentifier(), column);
2273                getColumnModel().removeColumn(column);
2274            }
2275            for (int i = 0; i < identifiers.length; i++) {
2276                TableColumn column = map.get(identifiers[i]);
2277                if (column != null) {
2278                    getColumnModel().addColumn(column);
2279                    columns.remove(column);
2280                }
2281            }
2282            for (Iterator<TableColumn> iter = columns.iterator(); iter.hasNext();) {
2283                TableColumn column = (TableColumn) iter.next();
2284                getColumnModel().addColumn(column);
2285            }
2286        }
2287    
2288        // --------------- implement TableColumnModelExtListener
2289    
2290        /**
2291         * {@inheritDoc}
2292         * 
2293         * Listens to column property changes.
2294         * 
2295         */
2296        public void columnPropertyChange(PropertyChangeEvent event) {
2297            if (event.getPropertyName().equals("editable")) {
2298                updateEditingAfterColumnChanged((TableColumn) event.getSource(),
2299                        (Boolean) event.getNewValue());
2300            } else if (event.getPropertyName().equals("sortable")) {
2301                updateSortableAfterColumnChanged((TableColumn) event.getSource(),
2302                        (Boolean) event.getNewValue());
2303            } else if (event.getPropertyName().equals("comparator")) {
2304                updateComparatorAfterColumnChanged((TableColumn) event.getSource(),
2305                        (Comparator<?>) event.getNewValue());
2306            } else if (event.getPropertyName().startsWith("highlighter")) {
2307                if (event.getSource() instanceof TableColumnExt
2308                        && getRowCount() > 0) {
2309                    TableColumnExt column = (TableColumnExt) event.getSource();
2310    
2311                    Rectangle r = getCellRect(0, convertColumnIndexToView(column
2312                            .getModelIndex()), true);
2313                    r.height = getHeight();
2314                    repaint(r);
2315                } else {
2316                    repaint();
2317                }
2318            }
2319    
2320        }
2321    
2322        /**
2323         * Adjusts editing state after column's property change. Cancels ongoing
2324         * editing if the sending column is the editingColumn and the column's
2325         * editable changed to <code>false</code>, otherwise does nothing.
2326         * 
2327         * @param column the <code>TableColumn</code> which sent the change
2328         *        notifcation
2329         * @param editable the new value of the column's editable property
2330         */
2331        private void updateEditingAfterColumnChanged(TableColumn column,
2332                boolean editable) {
2333            if (!isEditing())
2334                return;
2335            int viewIndex = convertColumnIndexToView(column.getModelIndex());
2336            if ((viewIndex < 0) || (viewIndex != getEditingColumn()))
2337                return;
2338            getCellEditor().cancelCellEditing();
2339        }
2340    
2341        /**
2342         * Synch's the SortController column sortable property to the new value, if 
2343         * available. Does nothing if there is no SortController. This method is
2344         * called on sortable property change notification from the ext column model. <p>
2345         * 
2346         * <b>Note</b>: as of post-1.0 a column's property is propagated to the SortController. 
2347         * Whether or not a change triggers a re-sort is up to either the concrete controller 
2348         * implementation (the default doesn't) or client code. This behaviour is
2349         * different from old SwingX style sorting.
2350         * 
2351         * @param column the <code>TableColumn</code> which sent the change
2352         *        notifcation
2353         * @param sortable the new value of the column's sortable property
2354         */
2355        private void updateSortableAfterColumnChanged(TableColumn column,
2356                boolean sortable) {
2357            if (getSortController() == null) return;
2358            getSortController().setSortable(column.getModelIndex(), sortable);
2359        }
2360        
2361        /**
2362         * Synch's the SortController column comparator property to the new value, if 
2363         * available. Does nothing if there is no SortController. This method is
2364         * called on comparator property change notification from the ext column model. <p>
2365         * 
2366         * <b>Note</b>: as of post-1.0 a column's property is propagated to the SortController. 
2367         * Whether or not a change triggers a re-sort is up to either the concrete controller 
2368         * implementation (the default doesn't) or client code. This behaviour is
2369         * different from old SwingX style sorting.
2370         * 
2371         * @param column the <code>TableColumn</code> which sent the change
2372         *        notifcation
2373         * @param sortable the new value of the column's sortable property
2374         */
2375        private void updateComparatorAfterColumnChanged(TableColumn column,
2376                Comparator<?> comparator) {
2377            if (getSortController() == null) return;
2378            getSortController().setComparator(column.getModelIndex(), comparator);
2379        }
2380    
2381        // -------------------------- ColumnFactory
2382    
2383        /**
2384         * Creates, configures and adds default <code>TableColumn</code>s for
2385         * columns in this table's <code>TableModel</code>. Removes all currently
2386         * contained <code>TableColumn</code>s. The exact type and configuration of
2387         * the columns is controlled completely by the <code>ColumnFactory</code>.
2388         * Client code can use {@link #setColumnFactory(ColumnFactory)} to plug-in a
2389         * custom ColumnFactory implementing their own default column creation and
2390         * behaviour.
2391         * <p>
2392         * 
2393         * <b>Note</b>: this method will probably become final (Issue #961-SwingX)
2394         * so it's strongly recommended to not override now (and replace existing
2395         * overrides by a custom ColumnFactory)!
2396         * 
2397         * @see #setColumnFactory(ColumnFactory)
2398         * @see org.jdesktop.swingx.table.ColumnFactory
2399         * 
2400         */
2401        @Override
2402        public final void createDefaultColumnsFromModel() {
2403            // JW: when could this happen?
2404            if (getModel() == null)
2405                return;
2406            // Remove any current columns
2407            removeColumns();
2408            createAndAddColumns();
2409        }
2410    
2411        /**
2412         * Creates and adds <code>TableColumn</code>s for each column of the table
2413         * model.
2414         * <p>
2415         * 
2416         * 
2417         */
2418        private void createAndAddColumns() {
2419            /*
2420             * PENDING: go the whole distance and let the factory decide which model
2421             * columns to map to view columns? That would introduce an collection
2422             * managing operation into the factory, sprawling? Can't (and probably
2423             * don't want to) move all collection related operations over - the
2424             * ColumnFactory relies on TableColumnExt type columns, while the
2425             * JXTable has to cope with all the base types.
2426             */
2427            for (int i = 0; i < getModel().getColumnCount(); i++) {
2428                // add directly to columnModel - don't go through this.addColumn
2429                // to guarantee full control of ColumnFactory
2430                // addColumn has the side-effect to set the header!
2431                TableColumnExt tableColumn = getColumnFactory()
2432                        .createAndConfigureTableColumn(getModel(), i);
2433                if (tableColumn != null) {
2434                    getColumnModel().addColumn(tableColumn);
2435                }
2436            }
2437        }
2438    
2439        /**
2440         * Remove all columns, make sure to include hidden.
2441         * <p>
2442         */
2443        private void removeColumns() {
2444            /*
2445             * TODO: promote this method to superclass, and change
2446             * createDefaultColumnsFromModel() to call this method
2447             */
2448            List<TableColumn> columns = getColumns(true);
2449            for (Iterator<TableColumn> iter = columns.iterator(); iter.hasNext();) {
2450                getColumnModel().removeColumn(iter.next());
2451    
2452            }
2453        }
2454    
2455        /**
2456         * Returns the ColumnFactory.
2457         * <p>
2458         * 
2459         * @return the columnFactory to use for column creation and configuration,
2460         *         guaranteed to not be null.
2461         * 
2462         * @see #setColumnFactory(ColumnFactory)
2463         * @see org.jdesktop.swingx.table.ColumnFactory
2464         */
2465        public ColumnFactory getColumnFactory() {
2466            /*
2467             * TODO JW: think about implications of not/ copying the reference to
2468             * the shared instance into the table's field? Better access the
2469             * getInstance() on each call? We are on single thread anyway...
2470             * Furthermore, we don't expect the instance to change often, typically
2471             * it is configured on startup. So we don't really have to worry about
2472             * changes which would destabilize column state?
2473             */
2474            if (columnFactory == null) {
2475                return ColumnFactory.getInstance();
2476                // columnFactory = ColumnFactory.getInstance();
2477            }
2478            return columnFactory;
2479        }
2480    
2481        /**
2482         * Sets the <code>ColumnFactory</code> to use for column creation and
2483         * configuration. The default value is the shared application ColumnFactory.
2484         * <p>
2485         * 
2486         * Note: this method has no side-effect, that is existing columns are
2487         * <b>not</b> re-created automatically, client code must trigger it
2488         * manually.
2489         * 
2490         * @param columnFactory the factory to use, <code>null</code> indicates to
2491         *        use the shared application factory.
2492         * 
2493         * @see #getColumnFactory()
2494         * @see org.jdesktop.swingx.table.ColumnFactory
2495         */
2496        public void setColumnFactory(ColumnFactory columnFactory) {
2497            /*
2498             * 
2499             * TODO auto-configure columns on set? or add public table api to do so?
2500             * Mostly, this is meant to be done once in the lifetime of the table,
2501             * preferably before a model is set ... overshoot?
2502             */
2503            ColumnFactory old = getColumnFactory();
2504            this.columnFactory = columnFactory;
2505            firePropertyChange("columnFactory", old, getColumnFactory());
2506        }
2507    
2508        // -------------------------------- enhanced sizing support
2509    
2510        /**
2511         * Packs all the columns to their optimal size. Works best with auto
2512         * resizing turned off.
2513         * 
2514         * @param margin the margin to apply to each column.
2515         * 
2516         * @see #packColumn(int, int)
2517         * @see #packColumn(int, int, int)
2518         */
2519        public void packTable(int margin) {
2520            for (int c = 0; c < getColumnCount(); c++)
2521                packColumn(c, margin, -1);
2522        }
2523    
2524        /**
2525         * Packs an indivudal column in the table.
2526         * 
2527         * @param column The Column index to pack in View Coordinates
2528         * @param margin The Margin to apply to the column width.
2529         * 
2530         * @see #packColumn(int, int, int)
2531         * @see #packTable(int)
2532         */
2533        public void packColumn(int column, int margin) {
2534            packColumn(column, margin, -1);
2535        }
2536    
2537        /**
2538         * Packs an indivual column in the table to less than or equal to the
2539         * maximum witdth. If maximum is -1 then the column is made as wide as it
2540         * needs.
2541         * 
2542         * @param column the column index to pack in view coordinates
2543         * @param margin the margin to apply to the column
2544         * @param max the maximum width the column can be resized to, -1 means no
2545         *        limit
2546         * 
2547         * @see #packColumn(int, int)
2548         * @see #packTable(int)
2549         * @see ColumnFactory#packColumn(JXTable, TableColumnExt, int, int)
2550         */
2551        public void packColumn(int column, int margin, int max) {
2552            getColumnFactory().packColumn(this, getColumnExt(column), margin, max);
2553        }
2554    
2555        /**
2556         * Returns the preferred number of rows to show in a
2557         * <code>JScrollPane</code>.
2558         * 
2559         * @return the number of rows to show in a <code>JScrollPane</code>
2560         * @see #setVisibleRowCount(int)
2561         */
2562        public int getVisibleRowCount() {
2563            return visibleRowCount;
2564        }
2565    
2566        /**
2567         * Sets the preferred number of rows to show in a <code>JScrollPane</code>.
2568         * <p>
2569         * 
2570         * This is a bound property. The default value is 20.
2571         * <p>
2572         * 
2573         * PENDING: allow negative for use-all? Analogous to visColumnCount.
2574         * 
2575         * @param visibleRowCount number of rows to show in a
2576         *        <code>JScrollPane</code>
2577         * @throws IllegalArgumentException if given count is negative.
2578         * 
2579         * @see #getVisibleRowCount()
2580         */
2581        public void setVisibleRowCount(int visibleRowCount) {
2582            if (visibleRowCount < 0)
2583                throw new IllegalArgumentException(
2584                        "visible row count must not be negative " + visibleRowCount);
2585            if (getVisibleRowCount() == visibleRowCount)
2586                return;
2587            int old = getVisibleRowCount();
2588            this.visibleRowCount = visibleRowCount;
2589            resetCalculatedScrollableSize(false);
2590            firePropertyChange("visibleRowCount", old, getVisibleRowCount());
2591        }
2592    
2593        /**
2594         * Returns the preferred number of columns to show in the
2595         * <code>JScrollPane</code>.
2596         * 
2597         * @return the number of columns to show in the scroll pane.
2598         * 
2599         * @see #setVisibleColumnCount
2600         */
2601        public int getVisibleColumnCount() {
2602            return visibleColumnCount;
2603        }
2604    
2605        /**
2606         * Sets the preferred number of Columns to show in a
2607         * <code>JScrollPane</code>. A negative number is interpreted as use-all
2608         * available visible columns.
2609         * <p>
2610         * 
2611         * This is a bound property. The default value is -1 (effectively the same
2612         * as before the introduction of this property).
2613         * 
2614         * @param visibleColumnCount number of rows to show in a
2615         *        <code>JScrollPane</code>
2616         * @see #getVisibleColumnCount()
2617         */
2618        public void setVisibleColumnCount(int visibleColumnCount) {
2619            if (getVisibleColumnCount() == visibleColumnCount)
2620                return;
2621            int old = getVisibleColumnCount();
2622            this.visibleColumnCount = visibleColumnCount;
2623            resetCalculatedScrollableSize(true);
2624            firePropertyChange("visibleColumnCount", old, getVisibleColumnCount());
2625        }
2626    
2627        /**
2628         * Resets the calculated scrollable size in one dimension, if appropriate.
2629         * 
2630         * @param isColumn flag to denote which dimension to reset, true for width,
2631         *        false for height
2632         * 
2633         */
2634        private void resetCalculatedScrollableSize(boolean isColumn) {
2635            if (calculatedPrefScrollableViewportSize != null) {
2636                if (isColumn) {
2637                    calculatedPrefScrollableViewportSize.width = -1;
2638                } else {
2639                    calculatedPrefScrollableViewportSize.height = -1;
2640                }
2641            }
2642        }
2643    
2644        /**
2645         * {@inheritDoc}
2646         * <p>
2647         * 
2648         * If the given dimension is null, the auto-calculation of the pref
2649         * scrollable size is enabled, otherwise the behaviour is the same as super.
2650         * 
2651         * The default is auto-calc enabled on.
2652         * 
2653         * @see #getPreferredScrollableViewportSize()
2654         */
2655        @Override
2656        public void setPreferredScrollableViewportSize(Dimension size) {
2657            // TODO: figure out why firing the event screws the
2658            // JXTableUnitTest.testPrefScrollableUpdatedOnStructureChanged
2659            // Dimension old = getPreferredScrollableViewportSize();
2660            super.setPreferredScrollableViewportSize(size);
2661            // firePropertyChange("preferredScrollableViewportSize", old,
2662            // getPreferredScrollableViewportSize());
2663        }
2664    
2665        /**
2666         * {@inheritDoc}
2667         * <p>
2668         * Overridden to support auto-calculation of pref scrollable size, dependent
2669         * on the visible row/column count properties. The auto-calc is on if
2670         * there's no explicit pref scrollable size set. Otherwise the fixed size is
2671         * returned
2672         * <p>
2673         * 
2674         * The calculation of the preferred scrollable width is delegated to the
2675         * ColumnFactory to allow configuration with custom strategies implemented
2676         * in custom factories.
2677         * 
2678         * @see #setPreferredScrollableViewportSize(Dimension)
2679         * @see org.jdesktop.swingx.table.ColumnFactory#getPreferredScrollableViewportWidth(JXTable)
2680         */
2681        @Override
2682        public Dimension getPreferredScrollableViewportSize() {
2683            // client code has set this - takes precedence.
2684            Dimension prefSize = super.getPreferredScrollableViewportSize();
2685            if (prefSize != null) {
2686                return new Dimension(prefSize);
2687            }
2688            if (calculatedPrefScrollableViewportSize == null) {
2689                calculatedPrefScrollableViewportSize = new Dimension();
2690                // JW: hmm... fishy ... shouldn't be necessary here?
2691                // maybe its the "early init" in super's tableChanged();
2692                // moved to init which looks okay so far
2693                // initializeColumnPreferredWidths();
2694            }
2695            // the width is reset to -1 in setVisibleColumnCount
2696            if (calculatedPrefScrollableViewportSize.width <= 0) {
2697                calculatedPrefScrollableViewportSize.width = getColumnFactory()
2698                        .getPreferredScrollableViewportWidth(this);
2699            }
2700            // the heigth is reset in setVisualRowCount
2701            if (calculatedPrefScrollableViewportSize.height <= 0) {
2702                calculatedPrefScrollableViewportSize.height = getVisibleRowCount()
2703                        * getRowHeight();
2704            }
2705            return new Dimension(calculatedPrefScrollableViewportSize);
2706        }
2707    
2708        /**
2709         * Initialize the width related properties of all contained TableColumns,
2710         * both visible and hidden.
2711         * <p>
2712         * <ul>
2713         * <li>PENDING: move into ColumnFactory?
2714         * <li>PENDING: what to do if autoCreateColumn off?
2715         * <li>PENDING: public? to allow manual setting of column properties which
2716         * might effect their default sizing. Needed in testing - but real-world?
2717         * the factory is meant to do the property setting, based on tableModel and
2718         * meta-data (from where?). But leads to funny call sequence for per-table
2719         * factory (new JXTable(), table.setColumnFactory(..), table.setModel(...))
2720         * </ul>
2721         * 
2722         * @see #initializeColumnPreferredWidth(TableColumn)
2723         */
2724        protected void initializeColumnWidths() {
2725            for (TableColumn column : getColumns(true)) {
2726                initializeColumnPreferredWidth(column);
2727            }
2728        }
2729    
2730        /**
2731         * Initialize the width related properties of the specified column. The
2732         * details are specified by the current <code>ColumnFactory</code> if the
2733         * column is of type <code>TableColumnExt</code>. Otherwise nothing is
2734         * changed.
2735         * <p>
2736         * 
2737         * TODO JW - need to cleanup getScrollablePreferred (refactor and inline)
2738         * 
2739         * @param column TableColumn object representing view column
2740         * @see org.jdesktop.swingx.table.ColumnFactory#configureColumnWidths
2741         */
2742        protected void initializeColumnPreferredWidth(TableColumn column) {
2743            if (column instanceof TableColumnExt) {
2744                getColumnFactory().configureColumnWidths(this,
2745                        (TableColumnExt) column);
2746            }
2747        }
2748    
2749        // ----------------- scrolling support
2750        /**
2751         * Scrolls vertically to make the given row visible. This might not have any
2752         * effect if the table isn't contained in a <code>JViewport</code>.
2753         * <p>
2754         * 
2755         * Note: this method has no precondition as it internally uses
2756         * <code>getCellRect</code> which is lenient to off-range coordinates.
2757         * 
2758         * @param row the view row index of the cell
2759         * 
2760         * @see #scrollColumnToVisible(int)
2761         * @see #scrollCellToVisible(int, int)
2762         * @see #scrollRectToVisible(Rectangle)
2763         */
2764        public void scrollRowToVisible(int row) {
2765            Rectangle cellRect = getCellRect(row, 0, false);
2766            Rectangle visibleRect = getVisibleRect();
2767            cellRect.x = visibleRect.x;
2768            cellRect.width = visibleRect.width;
2769            scrollRectToVisible(cellRect);
2770        }
2771    
2772        /**
2773         * Scrolls horizontally to make the given column visible. This might not
2774         * have any effect if the table isn't contained in a <code>JViewport</code>.
2775         * <p>
2776         * 
2777         * Note: this method has no precondition as it internally uses
2778         * <code>getCellRect</code> which is lenient to off-range coordinates.
2779         * 
2780         * @param column the view column index of the cell
2781         * 
2782         * @see #scrollRowToVisible(int)
2783         * @see #scrollCellToVisible(int, int)
2784         * @see #scrollRectToVisible(Rectangle)
2785         */
2786        public void scrollColumnToVisible(int column) {
2787            Rectangle cellRect = getCellRect(0, column, false);
2788            Rectangle visibleRect = getVisibleRect();
2789            cellRect.y = visibleRect.y;
2790            cellRect.height = visibleRect.height;
2791            scrollRectToVisible(cellRect);
2792        }
2793    
2794        /**
2795         * Scrolls to make the cell at row and column visible. This might not have
2796         * any effect if the table isn't contained in a <code>JViewport</code>.
2797         * <p>
2798         * 
2799         * Note: this method has no precondition as it internally uses
2800         * <code>getCellRect</code> which is lenient to off-range coordinates.
2801         * 
2802         * @param row the view row index of the cell
2803         * @param column the view column index of the cell
2804         * 
2805         * @see #scrollColumnToVisible(int)
2806         * @see #scrollRowToVisible(int)
2807         * @see #scrollRectToVisible(Rectangle)
2808         */
2809        public void scrollCellToVisible(int row, int column) {
2810            Rectangle cellRect = getCellRect(row, column, false);
2811            scrollRectToVisible(cellRect);
2812        }
2813    
2814        // ----------------------- delegating methods?? from super
2815        /**
2816         * Returns the selection mode used by this table's selection model.
2817         * <p>
2818         * PENDING JW - setter?
2819         * 
2820         * @return the selection mode used by this table's selection model
2821         * @see ListSelectionModel#getSelectionMode()
2822         */
2823        public int getSelectionMode() {
2824            return getSelectionModel().getSelectionMode();
2825        }
2826    
2827        // ----------------------- Search support
2828    
2829        /** 
2830         * Starts a search on this List's visible items. This implementation asks the
2831         * SearchFactory to open a find widget on itself.
2832         */
2833        protected void doFind() {
2834            SearchFactory.getInstance().showFindInput(this, getSearchable());
2835        }
2836    
2837        /**
2838         * Returns a Searchable for this component, guaranteed to be not null. This 
2839         * implementation lazily creates a TableSearchable if necessary.
2840         *  
2841         * 
2842         * @return a not-null Searchable for this component.
2843         * 
2844         * @see #setSearchable(Searchable)
2845         * @see org.jdesktop.swingx.search.TableSearchable
2846         */
2847        public Searchable getSearchable() {
2848            if (searchable == null) {
2849                searchable = new TableSearchable(this);
2850            }
2851            return searchable;
2852        }
2853    
2854        /**
2855         * Sets the Searchable for this table. If null, a default 
2856         * searchable will be used.
2857         * 
2858         * @param searchable the Searchable to use for this table, may be null to indicate
2859         *   using the table's default searchable.
2860         */
2861        public void setSearchable(Searchable searchable) {
2862            this.searchable = searchable;
2863        }
2864    
2865        // ----------------------------------- uniform data model access
2866        /**
2867         * @return the unconfigured ComponentAdapter.
2868         */
2869        protected ComponentAdapter getComponentAdapter() {
2870            if (dataAdapter == null) {
2871                dataAdapter = new TableAdapter(this);
2872            }
2873            return dataAdapter;
2874        }
2875    
2876        /**
2877         * Convenience to access a configured ComponentAdapter.
2878         * 
2879         * @param row the row index in view coordinates.
2880         * @param column the column index in view coordinates.
2881         * @return the configured ComponentAdapter.
2882         */
2883        protected ComponentAdapter getComponentAdapter(int row, int column) {
2884            ComponentAdapter adapter = getComponentAdapter();
2885            adapter.row = row;
2886            adapter.column = column;
2887            return adapter;
2888        }
2889    
2890        protected static class TableAdapter extends ComponentAdapter {
2891            private final JXTable table;
2892    
2893            /**
2894             * Constructs a <code>TableDataAdapter</code> for the specified target
2895             * component.
2896             * 
2897             * @param component the target component
2898             */
2899            public TableAdapter(JXTable component) {
2900                super(component);
2901                table = component;
2902            }
2903    
2904            /**
2905             * Typesafe accessor for the target component.
2906             * 
2907             * @return the target component as a {@link javax.swing.JTable}
2908             */
2909            public JXTable getTable() {
2910                return table;
2911            }
2912    
2913            /**
2914             * {@inheritDoc}
2915             */
2916            @Override
2917            public String getColumnName(int columnIndex) {
2918                TableColumn column = getColumnByModelIndex(columnIndex);
2919                return column == null ? "" : column.getHeaderValue().toString();
2920            }
2921    
2922            protected TableColumn getColumnByModelIndex(int modelColumn) {
2923                // throwing here makes a filter test fail .. it's probably an issue
2924                // but don't want to touch (swingx filters will be gone soon)
2925                // if ((modelColumn < 0) || (modelColumn >= getColumnCount())) {
2926                // throw new IllegalArgumentException("invalid column index: " +
2927                // modelColumn);
2928                // }
2929                List<TableColumn> columns = table.getColumns(true);
2930                for (Iterator<TableColumn> iter = columns.iterator(); iter
2931                        .hasNext();) {
2932                    TableColumn column = iter.next();
2933                    if (column.getModelIndex() == modelColumn) {
2934                        return column;
2935                    }
2936                }
2937                return null;
2938            }
2939    
2940            // @Override
2941            // public String getColumnIdentifier(int columnIndex) {
2942            // // TableColumn column = getColumnByModelIndex(columnIndex);
2943            // // Object identifier = column != null ? column.getIdentifier() :
2944            // null;
2945            // Object identifier = getColumnIdentifierAt(columnIndex);
2946            // return identifier != null ? identifier.toString() : null;
2947            // }
2948    
2949            /**
2950             * {@inheritDoc}
2951             */
2952            @Override
2953            public Object getColumnIdentifierAt(int columnIndex) {
2954                if ((columnIndex < 0) || (columnIndex >= getColumnCount())) {
2955                    throw new ArrayIndexOutOfBoundsException(
2956                            "invalid column index: " + columnIndex);
2957                }
2958                TableColumn column = getColumnByModelIndex(columnIndex);
2959                Object identifier = column != null ? column.getIdentifier() : null;
2960                return identifier;
2961            }
2962    
2963            /**
2964             * {@inheritDoc}
2965             */
2966            @Override
2967            public int getColumnIndex(Object identifier) {
2968                TableColumn column = table.getColumnExt(identifier);
2969                return column != null ? column.getModelIndex() : -1;
2970            }
2971    
2972            /**
2973             * {@inheritDoc}
2974             */
2975            @Override
2976            public int getColumnCount() {
2977                return table.getModel().getColumnCount();
2978            }
2979    
2980            /**
2981             * {@inheritDoc}
2982             */
2983            @Override
2984            public int getRowCount() {
2985                return table.getModel().getRowCount();
2986            }
2987    
2988            /**
2989             * {@inheritDoc}
2990             */
2991            @Override
2992            public Object getValueAt(int row, int column) {
2993                return table.getModel().getValueAt(row, column);
2994            }
2995    
2996            /**
2997             * {@inheritDoc}
2998             */
2999            @Override
3000            public boolean isCellEditable(int row, int column) {
3001                return table.getModel().isCellEditable(row, column);
3002            }
3003    
3004            /**
3005             * {@inheritDoc}
3006             */
3007            @Override
3008            public boolean isTestable(int column) {
3009                return getColumnByModelIndex(column) != null;
3010            }
3011    
3012            // -------------------------- accessing view state/values
3013    
3014            /**
3015             * {@inheritDoc}
3016             */
3017            @Override
3018            public Object getFilteredValueAt(int row, int column) {
3019                return getValueAt(table.convertRowIndexToModel(row), column);
3020            }
3021    
3022            /**
3023             * {@inheritDoc}
3024             */
3025            @Override
3026            public Object getValue() {
3027                return table.getValueAt(row, column);
3028            }
3029    
3030            /**
3031             * {@inheritDoc}
3032             * <p>
3033             * 
3034             * PENDING JW: this is implemented to duplicate this table's lookup code
3035             * if the column is not visible. That's not good enough if subclasses
3036             * implemented a different strategy! We do it anyway for now, mostly we
3037             * will be lucky and improve the situation against using toString
3038             * always.
3039             * 
3040             */
3041            @Override
3042            public String getFilteredStringAt(int row, int column) {
3043                int viewColumn = modelToView(column);
3044                if (viewColumn >= 0) {
3045                    return table.getStringAt(row, viewColumn);
3046                }
3047                // PENDING JW: how to get a String rep for invisible cells?
3048                // rows may be filtered, columns hidden.
3049                TableCellRenderer renderer = getRendererByModelColumn(column);
3050                if (renderer instanceof StringValue) {
3051                    return ((StringValue) renderer).getString(getFilteredValueAt(
3052                            row, column));
3053                }
3054                return super.getFilteredStringAt(row, column);
3055            }
3056    
3057            /**
3058             * {@inheritDoc}
3059             */
3060            @Override
3061            public String getString() {
3062                return table.getStringAt(row, column);
3063            }
3064    
3065            /**
3066             * {@inheritDoc}
3067             * 
3068             * PENDING JW: this is implemented to duplicate this table's lookup code
3069             * if either the row or the column is not visible. That's not good
3070             * enough if subclasses implemented a different strategy! We do it
3071             * anyway for now, mostly we will be lucky and improve the situation
3072             * against using toString always.
3073             * 
3074             */
3075            @Override
3076            public String getStringAt(int row, int column) {
3077                int viewRow = table.convertRowIndexToView(row);
3078                int viewColumn = table.convertColumnIndexToView(column);
3079                if ((viewRow >= 0) && (viewColumn >= 0)) {
3080                    return table.getStringAt(viewRow, viewColumn);
3081                }
3082    
3083                TableCellRenderer renderer = getRendererByModelColumn(column);
3084                if (renderer instanceof StringValue) {
3085                    return ((StringValue) renderer).getString(getValueAt(row,
3086                            column));
3087                }
3088                // no luck - return default
3089                return super.getStringAt(row, column);
3090            }
3091    
3092            /**
3093             * Returns a suitable renderer for the column index in model
3094             * coordinates.
3095             * 
3096             * PENDING JW: this duplicates this table's lookup code if column is not
3097             * visible. That's not good enough if subclasses implemented a different
3098             * strategy! We do it anyway for now, mostly we will be lucky and
3099             * improve the situation against using toString always.
3100             * 
3101             * @param column the columnIndex in model coordinates
3102             * @return a renderer suitable for rendering cells in the given column
3103             */
3104            private TableCellRenderer getRendererByModelColumn(int column) {
3105                // PENDING JW: here we are tricksing - duplicating JXTable renderer
3106                // lookup strategy
3107                // that's inherently unsafe, as subclasses may decide to do it
3108                // differently
3109                TableColumn tableColumn = getColumnByModelIndex(column);
3110                TableCellRenderer renderer = tableColumn.getCellRenderer();
3111                if (renderer == null) {
3112                    renderer = table.getDefaultRenderer(table.getModel()
3113                            .getColumnClass(column));
3114                }
3115                if (renderer == null) {
3116                    renderer = table.getDefaultRenderer(Object.class);
3117                }
3118                return renderer;
3119            }
3120    
3121            /**
3122             * {@inheritDoc}
3123             */
3124            @Override
3125            public boolean isEditable() {
3126                return table.isCellEditable(row, column);
3127            }
3128    
3129            /**
3130             * {@inheritDoc}
3131             */
3132            @Override
3133            public boolean isSelected() {
3134                return table.isCellSelected(row, column);
3135            }
3136    
3137            /**
3138             * {@inheritDoc}
3139             */
3140            @Override
3141            public boolean hasFocus() {
3142                boolean rowIsLead = (table.getSelectionModel()
3143                        .getLeadSelectionIndex() == row);
3144                boolean colIsLead = (table.getColumnModel().getSelectionModel()
3145                        .getLeadSelectionIndex() == column);
3146                return table.isFocusOwner() && (rowIsLead && colIsLead);
3147            }
3148    
3149            /**
3150             * {@inheritDoc}
3151             */
3152            @Override
3153            public int modelToView(int columnIndex) {
3154                return table.convertColumnIndexToView(columnIndex);
3155            }
3156    
3157            /**
3158             * {@inheritDoc}
3159             */
3160            @Override
3161            public int viewToModel(int columnIndex) {
3162                return table.convertColumnIndexToModel(columnIndex);
3163            }
3164    
3165        }
3166    
3167        // --------------------- managing renderers/editors
3168    
3169        /**
3170         * Sets the <code>Highlighter</code>s to the table, replacing any old
3171         * settings. None of the given Highlighters must be null.
3172         * <p>
3173         * 
3174         * This is a bound property.
3175         * <p>
3176         * 
3177         * Note: as of version #1.257 the null constraint is enforced strictly. To
3178         * remove all highlighters use this method without param.
3179         * 
3180         * @param highlighters zero or more not null highlighters to use for
3181         *        renderer decoration.
3182         * @throws NullPointerException if array is null or array contains null
3183         *         values.
3184         * 
3185         * @see #getHighlighters()
3186         * @see #addHighlighter(Highlighter)
3187         * @see #removeHighlighter(Highlighter)
3188         * 
3189         */
3190        public void setHighlighters(Highlighter... highlighters) {
3191            Highlighter[] old = getHighlighters();
3192            getCompoundHighlighter().setHighlighters(highlighters);
3193            firePropertyChange("highlighters", old, getHighlighters());
3194        }
3195    
3196        /**
3197         * Returns the <code>Highlighter</code>s used by this table. Maybe empty,
3198         * but guarantees to be never null.
3199         * 
3200         * @return the Highlighters used by this table, guaranteed to never null.
3201         * @see #setHighlighters(Highlighter[])
3202         */
3203        public Highlighter[] getHighlighters() {
3204            return getCompoundHighlighter().getHighlighters();
3205        }
3206    
3207        /**
3208         * Appends a <code>Highlighter</code> to the end of the list of used
3209         * <code>Highlighter</code>s. The argument must not be null.
3210         * <p>
3211         * 
3212         * @param highlighter the <code>Highlighter</code> to add, must not be null.
3213         * @throws NullPointerException if <code>Highlighter</code> is null.
3214         * 
3215         * @see #removeHighlighter(Highlighter)
3216         * @see #setHighlighters(Highlighter[])
3217         */
3218        public void addHighlighter(Highlighter highlighter) {
3219            Highlighter[] old = getHighlighters();
3220            getCompoundHighlighter().addHighlighter(highlighter);
3221            firePropertyChange("highlighters", old, getHighlighters());
3222        }
3223    
3224        /**
3225         * Removes the given Highlighter.
3226         * <p>
3227         * 
3228         * Does nothing if the Highlighter is not contained.
3229         * 
3230         * @param highlighter the Highlighter to remove.
3231         * @see #addHighlighter(Highlighter)
3232         * @see #setHighlighters(Highlighter...)
3233         */
3234        public void removeHighlighter(Highlighter highlighter) {
3235            Highlighter[] old = getHighlighters();
3236            getCompoundHighlighter().removeHighlighter(highlighter);
3237            firePropertyChange("highlighters", old, getHighlighters());
3238        }
3239    
3240        /**
3241         * Returns the CompoundHighlighter assigned to the table, null if none.
3242         * PENDING: open up for subclasses again?.
3243         * 
3244         * @return the CompoundHighlighter assigned to the table.
3245         */
3246        protected CompoundHighlighter getCompoundHighlighter() {
3247            if (compoundHighlighter == null) {
3248                compoundHighlighter = new CompoundHighlighter();
3249                compoundHighlighter
3250                        .addChangeListener(getHighlighterChangeListener());
3251            }
3252            return compoundHighlighter;
3253        }
3254    
3255        /**
3256         * Returns the <code>ChangeListener</code> to use with highlighters. Lazily
3257         * creates the listener.
3258         * 
3259         * @return the ChangeListener for observing changes of highlighters,
3260         *         guaranteed to be <code>not-null</code>
3261         */
3262        protected ChangeListener getHighlighterChangeListener() {
3263            if (highlighterChangeListener == null) {
3264                highlighterChangeListener = createHighlighterChangeListener();
3265            }
3266            return highlighterChangeListener;
3267        }
3268    
3269        /**
3270         * Creates and returns the ChangeListener observing Highlighters.
3271         * <p>
3272         * Here: repaints the table on receiving a stateChanged.
3273         * 
3274         * @return the ChangeListener defining the reaction to changes of
3275         *         highlighters.
3276         */
3277        protected ChangeListener createHighlighterChangeListener() {
3278            return new ChangeListener() {
3279                public void stateChanged(ChangeEvent e) {
3280                    repaint();
3281                }
3282            };
3283        }
3284    
3285        /**
3286         * Returns the string representation of the cell value at the given
3287         * position.
3288         * 
3289         * @param row the row index of the cell in view coordinates
3290         * @param column the column index of the cell in view coordinates.
3291         * @return the string representation of the cell value as it will appear in
3292         *         the table.
3293         */
3294        public String getStringAt(int row, int column) {
3295            TableCellRenderer renderer = getCellRenderer(row, column);
3296            if (renderer instanceof StringValue) {
3297                return ((StringValue) renderer).getString(getValueAt(row, column));
3298            }
3299            return StringValues.TO_STRING.getString(getValueAt(row, column));
3300        }
3301    
3302        /**
3303         * {@inheritDoc}
3304         * <p>
3305         * 
3306         * Overridden to fix core bug #4614616 (NPE if <code>TableModel</code>'s
3307         * <code>Class</code> for the column is an interface). This method
3308         * guarantees to always return a <code>not null</code> value. Returns the
3309         * default renderer for <code>Object</code> if super returns
3310         * <code>null</code>.
3311         * 
3312         * 
3313         */
3314        @Override
3315        public TableCellRenderer getCellRenderer(int row, int column) {
3316            TableCellRenderer renderer = super.getCellRenderer(row, column);
3317            if (renderer == null) {
3318                renderer = getDefaultRenderer(Object.class);
3319            }
3320            return renderer;
3321        }
3322    
3323        /**
3324         * Returns the decorated <code>Component</code> used as a stamp to render
3325         * the specified cell. Overrides superclass version to provide support for
3326         * cell decorators.
3327         * <p>
3328         * 
3329         * Adjusts component orientation (guaranteed to happen before applying
3330         * Highlighters).
3331         * <p>
3332         * 
3333         * Per-column highlighters contained in
3334         * {@link TableColumnExt#getHighlighters()} are applied to the renderer
3335         * <i>after</i> the table highlighters.
3336         * <p>
3337         * 
3338         * TODO kgs: interaction of search highlighter and column highlighters
3339         * <p>
3340         * 
3341         * Note: DefaultTableCellRenderer and subclasses require a hack to play
3342         * nicely with Highlighters because it has an internal "color memory" in
3343         * setForeground/setBackground. The hack is applied in
3344         * <code>resetDefaultTableCellRendererColors</code> which is called after
3345         * super.prepareRenderer and before applying the Highlighters. The method is
3346         * called always and for all renderers.
3347         * 
3348         * @param renderer the <code>TableCellRenderer</code> to prepare
3349         * @param row the row of the cell to render, where 0 is the first row
3350         * @param column the column of the cell to render, where 0 is the first
3351         *        column
3352         * @return the decorated <code>Component</code> used as a stamp to render
3353         *         the specified cell
3354         * @see #resetDefaultTableCellRendererColors(Component, int, int)
3355         * @see org.jdesktop.swingx.decorator.Highlighter
3356         */
3357        @Override
3358        public Component prepareRenderer(TableCellRenderer renderer, int row,
3359                int column) {
3360            Component stamp = super.prepareRenderer(renderer, row, column);
3361            // #145-swingx: default renderers don't respect componentOrientation.
3362            adjustComponentOrientation(stamp);
3363            // #258-swingx: hacking around DefaultTableCellRenderer color memory.
3364            resetDefaultTableCellRendererColors(stamp, row, column);
3365    
3366            ComponentAdapter adapter = getComponentAdapter(row, column);
3367            // a very slight optimization: if this instance never had a highlighter
3368            // added then don't create a compound here.
3369            if (compoundHighlighter != null) {
3370                stamp = compoundHighlighter.highlight(stamp, adapter);
3371            }
3372    
3373            TableColumnExt columnExt = getColumnExt(column);
3374    
3375            if (columnExt != null) {
3376                // JW: fix for #838 - artificial compound installs listener
3377                // PENDING JW: instead of doing the looping ourselves, how
3378                // about adding a method prepareRenderer to the TableColumnExt
3379                for (Highlighter highlighter : columnExt.getHighlighters()) {
3380                    stamp = highlighter.highlight(stamp, adapter);
3381    
3382                }
3383                // CompoundHighlighter columnHighlighters
3384                // = new CompoundHighlighter(columnExt.getHighlighters());
3385    
3386            }
3387    
3388            return stamp;
3389        }
3390    
3391        /**
3392         * 
3393         * Method to apply a hack around DefaultTableCellRenderer "color memory"
3394         * (Issue #258-swingx). Applies the hack if the client property
3395         * <code>USE_DTCR_COLORMEMORY_HACK</code> having the value of
3396         * <code>Boolean.TRUE</code>, does nothing otherwise. The property is true
3397         * by default.
3398         * <p>
3399         * 
3400         * The hack consists of applying a specialized <code>Highlighter</code> to
3401         * force reset the color "memory" of <code>DefaultTableCellRenderer</code>.
3402         * Note that the hack is applied always, that is even if there are no custom
3403         * Highlighters.
3404         * <p>
3405         * 
3406         * Client code which solves the problem at the core (that is in a
3407         * well-behaved <code>DefaultTableCellRenderer</code>) can disable the hack
3408         * by removing the client property or by subclassing and override this to do
3409         * nothing.
3410         * 
3411         * @param renderer the <code>TableCellRenderer</code> to hack
3412         * @param row the row of the cell to render
3413         * @param column the column index of the cell to render
3414         * 
3415         * @see #prepareRenderer(TableCellRenderer, int, int)
3416         * @see #USE_DTCR_COLORMEMORY_HACK
3417         * @see org.jdesktop.swingx.decorator.ResetDTCRColorHighlighter
3418         */
3419        protected void resetDefaultTableCellRendererColors(Component renderer,
3420                int row, int column) {
3421            if (!Boolean.TRUE.equals(getClientProperty(USE_DTCR_COLORMEMORY_HACK)))
3422                return;
3423            ComponentAdapter adapter = getComponentAdapter(row, column);
3424            if (resetDefaultTableCellRendererHighlighter == null) {
3425                resetDefaultTableCellRendererHighlighter = new ResetDTCRColorHighlighter();
3426            }
3427            // hacking around DefaultTableCellRenderer color memory.
3428            resetDefaultTableCellRendererHighlighter.highlight(renderer, adapter);
3429        }
3430    
3431        /**
3432         * {@inheritDoc}
3433         * <p>
3434         * 
3435         * Overridden to adjust the editor's component orientation.
3436         */
3437        @Override
3438        public Component prepareEditor(TableCellEditor editor, int row, int column) {
3439            Component comp = super.prepareEditor(editor, row, column);
3440            // JW: might be null if generic editor barks about constructor
3441            // super silently backs out - we do the same here
3442            if (comp != null) {
3443                adjustComponentOrientation(comp);
3444            }
3445            return comp;
3446        }
3447    
3448        /**
3449         * Adjusts the <code>Component</code>'s orientation to this
3450         * <code>JXTable</code>'s CO if appropriate. The parameter must not be
3451         * <code>null</code>.
3452         * <p>
3453         * 
3454         * This implementation synchs the CO always.
3455         * 
3456         * @param stamp the <code>Component</code> who's CO may need to be synched,
3457         *        must not be <code>null</code>.
3458         */
3459        protected void adjustComponentOrientation(Component stamp) {
3460            if (stamp.getComponentOrientation().equals(getComponentOrientation()))
3461                return;
3462            stamp.applyComponentOrientation(getComponentOrientation());
3463        }
3464    
3465        /**
3466         * Returns a new instance of the default renderer for the specified class.
3467         * This differs from <code>getDefaultRenderer()</code> in that it returns a
3468         * <b>new </b> instance each time so that the renderer may be set and
3469         * customized on a particular column.
3470         * <p>
3471         * 
3472         * NOTE: this doesn't work with swingx renderers! Do we really need it? It
3473         * had been used in JNTable which is practically obsolete. If needed, we
3474         * could make all renderer support classes clonable.
3475         * 
3476         * @param columnClass Class of value being rendered
3477         * @return TableCellRenderer instance which renders values of the specified
3478         *         type
3479         * @see #getDefaultRenderer(Class)
3480         */
3481        public TableCellRenderer getNewDefaultRenderer(Class<?> columnClass) {
3482            TableCellRenderer renderer = getDefaultRenderer(columnClass);
3483            if (renderer != null) {
3484                try {
3485                    return renderer.getClass().newInstance();
3486                } catch (Exception e) {
3487                    LOG.fine("could not create renderer for " + columnClass);
3488                }
3489            }
3490            // JW PENDING: must not return null!
3491            return null;
3492        }
3493    
3494        /**
3495         * Creates default cell renderers for <code>Object</code>s,
3496         * <code>Number</code>s, <code>Date</code>s, <code>Boolean</code>s, and
3497         * <code>Icon/Image/</code>s.
3498         * <p>
3499         * Overridden to install SwingX renderers plus hacking around huge memory
3500         * consumption of UIDefaults (see #6345050 in core Bug parade)
3501         * <p>
3502         * {@inheritDoc}
3503         * 
3504         * @see org.jdesktop.swingx.renderer.DefaultTableRenderer
3505         * @see org.jdesktop.swingx.renderer.ComponentProvider
3506         */
3507        @Override
3508        protected void createDefaultRenderers() {
3509            // super.createDefaultRenderers();
3510            // This duplicates JTable's functionality in order to make the renderers
3511            // available in getNewDefaultRenderer(); If JTable's renderers either
3512            // were public, or it provided a factory for *new* renderers, this would
3513            // not be needed
3514    
3515            // hack around #6345050 - new UIDefaults()
3516            // is created with a huge initialCapacity
3517            // giving a dummy key/value array as parameter reduces that capacity
3518            // to length/2.
3519            Object[] dummies = new Object[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0,
3520                    7, 0, 8, 0, 9, 0, 10, 0, };
3521            defaultRenderersByColumnClass = new UIDefaults(dummies);
3522            defaultRenderersByColumnClass.clear();
3523            // configured default table renderer (internally LabelProvider)
3524            setDefaultRenderer(Object.class, new DefaultTableRenderer());
3525            setDefaultRenderer(Number.class, new DefaultTableRenderer(
3526                    StringValues.NUMBER_TO_STRING, JLabel.RIGHT));
3527            setDefaultRenderer(Date.class, new DefaultTableRenderer(
3528                    StringValues.DATE_TO_STRING));
3529            // use the same center aligned default for Image/Icon
3530            TableCellRenderer renderer = new DefaultTableRenderer(new MappedValue(
3531                    StringValues.EMPTY, IconValues.ICON), JLabel.CENTER);
3532            setDefaultRenderer(Icon.class, renderer);
3533            setDefaultRenderer(ImageIcon.class, renderer);
3534            // use a ButtonProvider for booleans
3535            setDefaultRenderer(Boolean.class, new DefaultTableRenderer(
3536                    new CheckBoxProvider()));
3537    
3538            // // standard renderers
3539            // // Objects
3540            // setLazyRenderer(Object.class,
3541            // "javax.swing.table.DefaultTableCellRenderer");
3542            //
3543            // // Numbers
3544            // setLazyRenderer(Number.class,
3545            // "org.jdesktop.swingx.JXTable$NumberRenderer");
3546            //
3547            // // Doubles and Floats
3548            // setLazyRenderer(Float.class,
3549            // "org.jdesktop.swingx.JXTable$DoubleRenderer");
3550            // setLazyRenderer(Double.class,
3551            // "org.jdesktop.swingx.JXTable$DoubleRenderer");
3552            //
3553            // // Dates
3554            // setLazyRenderer(Date.class,
3555            // "org.jdesktop.swingx.JXTable$DateRenderer");
3556            //
3557            // // Icons and ImageIcons
3558            // setLazyRenderer(Icon.class,
3559            // "org.jdesktop.swingx.JXTable$IconRenderer");
3560            // setLazyRenderer(ImageIcon.class,
3561            // "org.jdesktop.swingx.JXTable$IconRenderer");
3562            //
3563            // // Booleans
3564            // setLazyRenderer(Boolean.class,
3565            // "org.jdesktop.swingx.JXTable$BooleanRenderer");
3566    
3567        }
3568    
3569        /** c&p'ed from super */
3570        @SuppressWarnings("unchecked")
3571        private void setLazyValue(Hashtable h, Class c, String s) {
3572            h.put(c, new UIDefaults.ProxyLazyValue(s));
3573        }
3574    
3575        /** c&p'ed from super */
3576        private void setLazyEditor(Class<?> c, String s) {
3577            setLazyValue(defaultEditorsByColumnClass, c, s);
3578        }
3579    
3580        /**
3581         * Creates default cell editors for objects, numbers, and boolean values.
3582         * <p>
3583         * Overridden to hook enhanced editors (f.i. <code>NumberEditorExt</code>
3584         * )plus hacking around huge memory consumption of UIDefaults (see #6345050
3585         * in core Bug parade)
3586         * 
3587         * @see DefaultCellEditor
3588         */
3589        @Override
3590        protected void createDefaultEditors() {
3591            Object[] dummies = new Object[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0,
3592                    7, 0, 8, 0, 9, 0, 10, 0,
3593    
3594            };
3595            defaultEditorsByColumnClass = new UIDefaults(dummies);
3596            defaultEditorsByColumnClass.clear();
3597            // defaultEditorsByColumnClass = new UIDefaults();
3598    
3599            // Objects
3600            setLazyEditor(Object.class, "org.jdesktop.swingx.JXTable$GenericEditor");
3601    
3602            // Numbers
3603            // setLazyEditor(Number.class,
3604            // "org.jdesktop.swingx.JXTable$NumberEditor");
3605            setLazyEditor(Number.class, "org.jdesktop.swingx.table.NumberEditorExt");
3606    
3607            // Booleans
3608            setLazyEditor(Boolean.class,
3609                    "org.jdesktop.swingx.JXTable$BooleanEditor");
3610    
3611        }
3612    
3613        /**
3614         * Default editor registered for <code>Object</code>. The editor tries to
3615         * create a new instance of the column's class by reflection. It assumes
3616         * that the class has a constructor taking a single <code>String</code>
3617         * parameter.
3618         * <p>
3619         * 
3620         * The editor can be configured with a custom <code>JTextField</code>.
3621         * 
3622         */
3623        public static class GenericEditor extends DefaultCellEditor {
3624    
3625            Class<?>[] argTypes = new Class<?>[] { String.class };
3626    
3627            java.lang.reflect.Constructor<?> constructor;
3628    
3629            Object value;
3630    
3631            public GenericEditor() {
3632                this(new JTextField());
3633            }
3634    
3635            public GenericEditor(JTextField textField) {
3636                super(textField);
3637                getComponent().setName("Table.editor");
3638            }
3639    
3640            @Override
3641            public boolean stopCellEditing() {
3642                String s = (String) super.getCellEditorValue();
3643                // Here we are dealing with the case where a user
3644                // has deleted the string value in a cell, possibly
3645                // after a failed validation. Return null, so that
3646                // they have the option to replace the value with
3647                // null or use escape to restore the original.
3648                // For Strings, return "" for backward compatibility.
3649                if ("".equals(s)) {
3650                    if (constructor.getDeclaringClass() == String.class) {
3651                        value = s;
3652                    }
3653                    super.stopCellEditing();
3654                }
3655    
3656                try {
3657                    value = constructor.newInstance(new Object[] { s });
3658                } catch (Exception e) {
3659                    ((JComponent) getComponent()).setBorder(new LineBorder(
3660                            Color.red));
3661                    return false;
3662                }
3663                return super.stopCellEditing();
3664            }
3665    
3666            @Override
3667            public Component getTableCellEditorComponent(JTable table,
3668                    Object value, boolean isSelected, int row, int column) {
3669                this.value = null;
3670                ((JComponent) getComponent())
3671                        .setBorder(new LineBorder(Color.black));
3672                try {
3673                    Class<?> type = table.getColumnClass(column);
3674                    // Since our obligation is to produce a value which is
3675                    // assignable for the required type it is OK to use the
3676                    // String constructor for columns which are declared
3677                    // to contain Objects. A String is an Object.
3678                    if (type == Object.class) {
3679                        type = String.class;
3680                    }
3681                    constructor = type.getConstructor(argTypes);
3682                } catch (Exception e) {
3683                    return null;
3684                }
3685                return super.getTableCellEditorComponent(table, value, isSelected,
3686                        row, column);
3687            }
3688    
3689            @Override
3690            public Object getCellEditorValue() {
3691                return value;
3692            }
3693        }
3694    
3695        /**
3696         * 
3697         * Editor for <code>Number</code>s.
3698         * <p>
3699         * Note: this is no longer registered by default. The current default is
3700         * <code>NumberEditorExt</code> which differs from this in being
3701         * locale-aware.
3702         * 
3703         */
3704        public static class NumberEditor extends GenericEditor {
3705    
3706            public NumberEditor() {
3707                ((JTextField) getComponent())
3708                        .setHorizontalAlignment(JTextField.RIGHT);
3709            }
3710        }
3711    
3712        /**
3713         * The default editor for <code>Boolean</code> types.
3714         */
3715        public static class BooleanEditor extends DefaultCellEditor {
3716            public BooleanEditor() {
3717                super(new JCheckBox());
3718                JCheckBox checkBox = (JCheckBox) getComponent();
3719                checkBox.setHorizontalAlignment(JCheckBox.CENTER);
3720            }
3721        }
3722    
3723        // ----------------------------- enhanced editing support
3724    
3725        /**
3726         * Returns the editable property of the <code>JXTable</code> as a whole.
3727         * 
3728         * @return boolean to indicate if the table is editable.
3729         * @see #setEditable
3730         */
3731        public boolean isEditable() {
3732            return editable;
3733        }
3734    
3735        /**
3736         * Sets the editable property. This property allows to mark all cells in a
3737         * table as read-only, independent of their per-column editability as
3738         * returned by <code>TableColumnExt.isEditable</code> and their per-cell
3739         * editability as returned by the <code>TableModel.isCellEditable</code>. If
3740         * a cell is read-only in its column or model layer, this property has no
3741         * effect.
3742         * <p>
3743         * 
3744         * The default value is <code>true</code>.
3745         * 
3746         * @param editable the flag to indicate if the table is editable.
3747         * @see #isEditable
3748         * @see #isCellEditable(int, int)
3749         */
3750        public void setEditable(boolean editable) {
3751            boolean old = isEditable();
3752            this.editable = editable;
3753            firePropertyChange("editable", old, isEditable());
3754        }
3755    
3756        /**
3757         * Returns the property which determines the edit termination behaviour on
3758         * focus lost.
3759         * 
3760         * @return boolean to indicate whether an ongoing edit should be terminated
3761         *         if the focus is moved to somewhere outside of the table.
3762         * @see #setTerminateEditOnFocusLost(boolean)
3763         */
3764        public boolean isTerminateEditOnFocusLost() {
3765            return Boolean.TRUE
3766                    .equals(getClientProperty("terminateEditOnFocusLost"));
3767        }
3768    
3769        /**
3770         * Sets the property to determine whether an ongoing edit should be
3771         * terminated if the focus is moved to somewhere outside of the table. If
3772         * true, terminates the edit, does nothing otherwise. The exact behaviour is
3773         * implemented in <code>JTable.CellEditorRemover</code>: "outside" is
3774         * interpreted to be on a component which is not under the table hierarchy
3775         * but inside the same toplevel window, "terminate" does so in any case,
3776         * first tries to stop the edit, if that's unsuccessful it cancels the edit.
3777         * <p>
3778         * The default value is <code>true</code>.
3779         * 
3780         * @param terminate the flag to determine whether or not to terminate the
3781         *        edit
3782         * @see #isTerminateEditOnFocusLost()
3783         */
3784        public void setTerminateEditOnFocusLost(boolean terminate) {
3785            // JW: we can leave the propertyChange notification to the
3786            // putClientProperty - the key and method name are the same
3787            putClientProperty("terminateEditOnFocusLost", terminate);
3788        }
3789    
3790        /**
3791         * Returns the autoStartsEdit property.
3792         * 
3793         * @return boolean to indicate whether a keyStroke should try to start
3794         *         editing.
3795         * @see #setAutoStartEditOnKeyStroke(boolean)
3796         */
3797        public boolean isAutoStartEditOnKeyStroke() {
3798            return !Boolean.FALSE
3799                    .equals(getClientProperty("JTable.autoStartsEdit"));
3800        }
3801    
3802        /**
3803         * Sets the autoStartsEdit property. If true, keystrokes are passed-on to
3804         * the cellEditor of the lead cell to let it decide whether to start an
3805         * edit.
3806         * <p>
3807         * The default value is <code>true</code>.
3808         * <p>
3809         * 
3810         * @param autoStart boolean to determine whether a keyStroke should try to
3811         *        start editing.
3812         * @see #isAutoStartEditOnKeyStroke()
3813         */
3814        public void setAutoStartEditOnKeyStroke(boolean autoStart) {
3815            boolean old = isAutoStartEditOnKeyStroke();
3816            // JW: we have to take over propertyChange notification
3817            // because the key and method name are different.
3818            // As a consequence, there are two events fired: one for
3819            // the client prop and one for this method.
3820            putClientProperty("JTable.autoStartsEdit", autoStart);
3821            firePropertyChange("autoStartEditOnKeyStroke", old,
3822                    isAutoStartEditOnKeyStroke());
3823        }
3824    
3825        /**
3826         * {@inheritDoc}
3827         * <p>
3828         * 
3829         * overridden to install a custom editor remover.
3830         */
3831        @Override
3832        public boolean editCellAt(int row, int column, EventObject e) {
3833            boolean started = super.editCellAt(row, column, e);
3834            if (started) {
3835                hackEditorRemover();
3836            }
3837            return started;
3838        }
3839    
3840        /**
3841         * Overridden with backport from Mustang fix for #4684090, #4887999.
3842         */
3843        @Override
3844        public void removeEditor() {
3845            // if (editorRemover != null) {
3846            // editorRemover.uninstall();
3847            // editorRemover = null;
3848            // }
3849            boolean isFocusOwnerInTheTable = isFocusOwnerDescending();
3850            // let super do its stuff
3851            super.removeEditor();
3852            if (isFocusOwnerInTheTable) {
3853                requestFocusInWindow();
3854            }
3855        }
3856    
3857        /**
3858         * Returns a boolean to indicate if the current focus owner is descending
3859         * from this table. Returns false if not editing, otherwise walks the
3860         * focusOwner hierarchy, taking popups into account.
3861         * 
3862         * @return a boolean to indicate if the current focus owner is contained.
3863         */
3864        private boolean isFocusOwnerDescending() {
3865            if (!isEditing())
3866                return false;
3867            Component focusOwner = KeyboardFocusManager
3868                    .getCurrentKeyboardFocusManager().getFocusOwner();
3869            // PENDING JW: special casing to not fall through ... really wanted?
3870            if (focusOwner == null)
3871                return false;
3872            if (SwingXUtilities.isDescendingFrom(focusOwner, this))
3873                return true;
3874            // same with permanent focus owner
3875            Component permanent = KeyboardFocusManager
3876                    .getCurrentKeyboardFocusManager().getPermanentFocusOwner();
3877            return SwingXUtilities.isDescendingFrom(permanent, this);
3878        }
3879    
3880        // /**
3881        // * @param focusOwner
3882        // * @return
3883        // */
3884        // private boolean isDescending(Component focusOwner) {
3885        // while (focusOwner != null) {
3886        // if (focusOwner instanceof JPopupMenu) {
3887        // focusOwner = ((JPopupMenu) focusOwner).getInvoker();
3888        // if (focusOwner == null) {
3889        // return false;
3890        // }
3891        // }
3892        // if (focusOwner == this) {
3893        // return true;
3894        // }
3895        // focusOwner = focusOwner.getParent();
3896        // }
3897        // return false;
3898        // }
3899    
3900        protected transient CellEditorRemover editorRemover;
3901    
3902        /**
3903         * removes the standard editor remover and adds the custom remover.
3904         * 
3905         */
3906        private void hackEditorRemover() {
3907            KeyboardFocusManager manager = KeyboardFocusManager
3908                    .getCurrentKeyboardFocusManager();
3909            PropertyChangeListener[] listeners = manager
3910                    .getPropertyChangeListeners("permanentFocusOwner");
3911            for (int i = listeners.length - 1; i >= 0; i--) {
3912                if (listeners[i].getClass().getName().startsWith(
3913                        "javax.swing.JTable")) {
3914                    manager.removePropertyChangeListener("permanentFocusOwner",
3915                            listeners[i]);
3916                    break;
3917                }
3918            }
3919            if (editorRemover == null) {
3920                editorRemover = new CellEditorRemover();
3921            }
3922        }
3923    
3924        /**
3925         * {@inheritDoc}
3926         * <p>
3927         * 
3928         * Overridden to uninstall the custom editor remover.
3929         */
3930        @Override
3931        public void removeNotify() {
3932            if (editorRemover != null) {
3933                editorRemover.uninstall();
3934                editorRemover = null;
3935            }
3936            super.removeNotify();
3937        }
3938    
3939        /**
3940         * {@inheritDoc}
3941         * <p>
3942         * 
3943         * Overridden to prevent spurious focus loss to outside of table while
3944         * removing the editor. This is essentially a hack around core bug #6210779.
3945         * 
3946         * PENDING: add link to wiki!
3947         */
3948        @Override
3949        public boolean isFocusCycleRoot() {
3950            if (isEditingFocusCycleRoot()) {
3951                return true;
3952            }
3953            return super.isFocusCycleRoot();
3954        }
3955    
3956        /**
3957         * {@inheritDoc}
3958         * <p>
3959         * Overridden to try to stop the edit, if appropriate. Calls super if
3960         * succeeded, does not yield otherwise.
3961         * 
3962         */
3963        @Override
3964        public void transferFocus() {
3965            if (isEditingFocusCycleRoot() && !getCellEditor().stopCellEditing())
3966                return;
3967            super.transferFocus();
3968        }
3969    
3970        /**
3971         * {@inheritDoc}
3972         * <p>
3973         * Overridden to try to stop the edit, if appropiate. Calls super if
3974         * succeeded, does not yield otherwise.
3975         * 
3976         */
3977        @Override
3978        public void transferFocusBackward() {
3979            if (isEditingFocusCycleRoot() && !getCellEditor().stopCellEditing())
3980                return;
3981            super.transferFocusBackward();
3982        }
3983    
3984        /**
3985         * 
3986         * @return a boolean to indicate whether the table needs to fake being focus
3987         *         cycle root.
3988         */
3989        private boolean isEditingFocusCycleRoot() {
3990            return isEditing() && isTerminateEditOnFocusLost();
3991        }
3992    
3993        /**
3994         * This class tracks changes in the keyboard focus state. It is used when
3995         * the JTable is editing to determine when to cancel the edit. If focus
3996         * switches to a component outside of the jtable, but in the same window,
3997         * this will cancel editing.
3998         */
3999        class CellEditorRemover implements PropertyChangeListener {
4000            KeyboardFocusManager focusManager;
4001    
4002            public CellEditorRemover() {
4003                install();
4004            }
4005    
4006            private void install() {
4007                focusManager = KeyboardFocusManager
4008                        .getCurrentKeyboardFocusManager();
4009                focusManager.addPropertyChangeListener("permanentFocusOwner", this);
4010                focusManager.addPropertyChangeListener("managingFocus", this);
4011            }
4012    
4013            /**
4014             * remove all listener registrations.
4015             * 
4016             */
4017            public void uninstall() {
4018                focusManager.removePropertyChangeListener("permanentFocusOwner",
4019                        this);
4020                focusManager.removePropertyChangeListener("managingFocus", this);
4021                focusManager = null;
4022            }
4023    
4024            public void propertyChange(PropertyChangeEvent ev) {
4025                if (ev == null)
4026                    return;
4027                if ("permanentFocusOwner".equals(ev.getPropertyName())) {
4028                    permanentFocusOwnerChange();
4029                } else if ("managingFocus".equals(ev.getPropertyName())) {
4030                    // TODO uninstall/install after manager changed.
4031                }
4032            }
4033    
4034            /**
4035             * 
4036             */
4037            private void permanentFocusOwnerChange() {
4038                if (!isEditing() || !isTerminateEditOnFocusLost()) {
4039                    return;
4040                }
4041    
4042                Component c = focusManager.getPermanentFocusOwner();
4043                while (c != null) {
4044                    // PENDING JW: logic untested!
4045                    if (c instanceof JPopupMenu) {
4046                        c = ((JPopupMenu) c).getInvoker();
4047                    } else {
4048                        if (c == JXTable.this) {
4049                            // focus remains inside the table
4050                            return;
4051                        } else if (c instanceof JPopupMenu) {
4052                            // PENDING JW: left-over? we should never reach this ...
4053                            // need to switch the hierarchy to a popups invoker
4054                        } else if ((c instanceof Window)
4055                                || (c instanceof Applet && c.getParent() == null)) {
4056                            if (c == SwingUtilities.getRoot(JXTable.this)) {
4057                                if (!getCellEditor().stopCellEditing()) {
4058                                    getCellEditor().cancelCellEditing();
4059                                }
4060                            }
4061                            break;
4062                        }
4063                        c = c.getParent();
4064                    }
4065                }
4066            }
4067        }
4068    
4069        // ---------------------------- updateUI support
4070    
4071        /**
4072         * {@inheritDoc}
4073         * <p>
4074         * Additionally updates auto-adjusted row height and highlighters.
4075         * <p>
4076         * Another of the override motivation is to fix core issue (?? ID): super
4077         * fails to update <b>all</b> renderers/editors.
4078         */
4079        @Override
4080        public void updateUI() {
4081            super.updateUI();
4082            updateColumnControlUI();
4083            for (Enumeration<?> defaultEditors = defaultEditorsByColumnClass
4084                    .elements(); defaultEditors.hasMoreElements();) {
4085                updateEditorUI(defaultEditors.nextElement());
4086            }
4087    
4088            for (Enumeration<?> defaultRenderers = defaultRenderersByColumnClass
4089                    .elements(); defaultRenderers.hasMoreElements();) {
4090                updateRendererUI(defaultRenderers.nextElement());
4091            }
4092            for (TableColumn column : getColumns(true)) {
4093                updateColumnUI(column);
4094            }
4095            updateRowHeightUI(true);
4096            updateHighlighterUI();
4097        }
4098    
4099        /**
4100         * Updates the ui of the columnControl if appropriate.
4101         */
4102        protected void updateColumnControlUI() {
4103            if ((columnControlButton != null)
4104                    && (columnControlButton.getParent() == null)) {
4105                SwingUtilities.updateComponentTreeUI(columnControlButton);
4106            }
4107        }
4108    
4109        /**
4110         * Tries its best to <code>updateUI</code> of the potential
4111         * <code>TableCellEditor</code>.
4112         * 
4113         * @param maybeEditor the potential editor.
4114         */
4115        private void updateEditorUI(Object maybeEditor) {
4116            // maybe null or proxyValue
4117            if (!(maybeEditor instanceof TableCellEditor))
4118                return;
4119            // super handled this
4120            if ((maybeEditor instanceof JComponent)
4121                    || (maybeEditor instanceof DefaultCellEditor))
4122                return;
4123            // custom editors might balk about fake rows/columns
4124            try {
4125                Component comp = ((TableCellEditor) maybeEditor)
4126                        .getTableCellEditorComponent(this, null, false, -1, -1);
4127                if (comp != null) {
4128                    SwingUtilities.updateComponentTreeUI(comp);
4129                }
4130            } catch (Exception e) {
4131                // ignore - can't do anything
4132            }
4133        }
4134    
4135        /**
4136         * Tries its best to <code>updateUI</code> of the potential
4137         * <code>TableCellRenderer</code>.
4138         * 
4139         * @param maybeRenderer the potential renderer.
4140         */
4141        private void updateRendererUI(Object maybeRenderer) {
4142            // maybe null or proxyValue
4143            if (!(maybeRenderer instanceof TableCellRenderer))
4144                return;
4145            // super handled this
4146            if (maybeRenderer instanceof JComponent)
4147                return;
4148            Component comp = null;
4149            if (maybeRenderer instanceof AbstractRenderer) {
4150                comp = ((AbstractRenderer) maybeRenderer).getComponentProvider()
4151                        .getRendererComponent(null);
4152            } else {
4153                try {
4154                    // custom editors might balk about fake rows/columns
4155                    comp = ((TableCellRenderer) maybeRenderer)
4156                            .getTableCellRendererComponent(this, null, false,
4157                                    false, -1, -1);
4158    
4159                } catch (Exception e) {
4160                    // can't do anything - renderer can't cope with off-range cells
4161                }
4162            }
4163            if (comp != null) {
4164                SwingUtilities.updateComponentTreeUI(comp);
4165            }
4166        }
4167    
4168        /**
4169         * Updates TableColumn after updateUI changes. This implementation delegates
4170         * to the column if it is of type UIDependent, takes over to try an update
4171         * of the column's cellEditor, Cell-/HeaderRenderer otherwise.
4172         * 
4173         * @param column the tableColumn to update.
4174         */
4175        protected void updateColumnUI(TableColumn column) {
4176            if (column instanceof UIDependent) {
4177                ((UIDependent) column).updateUI();
4178            } else {
4179                updateEditorUI(column.getCellEditor());
4180                updateRendererUI(column.getCellRenderer());
4181                updateRendererUI(column.getHeaderRenderer());
4182            }
4183        }
4184    
4185        /**
4186         * Updates highlighter after <code>updateUI</code> changes.
4187         * 
4188         * @see org.jdesktop.swingx.decorator.UIDependent
4189         */
4190        protected void updateHighlighterUI() {
4191            if (compoundHighlighter == null)
4192                return;
4193            compoundHighlighter.updateUI();
4194        }
4195    
4196        /**
4197         * Auto-adjusts rowHeight to something more pleasing then the default. This
4198         * method is called after instantiation and after updating the UI. Does
4199         * nothing if the given parameter is <code>true</code> and the rowHeight had
4200         * been already set by client code. The underlying problem is that raw types
4201         * can't implement UIResource.
4202         * <p>
4203         * This implementation asks the UIManager for a default value (stored with
4204         * key "JXTable.rowHeight"). If none is available, calculates a "reasonable"
4205         * height from the table's fontMetrics, assuming that most renderers/editors
4206         * will have a border with top/bottom of 1.
4207         * <p>
4208         * 
4209         * @param respectRowSetFlag a boolean to indicate whether client-code flag
4210         *        should be respected.
4211         * @see #isXTableRowHeightSet
4212         */
4213        protected void updateRowHeightUI(boolean respectRowSetFlag) {
4214            if (respectRowSetFlag && isXTableRowHeightSet)
4215                return;
4216            int uiHeight = UIManager.getInt(UIPREFIX + "rowHeight");
4217            if (uiHeight > 0) {
4218                setRowHeight(uiHeight);
4219            } else {
4220                int fontBasedHeight = getFontMetrics(getFont()).getHeight() + 2;
4221                int magicMinimum = 18;
4222                setRowHeight(Math.max(fontBasedHeight, magicMinimum));
4223            }
4224            isXTableRowHeightSet = false;
4225        }
4226    
4227        /**
4228         * Convenience to set both grid line visibility and default margin for
4229         * horizontal/vertical lines. The margin defaults to 1 or 0 if the grid
4230         * lines are drawn or not drawn.
4231         * <p>
4232         * 
4233         * @param showHorizontalLines boolean to decide whether to draw horizontal
4234         *        grid lines.
4235         * @param showVerticalLines boolean to decide whether to draw vertical grid
4236         *        lines.
4237         * @see javax.swing.JTable#setShowGrid(boolean)
4238         * @see javax.swing.JTable#setIntercellSpacing(Dimension)
4239         */
4240        public void setShowGrid(boolean showHorizontalLines,
4241                boolean showVerticalLines) {
4242            int defaultRowMargin = showHorizontalLines ? 1 : 0;
4243            setRowMargin(defaultRowMargin);
4244            setShowHorizontalLines(showHorizontalLines);
4245            int defaultColumnMargin = showVerticalLines ? 1 : 0;
4246            setColumnMargin(defaultColumnMargin);
4247            setShowVerticalLines(showVerticalLines);
4248        }
4249    
4250        /**
4251         * {@inheritDoc}
4252         * <p>
4253         * Behaves exactly like super.
4254         * <p>
4255         * It's overridden to warn against a frequent programming error: this method
4256         * toggles only the <b>visibility</b> of the grid lines, it <b>does not</b>
4257         * update the row/column margins - which may lead to visual artefacts, as
4258         * f.i. not showing the lines at all or showing normal table background in
4259         * selected state where the lines should have been.
4260         * 
4261         * @see #setShowGrid(boolean, boolean)
4262         */
4263        @Override
4264        public void setShowGrid(boolean showGrid) {
4265            super.setShowGrid(showGrid);
4266        }
4267    
4268        /**
4269         * {@inheritDoc}
4270         * <p>
4271         * Overriden to mark the request as client-code induced.
4272         * 
4273         * @see #isXTableRowHeightSet
4274         */
4275        @Override
4276        public void setRowHeight(int rowHeight) {
4277            super.setRowHeight(rowHeight);
4278            if (rowHeight > 0) {
4279                isXTableRowHeightSet = true;
4280            }
4281        }
4282    
4283        /**
4284         * Sets enablement of individual rowHeight support. Enabling the support
4285         * involves reflective access to super's private field rowModel which may
4286         * fail due to security issues. If failing the support is not enabled.
4287         * <p>
4288         * The default value is <code>false</code>.
4289         * 
4290         * @param enabled a boolean to indicate whether per-row heights should be
4291         *        enabled.
4292         * @see #isRowHeightEnabled()
4293         * @see #setRowHeight(int, int)
4294         * 
4295         * @deprecated no longer necessary (switched to 1.6)
4296         */
4297        @Deprecated
4298        public void setRowHeightEnabled(boolean enabled) {
4299        }
4300    
4301        /**
4302         * Returns a boolean to indicate whether individual row height is enabled.
4303         * 
4304         * @return a boolean to indicate whether individual row height support is
4305         *         enabled, always true
4306         * @see #setRowHeightEnabled(boolean)
4307         * 
4308         * @deprecated no longer necessary (switched to 1.6)
4309         */
4310        @Deprecated
4311        public boolean isRowHeightEnabled() {
4312            return true;
4313        }
4314    
4315        /**
4316         * Sets the rowHeight for all rows to the given value. Keeps the flag
4317         * <code>isXTableRowHeight</code> unchanged. This enables the distinction
4318         * between setting the height for internal reasons from doing so by client
4319         * code.
4320         * 
4321         * @param rowHeight new height in pixel.
4322         * @see #setRowHeight(int)
4323         * @see #isXTableRowHeightSet
4324         */
4325        protected void adminSetRowHeight(int rowHeight) {
4326            boolean heightSet = isXTableRowHeightSet;
4327            setRowHeight(rowHeight);
4328            isXTableRowHeightSet = heightSet;
4329        }
4330    
4331        // ---------------------------- overriding super factory methods and buggy
4332        /**
4333         * {@inheritDoc}
4334         * <p>
4335         * Overridden to work around core Bug (ID #6291631): negative y is mapped to
4336         * row 0).
4337         * 
4338         */
4339        @Override
4340        public int rowAtPoint(Point point) {
4341            if (point.y < 0)
4342                return -1;
4343            return super.rowAtPoint(point);
4344        }
4345    
4346        /**
4347         * 
4348         * {@inheritDoc}
4349         * <p>
4350         * 
4351         * Overridden to return a <code>JXTableHeader</code>.
4352         * 
4353         * @see JXTableHeader
4354         */
4355        @Override
4356        protected JTableHeader createDefaultTableHeader() {
4357            return new JXTableHeader(columnModel);
4358        }
4359    
4360        /**
4361         * 
4362         * {@inheritDoc}
4363         * <p>
4364         * 
4365         * Overridden to return a <code>DefaultTableColumnModelExt</code>.
4366         * 
4367         * @see org.jdesktop.swingx.table.DefaultTableColumnModelExt
4368         */
4369        @Override
4370        protected TableColumnModel createDefaultColumnModel() {
4371            return new DefaultTableColumnModelExt();
4372        }
4373    
4374        /**
4375         * {@inheritDoc}
4376         * <p>
4377         * Overridden because super throws NPE on null param.
4378         */
4379        @Override
4380        public void setSelectionBackground(Color selectionBackground) {
4381            Color old = getSelectionBackground();
4382            this.selectionBackground = selectionBackground;
4383            firePropertyChange("selectionBackground", old, getSelectionBackground());
4384            repaint();
4385            // super.setSelectionBackground(selectionBackground);
4386        }
4387    
4388        /**
4389         * {@inheritDoc}
4390         * <p>
4391         * Overridden because super throws NPE on null param.
4392         */
4393        @Override
4394        public void setSelectionForeground(Color selectionForeground) {
4395            Color old = getSelectionForeground();
4396            this.selectionForeground = selectionForeground;
4397            firePropertyChange("selectionForeground", old, getSelectionForeground());
4398            repaint();
4399        }
4400    
4401        /**
4402         * {@inheritDoc}
4403         * <p>
4404         * Overridden because super throws NPE on null param.
4405         */
4406        @Override
4407        public void setGridColor(Color gridColor) {
4408            Color old = getGridColor();
4409            this.gridColor = gridColor;
4410            firePropertyChange("gridColor", old, getGridColor());
4411            repaint();
4412        }
4413    
4414    }