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