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