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