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