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