001 /* 002 * $Id: JXTree.java 3401 2009-07-22 18:09:08Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 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.KeyboardFocusManager; 028 import java.awt.Window; 029 import java.awt.event.ActionEvent; 030 import java.beans.PropertyChangeEvent; 031 import java.beans.PropertyChangeListener; 032 import java.util.Hashtable; 033 import java.util.Vector; 034 import java.util.logging.Logger; 035 036 import javax.swing.Action; 037 import javax.swing.ActionMap; 038 import javax.swing.CellEditor; 039 import javax.swing.Icon; 040 import javax.swing.JComponent; 041 import javax.swing.JPopupMenu; 042 import javax.swing.JTree; 043 import javax.swing.KeyStroke; 044 import javax.swing.SwingUtilities; 045 import javax.swing.UIManager; 046 import javax.swing.event.CellEditorListener; 047 import javax.swing.event.ChangeEvent; 048 import javax.swing.event.ChangeListener; 049 import javax.swing.event.TreeModelEvent; 050 import javax.swing.event.TreeModelListener; 051 import javax.swing.plaf.basic.BasicTreeUI; 052 import javax.swing.tree.DefaultTreeCellRenderer; 053 import javax.swing.tree.TreeCellRenderer; 054 import javax.swing.tree.TreeModel; 055 import javax.swing.tree.TreeNode; 056 import javax.swing.tree.TreePath; 057 058 import org.jdesktop.swingx.decorator.ComponentAdapter; 059 import org.jdesktop.swingx.decorator.CompoundHighlighter; 060 import org.jdesktop.swingx.decorator.Highlighter; 061 import org.jdesktop.swingx.decorator.UIDependent; 062 import org.jdesktop.swingx.renderer.StringValue; 063 import org.jdesktop.swingx.renderer.StringValues; 064 import org.jdesktop.swingx.rollover.RolloverProducer; 065 import org.jdesktop.swingx.rollover.RolloverRenderer; 066 import org.jdesktop.swingx.rollover.TreeRolloverController; 067 import org.jdesktop.swingx.rollover.TreeRolloverProducer; 068 import org.jdesktop.swingx.search.SearchFactory; 069 import org.jdesktop.swingx.search.Searchable; 070 import org.jdesktop.swingx.search.TreeSearchable; 071 import org.jdesktop.swingx.tree.DefaultXTreeCellEditor; 072 import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer; 073 074 075 /** 076 * Enhanced Tree component with support for SwingX rendering, highlighting, 077 * rollover and search functionality. 078 * <p> 079 * 080 * <h2>Rendering and Highlighting</h2> 081 * 082 * As all SwingX collection views, a JXTree is a HighlighterClient (PENDING JW: 083 * formally define and implement, like in AbstractTestHighlighter), that is it 084 * provides consistent api to add and remove Highlighters which can visually 085 * decorate the rendering component. 086 * <p> 087 * 088 * <pre><code> 089 * 090 * JXTree tree = new JXTree(new FileSystemModel()); 091 * // use system file icons and name to render 092 * tree.setCellRenderer(new DefaultTreeRenderer(IconValues.FILE_ICON, 093 * StringValues.FILE_NAME)); 094 * // highlight condition: file modified after a date 095 * HighlightPredicate predicate = new HighlightPredicate() { 096 * public boolean isHighlighted(Component renderer, 097 * ComponentAdapter adapter) { 098 * File file = getUserObject(adapter.getValue()); 099 * return file != null ? lastWeek < file.lastModified : false; 100 * } 101 * }; 102 * // highlight with foreground color 103 * tree.addHighlighter(new ColorHighlighter(predicate, null, Color.RED); 104 * 105 * </code></pre> 106 * 107 * <i>Note:</i> for full functionality, a DefaultTreeRenderer must be installed 108 * as TreeCellRenderer. This is not done by default, because there are 109 * unresolved issues when editing. PENDING JW: still? Check! 110 * 111 * <i>Note:</i> to support the highlighting this implementation wraps the 112 * TreeCellRenderer set by client code with a DelegatingRenderer which applies 113 * the Highlighter after delegating the default configuration to the wrappee. As 114 * a side-effect, getCellRenderer does return the wrapper instead of the custom 115 * renderer. To access the latter, client code must call getWrappedCellRenderer. 116 * <p> 117 * <h2>Rollover</h2> 118 * 119 * As all SwingX collection views, a JXTree supports per-cell rollover. If 120 * enabled, the component fires rollover events on enter/exit of a cell which by 121 * default is promoted to the renderer if it implements RolloverRenderer, that 122 * is simulates live behaviour. The rollover events can be used by client code 123 * as well, f.i. to decorate the rollover row using a Highlighter. 124 * 125 * <pre><code> 126 * 127 * JXTree tree = new JXTree(); 128 * tree.setRolloverEnabled(true); 129 * tree.setCellRenderer(new DefaultTreeRenderer()); 130 * tree.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, 131 * null, Color.RED); 132 * 133 * </code></pre> 134 * 135 * 136 * <h2>Search</h2> 137 * 138 * As all SwingX collection views, a JXTree is searchable. A search action is 139 * registered in its ActionMap under the key "find". The default behaviour is to 140 * ask the SearchFactory to open a search component on this component. The 141 * default keybinding is retrieved from the SearchFactory, typically ctrl-f (or 142 * cmd-f for Mac). Client code can register custom actions and/or bindings as 143 * appropriate. 144 * <p> 145 * 146 * JXTree provides api to vend a renderer-controlled String representation of 147 * cell content. This allows the Searchable and Highlighters to use WYSIWYM 148 * (What-You-See-Is-What-You-Match), that is pattern matching against the actual 149 * string as seen by the user. 150 * 151 * <h2>Miscellaneous</h2> 152 * 153 * <ul> 154 * <li> Improved usability for editing: guarantees that the tree is the 155 * focusOwner if editing terminated by user gesture and guards against data 156 * corruption if focusLost while editing 157 * <li> Access methods for selection colors, for consistency with JXTable, 158 * JXList 159 * <li> Convenience methods and actions to expand, collapse all nodes 160 * </ul> 161 * 162 * @author Ramesh Gupta 163 * @author Jeanette Winzenburg 164 * 165 * @see org.jdesktop.swingx.renderer.DefaultTreeRenderer 166 * @see org.jdesktop.swingx.renderer.ComponentProvider 167 * @see org.jdesktop.swingx.decorator.Highlighter 168 * @see org.jdesktop.swingx.decorator.HighlightPredicate 169 * @see org.jdesktop.swingx.search.SearchFactory 170 * @see org.jdesktop.swingx.search.Searchable 171 * 172 */ 173 public class JXTree extends JTree { 174 @SuppressWarnings("unused") 175 private static final Logger LOG = Logger.getLogger(JXTree.class.getName()); 176 177 178 /** Empty int array used in getSelectedRows(). */ 179 private static final int[] EMPTY_INT_ARRAY = new int[0]; 180 /** Empty TreePath used in getSelectedPath() if selection empty. */ 181 private static final TreePath[] EMPTY_TREEPATH_ARRAY = new TreePath[0]; 182 183 /** Collection of active Highlighters. */ 184 protected CompoundHighlighter compoundHighlighter; 185 /** Listener to changes of Highlighters in collection. */ 186 private ChangeListener highlighterChangeListener; 187 188 /** Wrapper around the installed renderer, needed to support Highlighters. */ 189 private DelegatingRenderer delegatingRenderer; 190 191 /** 192 * The RolloverProducer used if rollover is enabled. 193 */ 194 private RolloverProducer rolloverProducer; 195 196 /** 197 * The RolloverController used if rollover is enabled. 198 */ 199 private TreeRolloverController<JXTree> linkController; 200 201 private boolean overwriteIcons; 202 private Searchable searchable; 203 204 // hacks around core focus issues around editing. 205 /** 206 * The propertyChangeListener responsible for terminating 207 * edits if focus lost. 208 */ 209 private CellEditorRemover editorRemover; 210 /** 211 * The CellEditorListener responsible to force the 212 * focus back to the tree after terminating edits. 213 */ 214 private CellEditorListener editorListener; 215 216 /** Color of selected foreground. Added for consistent api across collection components. */ 217 private Color selectionForeground; 218 /** Color of selected background. Added for consistent api across collection components. */ 219 private Color selectionBackground; 220 221 222 223 /** 224 * Constructs a <code>JXTree</code> with a sample model. The default model 225 * used by this tree defines a leaf node as any node without children. 226 */ 227 public JXTree() { 228 init(); 229 } 230 231 /** 232 * Constructs a <code>JXTree</code> with each element of the specified array 233 * as the child of a new root node which is not displayed. By default, this 234 * tree defines a leaf node as any node without children. 235 * 236 * This version of the constructor simply invokes the super class version 237 * with the same arguments. 238 * 239 * @param value an array of objects that are children of the root. 240 */ 241 public JXTree(Object[] value) { 242 super(value); 243 init(); 244 } 245 246 /** 247 * Constructs a <code>JXTree</code> with each element of the specified 248 * Vector as the child of a new root node which is not displayed. 249 * By default, this tree defines a leaf node as any node without children. 250 * 251 * This version of the constructor simply invokes the super class version 252 * with the same arguments. 253 * 254 * @param value an Vector of objects that are children of the root. 255 */ 256 public JXTree(Vector value) { 257 super(value); 258 init(); 259 } 260 261 /** 262 * Constructs a <code>JXTree</code> created from a Hashtable which does not 263 * display with root. Each value-half of the key/value pairs in the HashTable 264 * becomes a child of the new root node. By default, the tree defines a leaf 265 * node as any node without children. 266 * 267 * This version of the constructor simply invokes the super class version 268 * with the same arguments. 269 * 270 * @param value a Hashtable containing objects that are children of the root. 271 */ 272 public JXTree(Hashtable value) { 273 super(value); 274 init(); 275 } 276 277 /** 278 * Constructs a <code>JXTree</code> with the specified TreeNode as its root, 279 * which displays the root node. By default, the tree defines a leaf node as 280 * any node without children. 281 * 282 * This version of the constructor simply invokes the super class version 283 * with the same arguments. 284 * 285 * @param root root node of this tree 286 */ 287 public JXTree(TreeNode root) { 288 super(root, false); 289 init(); 290 } 291 292 /** 293 * Constructs a <code>JXTree</code> with the specified TreeNode as its root, 294 * which displays the root node and which decides whether a node is a leaf 295 * node in the specified manner. 296 * 297 * This version of the constructor simply invokes the super class version 298 * with the same arguments. 299 * 300 * @param root root node of this tree 301 * @param asksAllowsChildren if true, only nodes that do not allow children 302 * are leaf nodes; otherwise, any node without children is a leaf node; 303 * @see javax.swing.tree.DefaultTreeModel#asksAllowsChildren 304 */ 305 public JXTree(TreeNode root, boolean asksAllowsChildren) { 306 super(root, asksAllowsChildren); 307 init(); 308 } 309 310 /** 311 * Constructs an instance of <code>JXTree</code> which displays the root 312 * node -- the tree is created using the specified data model. 313 * 314 * This version of the constructor simply invokes the super class version 315 * with the same arguments. 316 * 317 * @param newModel 318 * the <code>TreeModel</code> to use as the data model 319 */ 320 public JXTree(TreeModel newModel) { 321 super(newModel); 322 init(); 323 } 324 325 /** 326 * Instantiats JXTree state which is new compared to super. Installs the 327 * Delegating renderer and editor, registers actions and keybindings. 328 * 329 * This must be called from each constructor. 330 */ 331 private void init() { 332 // Issue #1061-swingx: renderer inconsistencies 333 // force setting of renderer 334 setCellRenderer(createDefaultCellRenderer()); 335 // Issue #233-swingx: default editor not bidi-compliant 336 // manually install an enhanced TreeCellEditor which 337 // behaves slightly better in RtoL orientation. 338 // Issue #231-swingx: icons lost 339 // Anyway, need to install the editor manually because 340 // the default install in BasicTreeUI doesn't know about 341 // the DelegatingRenderer and therefore can't see 342 // the DefaultTreeCellRenderer type to delegate to. 343 // As a consequence, the icons are lost in the default 344 // setup. 345 // JW PENDING need to mimic ui-delegate default re-set? 346 // JW PENDING alternatively, cleanup and use DefaultXXTreeCellEditor in incubator 347 if (getWrappedCellRenderer() instanceof DefaultTreeCellRenderer) { 348 setCellEditor(new DefaultXTreeCellEditor(this, (DefaultTreeCellRenderer) getWrappedCellRenderer())); 349 } 350 // Register the actions that this class can handle. 351 ActionMap map = getActionMap(); 352 map.put("expand-all", new Actions("expand-all")); 353 map.put("collapse-all", new Actions("collapse-all")); 354 map.put("find", createFindAction()); 355 356 KeyStroke findStroke = SearchFactory.getInstance().getSearchAccelerator(); 357 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find"); 358 } 359 360 /** 361 * Listens to the model and updates the {@code expandedState} accordingly 362 * when nodes are removed, or changed. 363 * <p> 364 * This class will expand an invisible root when a child has been added to 365 * it. 366 * 367 * @author Karl George Schaefer 368 */ 369 protected class XTreeModelHandler extends TreeModelHandler { 370 /** 371 * {@inheritDoc} 372 */ 373 @Override 374 public void treeNodesInserted(TreeModelEvent e) { 375 TreePath path = e.getTreePath(); 376 377 //fixes SwingX bug #612 378 if (path.getParentPath() == null && !isRootVisible() && isCollapsed(path)) { 379 //should this be wrapped in SwingUtilities.invokeLater? 380 expandPath(path); 381 } 382 383 super.treeNodesInserted(e); 384 } 385 } 386 387 /** 388 * {@inheritDoc} 389 */ 390 @Override 391 protected TreeModelListener createTreeModelListener() { 392 return new XTreeModelHandler(); 393 } 394 395 /** 396 * A small class which dispatches actions. 397 * TODO: Is there a way that we can make this static? 398 */ 399 private class Actions extends UIAction { 400 Actions(String name) { 401 super(name); 402 } 403 404 public void actionPerformed(ActionEvent evt) { 405 if ("expand-all".equals(getName())) { 406 expandAll(); 407 } 408 else if ("collapse-all".equals(getName())) { 409 collapseAll(); 410 } 411 } 412 } 413 414 415 //-------------------- search support 416 417 /** 418 * Creates and returns the action to invoke on a find request. 419 * 420 * @return the action to invoke on a find request. 421 */ 422 private Action createFindAction() { 423 return new UIAction("find") { 424 public void actionPerformed(ActionEvent e) { 425 doFind(); 426 } 427 }; 428 } 429 430 /** 431 * Starts a search on this Tree's visible nodes. This implementation asks the 432 * SearchFactory to open a find widget on itself. 433 */ 434 protected void doFind() { 435 SearchFactory.getInstance().showFindInput(this, getSearchable()); 436 } 437 438 /** 439 * Returns a Searchable for this component, guaranteed to be not null. This 440 * implementation lazily creates a TreeSearchable if necessary. 441 * 442 * 443 * @return a not-null Searchable for this component. 444 * 445 * @see #setSearchable(Searchable) 446 * @see org.jdesktop.swingx.search.TreeSearchable 447 */ 448 public Searchable getSearchable() { 449 if (searchable == null) { 450 searchable = new TreeSearchable(this); 451 } 452 return searchable; 453 } 454 455 /** 456 * Sets the Searchable for this component. If null, a default 457 * Searchable will be created and used. 458 * 459 * @param searchable the Searchable to use for this component, may be null to 460 * indicate using the default. 461 * 462 * @see #getSearchable() 463 */ 464 public void setSearchable(Searchable searchable) { 465 this.searchable = searchable; 466 } 467 468 /** 469 * Returns the string representation of the cell value at the given position. 470 * 471 * @param row the row index of the cell in view coordinates 472 * @return the string representation of the cell value as it will appear in the 473 * table. 474 */ 475 public String getStringAt(int row) { 476 return getStringAt(getPathForRow(row)); 477 } 478 479 /** 480 * Returns the string representation of the cell value at the given position. 481 * 482 * @param path the TreePath representing the node. 483 * @return the string representation of the cell value as it will appear in the 484 * table, or null if the path is not visible. 485 */ 486 public String getStringAt(TreePath path) { 487 if (path == null) return null; 488 TreeCellRenderer renderer = getDelegatingRenderer().getDelegateRenderer(); 489 if (renderer instanceof StringValue) { 490 return ((StringValue) renderer).getString(path.getLastPathComponent()); 491 } 492 return StringValues.TO_STRING.getString(path.getLastPathComponent()); 493 } 494 495 496 //--------------------- misc. new api and super overrides 497 498 /** 499 * Collapses all nodes in this tree. 500 */ 501 public void collapseAll() { 502 for (int i = getRowCount() - 1; i >= 0 ; i--) { 503 collapseRow(i); 504 } 505 } 506 507 /** 508 * Expands all nodes in this tree. 509 */ 510 public void expandAll() { 511 if (getRowCount() == 0) { 512 expandRoot(); 513 } 514 for (int i = 0; i < getRowCount(); i++) { 515 expandRow(i); 516 } 517 } 518 519 /** 520 * Expands the root path if a TreeModel has been set, does nothing if not. 521 * 522 */ 523 private void expandRoot() { 524 TreeModel model = getModel(); 525 if (model != null && model.getRoot() != null) { 526 expandPath(new TreePath(model.getRoot())); 527 } 528 } 529 530 /** 531 * {@inheritDoc} 532 * <p> 533 * 534 * Overridden to always return a not-null array (following SwingX 535 * convention). 536 */ 537 @Override 538 public int[] getSelectionRows() { 539 int[] rows = super.getSelectionRows(); 540 return rows != null ? rows : EMPTY_INT_ARRAY; 541 } 542 543 /** 544 * {@inheritDoc} 545 * <p> 546 * 547 * Overridden to always return a not-null array (following SwingX 548 * convention). 549 */ 550 @Override 551 public TreePath[] getSelectionPaths() { 552 TreePath[] paths = super.getSelectionPaths(); 553 return paths != null ? paths : EMPTY_TREEPATH_ARRAY; 554 } 555 556 /** 557 * Returns the background color for selected cells. 558 * 559 * @return the <code>Color</code> used for the background of 560 * selected list items 561 * @see #setSelectionBackground 562 * @see #setSelectionForeground 563 */ 564 public Color getSelectionBackground() { 565 return selectionBackground; 566 } 567 568 /** 569 * Returns the selection foreground color. 570 * 571 * @return the <code>Color</code> object for the foreground property 572 * @see #setSelectionForeground 573 * @see #setSelectionBackground 574 */ 575 public Color getSelectionForeground() { 576 return selectionForeground; 577 } 578 579 /** 580 * Sets the foreground color for selected cells. Cell renderers 581 * can use this color to render text and graphics for selected 582 * cells. 583 * <p> 584 * The default value of this property is defined by the look 585 * and feel implementation. 586 * <p> 587 * This is a JavaBeans bound property. 588 * 589 * @param selectionForeground the <code>Color</code> to use in the foreground 590 * for selected list items 591 * @see #getSelectionForeground 592 * @see #setSelectionBackground 593 * @see #setForeground 594 * @see #setBackground 595 * @see #setFont 596 * @beaninfo 597 * bound: true 598 * attribute: visualUpdate true 599 * description: The foreground color of selected cells. 600 */ 601 public void setSelectionForeground(Color selectionForeground) { 602 Object oldValue = getSelectionForeground(); 603 this.selectionForeground = selectionForeground; 604 firePropertyChange("selectionForeground", oldValue, getSelectionForeground()); 605 repaint(); 606 } 607 608 /** 609 * Sets the background color for selected cells. Cell renderers 610 * can use this color to the fill selected cells. 611 * <p> 612 * The default value of this property is defined by the look 613 * and feel implementation. 614 * <p> 615 * This is a JavaBeans bound property. 616 * 617 * @param selectionBackground the <code>Color</code> to use for the 618 * background of selected cells 619 * @see #getSelectionBackground 620 * @see #setSelectionForeground 621 * @see #setForeground 622 * @see #setBackground 623 * @see #setFont 624 * @beaninfo 625 * bound: true 626 * attribute: visualUpdate true 627 * description: The background color of selected cells. 628 */ 629 public void setSelectionBackground(Color selectionBackground) { 630 Object oldValue = getSelectionBackground(); 631 this.selectionBackground = selectionBackground; 632 firePropertyChange("selectionBackground", oldValue, getSelectionBackground()); 633 repaint(); 634 } 635 636 637 //------------------------- update ui 638 639 /** 640 * {@inheritDoc} <p> 641 * 642 * Overridden to update selection background/foreground. Mimicking behaviour of 643 * ui-delegates for JTable, JList. 644 */ 645 @Override 646 public void updateUI() { 647 uninstallSelectionColors(); 648 super.updateUI(); 649 installSelectionColors(); 650 updateHighlighterUI(); 651 updateRendererEditorUI(); 652 } 653 654 655 /** 656 * Quick fix for #1060-swingx: icons lost on toggling LAF 657 */ 658 protected void updateRendererEditorUI() { 659 if (getCellEditor() instanceof UIDependent) { 660 ((UIDependent) getCellEditor()).updateUI(); 661 } 662 // PENDING JW: here we get the DelegationRenderer which is not (yet) UIDependent 663 // need to think about how to handle the per-tree icons 664 // anyway, the "real" renderer usually is updated accidentally 665 // don't know exactly why, added to the comp hierarchy? 666 // if (getCellRenderer() instanceof UIDependent) { 667 // ((UIDependent) getCellRenderer()).updateUI(); 668 // } 669 } 670 671 /** 672 * Installs selection colors from UIManager. <p> 673 * 674 * <b>Note:</b> this should be done in the UI delegate. 675 */ 676 private void installSelectionColors() { 677 if (SwingXUtilities.isUIInstallable(getSelectionBackground())) { 678 setSelectionBackground(UIManager.getColor("Tree.selectionBackground")); 679 } 680 if (SwingXUtilities.isUIInstallable(getSelectionForeground())) { 681 setSelectionForeground(UIManager.getColor("Tree.selectionForeground")); 682 } 683 684 } 685 686 /** 687 * Uninstalls selection colors. <p> 688 * 689 * <b>Note:</b> this should be done in the UI delegate. 690 */ 691 private void uninstallSelectionColors() { 692 if (SwingXUtilities.isUIInstallable(getSelectionBackground())) { 693 setSelectionBackground(null); 694 } 695 if (SwingXUtilities.isUIInstallable(getSelectionForeground())) { 696 setSelectionForeground(null); 697 } 698 } 699 700 /** 701 * Updates highlighter after <code>updateUI</code> changes. 702 * 703 * @see org.jdesktop.swingx.decorator.UIDependent 704 */ 705 protected void updateHighlighterUI() { 706 if (compoundHighlighter == null) return; 707 compoundHighlighter.updateUI(); 708 } 709 710 711 712 //------------------------ Rollover support 713 714 /** 715 * Sets the property to enable/disable rollover support. If enabled, the list 716 * fires property changes on per-cell mouse rollover state, i.e. 717 * when the mouse enters/leaves a list cell. <p> 718 * 719 * This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell 720 * rendered by a JXHyperlink.<p> 721 * 722 * The default value is false. 723 * 724 * @param rolloverEnabled a boolean indicating whether or not the rollover 725 * functionality should be enabled. 726 * 727 * @see #isRolloverEnabled() 728 * @see #getLinkController() 729 * @see #createRolloverProducer() 730 * @see org.jdesktop.swingx.rollover.RolloverRenderer 731 */ 732 public void setRolloverEnabled(boolean rolloverEnabled) { 733 boolean old = isRolloverEnabled(); 734 if (rolloverEnabled == old) return; 735 if (rolloverEnabled) { 736 rolloverProducer = createRolloverProducer(); 737 addMouseListener(rolloverProducer); 738 addMouseMotionListener(rolloverProducer); 739 getLinkController().install(this); 740 } else { 741 removeMouseListener(rolloverProducer); 742 removeMouseMotionListener(rolloverProducer); 743 rolloverProducer = null; 744 getLinkController().release(); 745 } 746 firePropertyChange("rolloverEnabled", old, isRolloverEnabled()); 747 } 748 749 /** 750 * Returns a boolean indicating whether or not rollover support is enabled. 751 * 752 * @return a boolean indicating whether or not rollover support is enabled. 753 * 754 * @see #setRolloverEnabled(boolean) 755 */ 756 public boolean isRolloverEnabled() { 757 return rolloverProducer != null; 758 } 759 760 /** 761 * Returns the RolloverController for this component. Lazyly creates the 762 * controller if necessary, that is the return value is guaranteed to be 763 * not null. <p> 764 * 765 * PENDING JW: rename to getRolloverController 766 * 767 * @return the RolloverController for this tree, guaranteed to be not null. 768 * 769 * @see #setRolloverEnabled(boolean) 770 * @see #createLinkController() 771 * @see org.jdesktop.swingx.rollover.RolloverController 772 */ 773 protected TreeRolloverController<JXTree> getLinkController() { 774 if (linkController == null) { 775 linkController = createLinkController(); 776 } 777 return linkController; 778 } 779 780 /** 781 * Creates and returns a RolloverController appropriate for this tree. 782 * 783 * @return a RolloverController appropriate for this tree. 784 * 785 * @see #getLinkController() 786 * @see org.jdesktop.swingx.rollover.RolloverController 787 */ 788 protected TreeRolloverController<JXTree> createLinkController() { 789 return new TreeRolloverController<JXTree>(); 790 } 791 792 /** 793 * Creates and returns the RolloverProducer to use with this tree. 794 * <p> 795 * 796 * @return <code>RolloverProducer</code> to use with this tree 797 * 798 * @see #setRolloverEnabled(boolean) 799 */ 800 protected RolloverProducer createRolloverProducer() { 801 return new TreeRolloverProducer(); 802 } 803 804 805 //----------------------- Highlighter api 806 807 /** 808 * Sets the <code>Highlighter</code>s to the table, replacing any old settings. 809 * None of the given Highlighters must be null.<p> 810 * 811 * This is a bound property. <p> 812 * 813 * Note: as of version #1.257 the null constraint is enforced strictly. To remove 814 * all highlighters use this method without param. 815 * 816 * @param highlighters zero or more not null highlighters to use for renderer decoration. 817 * @throws NullPointerException if array is null or array contains null values. 818 * 819 * @see #getHighlighters() 820 * @see #addHighlighter(Highlighter) 821 * @see #removeHighlighter(Highlighter) 822 * 823 */ 824 public void setHighlighters(Highlighter... highlighters) { 825 Highlighter[] old = getHighlighters(); 826 getCompoundHighlighter().setHighlighters(highlighters); 827 firePropertyChange("highlighters", old, getHighlighters()); 828 } 829 830 /** 831 * Returns the <code>Highlighter</code>s used by this table. 832 * Maybe empty, but guarantees to be never null. 833 * 834 * @return the Highlighters used by this table, guaranteed to never null. 835 * @see #setHighlighters(Highlighter[]) 836 */ 837 public Highlighter[] getHighlighters() { 838 return getCompoundHighlighter().getHighlighters(); 839 } 840 841 /** 842 * Appends a <code>Highlighter</code> to the end of the list of used 843 * <code>Highlighter</code>s. The argument must not be null. 844 * <p> 845 * 846 * @param highlighter the <code>Highlighter</code> to add, must not be null. 847 * @throws NullPointerException if <code>Highlighter</code> is null. 848 * 849 * @see #removeHighlighter(Highlighter) 850 * @see #setHighlighters(Highlighter[]) 851 */ 852 public void addHighlighter(Highlighter highlighter) { 853 Highlighter[] old = getHighlighters(); 854 getCompoundHighlighter().addHighlighter(highlighter); 855 firePropertyChange("highlighters", old, getHighlighters()); 856 } 857 858 /** 859 * Removes the given Highlighter. <p> 860 * 861 * Does nothing if the Highlighter is not contained. 862 * 863 * @param highlighter the Highlighter to remove. 864 * @see #addHighlighter(Highlighter) 865 * @see #setHighlighters(Highlighter...) 866 */ 867 public void removeHighlighter(Highlighter highlighter) { 868 Highlighter[] old = getHighlighters(); 869 getCompoundHighlighter().removeHighlighter(highlighter); 870 firePropertyChange("highlighters", old, getHighlighters()); 871 } 872 873 /** 874 * Returns the CompoundHighlighter assigned to the table, null if none. 875 * PENDING: open up for subclasses again?. 876 * 877 * @return the CompoundHighlighter assigned to the table. 878 */ 879 protected CompoundHighlighter getCompoundHighlighter() { 880 if (compoundHighlighter == null) { 881 compoundHighlighter = new CompoundHighlighter(); 882 compoundHighlighter.addChangeListener(getHighlighterChangeListener()); 883 } 884 return compoundHighlighter; 885 } 886 887 /** 888 * Returns the <code>ChangeListener</code> to use with highlighters. Lazily 889 * creates the listener. 890 * 891 * @return the ChangeListener for observing changes of highlighters, 892 * guaranteed to be <code>not-null</code> 893 */ 894 protected ChangeListener getHighlighterChangeListener() { 895 if (highlighterChangeListener == null) { 896 highlighterChangeListener = createHighlighterChangeListener(); 897 } 898 return highlighterChangeListener; 899 } 900 901 /** 902 * Creates and returns the ChangeListener observing Highlighters. 903 * <p> 904 * Here: repaints the table on receiving a stateChanged. 905 * 906 * @return the ChangeListener defining the reaction to changes of 907 * highlighters. 908 */ 909 protected ChangeListener createHighlighterChangeListener() { 910 return new ChangeListener() { 911 public void stateChanged(ChangeEvent e) { 912 repaint(); 913 } 914 }; 915 } 916 917 /** 918 * Sets the Icon to use for the handle of an expanded node.<p> 919 * 920 * Note: this will only succeed if the current ui delegate is 921 * a BasicTreeUI otherwise it will do nothing.<p> 922 * 923 * PENDING JW: incomplete api (no getter) and not a bound property. 924 * 925 * @param expandedIcon the Icon to use for the handle of an expanded node. 926 */ 927 public void setExpandedIcon(Icon expandedIcon) { 928 if (getUI() instanceof BasicTreeUI) { 929 ((BasicTreeUI) getUI()).setExpandedIcon(expandedIcon); 930 } 931 } 932 933 /** 934 * Sets the Icon to use for the handle of a collapsed node. 935 * 936 * Note: this will only succeed if the current ui delegate is 937 * a BasicTreeUI otherwise it will do nothing. 938 * 939 * PENDING JW: incomplete api (no getter) and not a bound property. 940 * 941 * @param collapsedIcon the Icon to use for the handle of a collapsed node. 942 */ 943 public void setCollapsedIcon(Icon collapsedIcon) { 944 if (getUI() instanceof BasicTreeUI) { 945 ((BasicTreeUI) getUI()).setCollapsedIcon(collapsedIcon); 946 } 947 } 948 949 /** 950 * Sets the Icon to use for a leaf node.<p> 951 * 952 * Note: this will only succeed if current renderer is a 953 * DefaultTreeCellRenderer.<p> 954 * 955 * PENDING JW: this (all setXXIcon) is old api pulled up from the JXTreeTable. 956 * Need to review if we really want it - problematic if sharing the same 957 * renderer instance across different trees. 958 * 959 * PENDING JW: incomplete api (no getter) and not a bound property.<p> 960 * 961 * @param leafIcon the Icon to use for a leaf node. 962 */ 963 public void setLeafIcon(Icon leafIcon) { 964 getDelegatingRenderer().setLeafIcon(leafIcon); 965 } 966 967 /** 968 * Sets the Icon to use for an open folder node. 969 * 970 * Note: this will only succeed if current renderer is a 971 * DefaultTreeCellRenderer. 972 * 973 * PENDING JW: incomplete api (no getter) and not a bound property. 974 * 975 * @param openIcon the Icon to use for an open folder node. 976 */ 977 public void setOpenIcon(Icon openIcon) { 978 getDelegatingRenderer().setOpenIcon(openIcon); 979 } 980 981 /** 982 * Sets the Icon to use for a closed folder node. 983 * 984 * Note: this will only succeed if current renderer is a 985 * DefaultTreeCellRenderer. 986 * 987 * PENDING JW: incomplete api (no getter) and not a bound property. 988 * 989 * @param closedIcon the Icon to use for a closed folder node. 990 */ 991 public void setClosedIcon(Icon closedIcon) { 992 getDelegatingRenderer().setClosedIcon(closedIcon); 993 } 994 995 /** 996 * Property to control whether per-tree icons should be 997 * copied to the renderer on setCellRenderer. <p> 998 * 999 * The default value is false. 1000 * 1001 * PENDING: should update the current renderer's icons when 1002 * setting to true? 1003 * 1004 * @param overwrite a boolean to indicate if the per-tree Icons should 1005 * be copied to the new renderer on setCellRenderer. 1006 * 1007 * @see #isOverwriteRendererIcons() 1008 * @see #setLeafIcon(Icon) 1009 * @see #setOpenIcon(Icon) 1010 * @see #setClosedIcon(Icon) 1011 */ 1012 public void setOverwriteRendererIcons(boolean overwrite) { 1013 if (overwriteIcons == overwrite) return; 1014 boolean old = overwriteIcons; 1015 this.overwriteIcons = overwrite; 1016 firePropertyChange("overwriteRendererIcons", old, overwrite); 1017 } 1018 1019 /** 1020 * Returns a boolean indicating whether the per-tree icons should be 1021 * copied to the renderer on setCellRenderer. 1022 * 1023 * @return true if a TreeCellRenderer's icons will be overwritten with the 1024 * tree's Icons, false if the renderer's icons will be unchanged. 1025 * 1026 * @see #setOverwriteRendererIcons(boolean) 1027 * @see #setLeafIcon(Icon) 1028 * @see #setOpenIcon(Icon) 1029 * @see #setClosedIcon(Icon) 1030 * 1031 */ 1032 public boolean isOverwriteRendererIcons() { 1033 return overwriteIcons; 1034 } 1035 1036 private DelegatingRenderer getDelegatingRenderer() { 1037 if (delegatingRenderer == null) { 1038 // only called once... to get hold of the default? 1039 delegatingRenderer = new DelegatingRenderer(); 1040 } 1041 return delegatingRenderer; 1042 } 1043 1044 /** 1045 * Creates and returns the default cell renderer to use. Subclasses may 1046 * override to use a different type. 1047 * <p> 1048 * 1049 * This implementation returns a renderer of type 1050 * <code>DefaultTreeCellRenderer</code>. <b>Note:</b> Will be changed to 1051 * return a renderer of type <code>DefaultTreeRenderer</code>, 1052 * once WrappingProvider is reasonably stable. 1053 * 1054 * @return the default cell renderer to use with this tree. 1055 */ 1056 protected TreeCellRenderer createDefaultCellRenderer() { 1057 // return new DefaultTreeCellRenderer(); 1058 return new DefaultXTreeCellRenderer(); 1059 } 1060 1061 /** 1062 * {@inheritDoc} <p> 1063 * 1064 * Overridden to return the delegating renderer which is wrapped around the 1065 * original to support highlighting. The returned renderer is of type 1066 * DelegatingRenderer and guaranteed to not-null<p> 1067 * 1068 * @see #setCellRenderer(TreeCellRenderer) 1069 * @see DelegatingRenderer 1070 */ 1071 @Override 1072 public TreeCellRenderer getCellRenderer() { 1073 // PENDING JW: something wrong here - why exactly can't we return super? 1074 // not even if we force the initial setting in init? 1075 // return super.getCellRenderer(); 1076 return getDelegatingRenderer(); 1077 } 1078 1079 /** 1080 * Returns the renderer installed by client code or the default if none has 1081 * been set. 1082 * 1083 * @return the wrapped renderer. 1084 * @see #setCellRenderer(TreeCellRenderer) 1085 */ 1086 public TreeCellRenderer getWrappedCellRenderer() { 1087 return getDelegatingRenderer().getDelegateRenderer(); 1088 } 1089 1090 /** 1091 * {@inheritDoc} <p> 1092 * 1093 * Overridden to wrap the given renderer in a DelegatingRenderer to support 1094 * highlighting. <p> 1095 * 1096 * Note: the wrapping implies that the renderer returned from the getCellRenderer 1097 * is <b>not</b> the renderer as given here, but the wrapper. To access the original, 1098 * use <code>getWrappedCellRenderer</code>. 1099 * 1100 * @see #getWrappedCellRenderer() 1101 * @see #getCellRenderer() 1102 */ 1103 @Override 1104 public void setCellRenderer(TreeCellRenderer renderer) { 1105 // PENDING: do something against recursive setting 1106 // == multiple delegation... 1107 getDelegatingRenderer().setDelegateRenderer(renderer); 1108 super.setCellRenderer(delegatingRenderer); 1109 // quick hack for #1061: renderer/editor inconsistent 1110 if ((renderer instanceof DefaultTreeCellRenderer) && 1111 (getCellEditor() instanceof DefaultXTreeCellEditor)) { 1112 ((DefaultXTreeCellEditor) getCellEditor()).setRenderer((DefaultTreeCellRenderer) renderer); 1113 } 1114 } 1115 1116 1117 /** 1118 * A decorator for the original TreeCellRenderer. Needed to hook highlighters 1119 * after messaging the delegate.<p> 1120 * 1121 * PENDING JW: formally implement UIDependent? 1122 * PENDING JW: missing updateUI anyway (got lost when c&p from JXList ;-) 1123 * PENDING JW: missing override of updateUI in xtree ... 1124 */ 1125 public class DelegatingRenderer implements TreeCellRenderer, RolloverRenderer { 1126 private Icon closedIcon = null; 1127 private Icon openIcon = null; 1128 private Icon leafIcon = null; 1129 1130 private TreeCellRenderer delegate; 1131 1132 /** 1133 * Instantiates a DelegatingRenderer with tree's default renderer as delegate. 1134 */ 1135 public DelegatingRenderer() { 1136 this(null); 1137 initIcons(new DefaultTreeCellRenderer()); 1138 } 1139 1140 /** 1141 * Instantiates a DelegatingRenderer with the given delegate. If the 1142 * delegate is null, the default is created via the list's factory method. 1143 * 1144 * @param delegate the delegate to use, if null the tree's default is 1145 * created and used. 1146 */ 1147 public DelegatingRenderer(TreeCellRenderer delegate) { 1148 initIcons((DefaultTreeCellRenderer) (delegate instanceof DefaultTreeCellRenderer ? 1149 delegate : new DefaultTreeCellRenderer())); 1150 setDelegateRenderer(delegate); 1151 } 1152 1153 /** 1154 * initially sets the icons to the defaults as given 1155 * by a DefaultTreeCellRenderer. 1156 * 1157 * @param renderer 1158 */ 1159 private void initIcons(DefaultTreeCellRenderer renderer) { 1160 closedIcon = renderer.getDefaultClosedIcon(); 1161 openIcon = renderer.getDefaultOpenIcon(); 1162 leafIcon = renderer.getDefaultLeafIcon(); 1163 } 1164 1165 /** 1166 * Sets the delegate. If the 1167 * delegate is null, the default is created via the list's factory method. 1168 * Updates the folder/leaf icons. 1169 * 1170 * THINK: how to update? always override with this.icons, only 1171 * if renderer's icons are null, update this icons if they are not, 1172 * update all if only one is != null.... ?? 1173 * 1174 * @param delegate the delegate to use, if null the list's default is 1175 * created and used. 1176 */ 1177 public void setDelegateRenderer(TreeCellRenderer delegate) { 1178 if (delegate == null) { 1179 delegate = createDefaultCellRenderer(); 1180 } 1181 this.delegate = delegate; 1182 updateIcons(); 1183 } 1184 1185 /** 1186 * tries to set the renderers icons. Can succeed only if the 1187 * delegate is a DefaultTreeCellRenderer. 1188 * THINK: how to update? always override with this.icons, only 1189 * if renderer's icons are null, update this icons if they are not, 1190 * update all if only one is != null.... ?? 1191 * 1192 */ 1193 private void updateIcons() { 1194 if (!isOverwriteRendererIcons()) return; 1195 setClosedIcon(closedIcon); 1196 setOpenIcon(openIcon); 1197 setLeafIcon(leafIcon); 1198 } 1199 1200 public void setClosedIcon(Icon closedIcon) { 1201 if (delegate instanceof DefaultTreeCellRenderer) { 1202 ((DefaultTreeCellRenderer) delegate).setClosedIcon(closedIcon); 1203 } 1204 this.closedIcon = closedIcon; 1205 } 1206 1207 public void setOpenIcon(Icon openIcon) { 1208 if (delegate instanceof DefaultTreeCellRenderer) { 1209 ((DefaultTreeCellRenderer) delegate).setOpenIcon(openIcon); 1210 } 1211 this.openIcon = openIcon; 1212 } 1213 1214 public void setLeafIcon(Icon leafIcon) { 1215 if (delegate instanceof DefaultTreeCellRenderer) { 1216 ((DefaultTreeCellRenderer) delegate).setLeafIcon(leafIcon); 1217 } 1218 this.leafIcon = leafIcon; 1219 } 1220 1221 //--------------- TreeCellRenderer 1222 1223 /** 1224 * Returns the delegate. 1225 * 1226 * @return the delegate renderer used by this renderer, guaranteed to 1227 * not-null. 1228 */ 1229 public TreeCellRenderer getDelegateRenderer() { 1230 return delegate; 1231 } 1232 1233 /** 1234 * {@inheritDoc} <p> 1235 * 1236 * Overridden to apply the highlighters, if any, after calling the delegate. 1237 * The decorators are not applied if the row is invalid. 1238 */ 1239 public Component getTreeCellRendererComponent(JTree tree, Object value, 1240 boolean selected, boolean expanded, boolean leaf, int row, 1241 boolean hasFocus) { 1242 Component result = delegate.getTreeCellRendererComponent(tree, 1243 value, selected, expanded, leaf, row, hasFocus); 1244 1245 if ((compoundHighlighter != null) && (row < getRowCount()) 1246 && (row >= 0)) { 1247 result = compoundHighlighter.highlight(result, 1248 getComponentAdapter(row)); 1249 } 1250 1251 return result; 1252 } 1253 1254 // ------------------ RolloverRenderer 1255 1256 public boolean isEnabled() { 1257 return (delegate instanceof RolloverRenderer) 1258 && ((RolloverRenderer) delegate).isEnabled(); 1259 } 1260 1261 public void doClick() { 1262 if (isEnabled()) { 1263 ((RolloverRenderer) delegate).doClick(); 1264 } 1265 } 1266 1267 } 1268 1269 1270 //----------------------- edit 1271 1272 /** 1273 * {@inheritDoc} <p> 1274 * Overridden to fix focus issues with editors. 1275 * This method installs and updates the internal CellEditorRemover which 1276 * terminates ongoing edits if appropriate. Additionally, it 1277 * registers a CellEditorListener with the cell editor to grab the 1278 * focus back to tree, if appropriate. 1279 * 1280 * @see #updateEditorRemover() 1281 */ 1282 @Override 1283 public void startEditingAtPath(TreePath path) { 1284 super.startEditingAtPath(path); 1285 if (isEditing()) { 1286 updateEditorListener(); 1287 updateEditorRemover(); 1288 } 1289 } 1290 1291 1292 /** 1293 * Hack to grab focus after editing. 1294 */ 1295 private void updateEditorListener() { 1296 if (editorListener == null) { 1297 editorListener = new CellEditorListener() { 1298 1299 public void editingCanceled(ChangeEvent e) { 1300 terminated(e); 1301 } 1302 1303 /** 1304 * @param e 1305 */ 1306 private void terminated(ChangeEvent e) { 1307 analyseFocus(); 1308 ((CellEditor) e.getSource()).removeCellEditorListener(editorListener); 1309 } 1310 1311 public void editingStopped(ChangeEvent e) { 1312 terminated(e); 1313 } 1314 1315 }; 1316 } 1317 getCellEditor().addCellEditorListener(editorListener); 1318 1319 } 1320 1321 /** 1322 * This is called from cell editor listener if edit terminated. 1323 * Trying to analyse if we should grab the focus back to the 1324 * tree after. Brittle ... we assume we are the first to 1325 * get the event, so we can analyse the hierarchy before the 1326 * editing component is removed. 1327 */ 1328 protected void analyseFocus() { 1329 if (isFocusOwnerDescending()) { 1330 requestFocusInWindow(); 1331 } 1332 } 1333 1334 1335 /** 1336 * Returns a boolean to indicate if the current focus owner 1337 * is descending from this table. 1338 * Returns false if not editing, otherwise walks the focusOwner 1339 * hierarchy, taking popups into account. <p> 1340 * 1341 * PENDING: copied from JXTable ... should be somewhere in a utility 1342 * class? 1343 * 1344 * @return a boolean to indicate if the current focus 1345 * owner is contained. 1346 */ 1347 private boolean isFocusOwnerDescending() { 1348 if (!isEditing()) return false; 1349 Component focusOwner = 1350 KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 1351 // PENDING JW: special casing to not fall through ... really wanted? 1352 if (focusOwner == null) return false; 1353 if (SwingXUtilities.isDescendingFrom(focusOwner, this)) return true; 1354 // same with permanent focus owner 1355 Component permanent = 1356 KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner(); 1357 return SwingXUtilities.isDescendingFrom(permanent, this); 1358 } 1359 1360 1361 1362 /** 1363 * Overridden to release the CellEditorRemover, if any. 1364 */ 1365 @Override 1366 public void removeNotify() { 1367 if (editorRemover != null) { 1368 editorRemover.release(); 1369 editorRemover = null; 1370 } 1371 super.removeNotify(); 1372 } 1373 1374 /** 1375 * Lazily creates and updates the internal CellEditorRemover. 1376 * 1377 * 1378 */ 1379 private void updateEditorRemover() { 1380 if (editorRemover == null) { 1381 editorRemover = new CellEditorRemover(); 1382 } 1383 editorRemover.updateKeyboardFocusManager(); 1384 } 1385 1386 /** This class tracks changes in the keyboard focus state. It is used 1387 * when the JXTree is editing to determine when to terminate the edit. 1388 * If focus switches to a component outside of the JXTree, but in the 1389 * same window, this will terminate editing. The exact terminate 1390 * behaviour is controlled by the invokeStopEditing property. 1391 * 1392 * @see javax.swing.JTree#setInvokesStopCellEditing(boolean) 1393 * 1394 */ 1395 public class CellEditorRemover implements PropertyChangeListener { 1396 /** the focusManager this is listening to. */ 1397 KeyboardFocusManager focusManager; 1398 1399 public CellEditorRemover() { 1400 updateKeyboardFocusManager(); 1401 } 1402 1403 /** 1404 * Updates itself to listen to the current KeyboardFocusManager. 1405 * 1406 */ 1407 public void updateKeyboardFocusManager() { 1408 KeyboardFocusManager current = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 1409 setKeyboardFocusManager(current); 1410 } 1411 1412 /** 1413 * stops listening. 1414 * 1415 */ 1416 public void release() { 1417 setKeyboardFocusManager(null); 1418 } 1419 1420 /** 1421 * Sets the focusManager this is listening to. 1422 * Unregisters/registers itself from/to the old/new manager, 1423 * respectively. 1424 * 1425 * @param current the KeyboardFocusManager to listen too. 1426 */ 1427 private void setKeyboardFocusManager(KeyboardFocusManager current) { 1428 if (focusManager == current) 1429 return; 1430 KeyboardFocusManager old = focusManager; 1431 if (old != null) { 1432 old.removePropertyChangeListener("permanentFocusOwner", this); 1433 } 1434 focusManager = current; 1435 if (focusManager != null) { 1436 focusManager.addPropertyChangeListener("permanentFocusOwner", 1437 this); 1438 } 1439 1440 } 1441 public void propertyChange(PropertyChangeEvent ev) { 1442 if (!isEditing()) { 1443 return; 1444 } 1445 1446 Component c = focusManager.getPermanentFocusOwner(); 1447 JXTree tree = JXTree.this; 1448 while (c != null) { 1449 if (c instanceof JPopupMenu) { 1450 c = ((JPopupMenu) c).getInvoker(); 1451 } else { 1452 1453 if (c == tree) { 1454 // focus remains inside the table 1455 return; 1456 } else if ((c instanceof Window) || 1457 (c instanceof Applet && c.getParent() == null)) { 1458 if (c == SwingUtilities.getRoot(tree)) { 1459 if (tree.getInvokesStopCellEditing()) { 1460 tree.stopEditing(); 1461 } 1462 if (tree.isEditing()) { 1463 tree.cancelEditing(); 1464 } 1465 } 1466 break; 1467 } 1468 c = c.getParent(); 1469 } 1470 } 1471 } 1472 } 1473 1474 // ------------------ oldish String conversion api, no longer recommended 1475 1476 /** 1477 * {@inheritDoc} <p> 1478 * 1479 * Overridden to initialize the String conversion method of the model, if any.<p> 1480 * PENDING JW: remove - that is an outdated approach? 1481 */ 1482 @Override 1483 public void setModel(TreeModel newModel) { 1484 super.setModel(newModel); 1485 } 1486 1487 1488 1489 //------------------------------- ComponentAdapter 1490 /** 1491 * @return the unconfigured ComponentAdapter. 1492 */ 1493 protected ComponentAdapter getComponentAdapter() { 1494 if (dataAdapter == null) { 1495 dataAdapter = new TreeAdapter(this); 1496 } 1497 return dataAdapter; 1498 } 1499 1500 /** 1501 * Convenience to access a configured ComponentAdapter. 1502 * Note: the column index of the configured adapter is always 0. 1503 * 1504 * @param index the row index in view coordinates, must be valid. 1505 * @return the configured ComponentAdapter. 1506 */ 1507 protected ComponentAdapter getComponentAdapter(int index) { 1508 ComponentAdapter adapter = getComponentAdapter(); 1509 adapter.column = 0; 1510 adapter.row = index; 1511 return adapter; 1512 } 1513 1514 protected ComponentAdapter dataAdapter; 1515 1516 protected static class TreeAdapter extends ComponentAdapter { 1517 private final JXTree tree; 1518 1519 /** 1520 * Constructs a <code>TableCellRenderContext</code> for the specified 1521 * target component. 1522 * 1523 * @param component the target component 1524 */ 1525 public TreeAdapter(JXTree component) { 1526 super(component); 1527 tree = component; 1528 } 1529 1530 public JXTree getTree() { 1531 return tree; 1532 } 1533 1534 /** 1535 * {@inheritDoc} 1536 */ 1537 @Override 1538 public boolean hasFocus() { 1539 return tree.isFocusOwner() && (tree.getLeadSelectionRow() == row); 1540 } 1541 1542 /** 1543 * {@inheritDoc} 1544 */ 1545 @Override 1546 public Object getValueAt(int row, int column) { 1547 TreePath path = tree.getPathForRow(row); 1548 return path.getLastPathComponent(); 1549 } 1550 1551 /** 1552 * {@inheritDoc} <p> 1553 * 1554 * JXTree doesn't support filtering/sorting. This implies that 1555 * model and view coordinates are the same. So this method is 1556 * implemented to call getValueAt(row, column). 1557 * 1558 */ 1559 @Override 1560 public Object getFilteredValueAt(int row, int column) { 1561 /** TODO: Implement filtering */ 1562 return getValueAt(row, column); 1563 } 1564 1565 1566 /** 1567 * {@inheritDoc} <p> 1568 * 1569 * JXTree doesn't support filtering/sorting. This implies that 1570 * model and view coordinates are the same. So this method is 1571 * implemented to call getValueAt(row, column). 1572 * 1573 */ 1574 @Override 1575 public Object getValue() { 1576 return getValueAt(row, column); 1577 } 1578 1579 1580 /** 1581 * {@inheritDoc} 1582 */ 1583 @Override 1584 public String getFilteredStringAt(int row, int column) { 1585 return tree.getStringAt(row); 1586 } 1587 1588 /** 1589 * {@inheritDoc} 1590 */ 1591 @Override 1592 public String getString() { 1593 return tree.getStringAt(row); 1594 } 1595 1596 /** 1597 * {@inheritDoc} 1598 */ 1599 @Override 1600 public String getStringAt(int row, int column) { 1601 return tree.getStringAt(row); 1602 } 1603 1604 /** 1605 * {@inheritDoc} 1606 */ 1607 @Override 1608 public boolean isEditable() { 1609 //this is not as robust as JXTable; should it be? -- kgs 1610 return tree.isPathEditable(tree.getPathForRow(row)); 1611 } 1612 1613 /** 1614 * {@inheritDoc} 1615 */ 1616 @Override 1617 public boolean isSelected() { 1618 return tree.isRowSelected(row); 1619 } 1620 1621 /** 1622 * {@inheritDoc} 1623 */ 1624 @Override 1625 public boolean isExpanded() { 1626 return tree.isExpanded(row); 1627 } 1628 1629 /** 1630 * {@inheritDoc} 1631 */ 1632 @Override 1633 public int getDepth() { 1634 return tree.getPathForRow(row).getPathCount() - 1; 1635 } 1636 1637 /** 1638 * {@inheritDoc} 1639 */ 1640 @Override 1641 public boolean isHierarchical() { 1642 return true; 1643 } 1644 1645 /** 1646 * {@inheritDoc} 1647 */ 1648 @Override 1649 public boolean isLeaf() { 1650 return tree.getModel().isLeaf(getValue()); 1651 } 1652 1653 /** 1654 * {@inheritDoc} 1655 */ 1656 @Override 1657 public boolean isCellEditable(int row, int column) { 1658 return false; /** TODO: */ 1659 } 1660 } 1661 1662 1663 }