Source for javax.swing.plaf.basic.BasicTreeUI

   1: /* BasicTreeUI.java --
   2:  Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
   3: 
   4:  This file is part of GNU Classpath.
   5: 
   6:  GNU Classpath is free software; you can redistribute it and/or modify
   7:  it under the terms of the GNU General Public License as published by
   8:  the Free Software Foundation; either version 2, or (at your option)
   9:  any later version.
  10: 
  11:  GNU Classpath is distributed in the hope that it will be useful, but
  12:  WITHOUT ANY WARRANTY; without even the implied warranty of
  13:  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14:  General Public License for more details.
  15: 
  16:  You should have received a copy of the GNU General Public License
  17:  along with GNU Classpath; see the file COPYING.  If not, write to the
  18:  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19:  02110-1301 USA.
  20: 
  21:  Linking this library statically or dynamically with other modules is
  22:  making a combined work based on this library.  Thus, the terms and
  23:  conditions of the GNU General Public License cover the whole
  24:  combination.
  25: 
  26:  As a special exception, the copyright holders of this library give you
  27:  permission to link this library with independent modules to produce an
  28:  executable, regardless of the license terms of these independent
  29:  modules, and to copy and distribute the resulting executable under
  30:  terms of your choice, provided that you also meet, for each linked
  31:  independent module, the terms and conditions of the license of that
  32:  module.  An independent module is a module which is not derived from
  33:  or based on this library.  If you modify this library, you may extend
  34:  this exception to your version of the library, but you are not
  35:  obligated to do so.  If you do not wish to do so, delete this
  36:  exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import gnu.javax.swing.tree.GnuPath;
  42: 
  43: import java.awt.Color;
  44: import java.awt.Component;
  45: import java.awt.Container;
  46: import java.awt.Dimension;
  47: import java.awt.Graphics;
  48: import java.awt.Insets;
  49: import java.awt.Label;
  50: import java.awt.Point;
  51: import java.awt.Rectangle;
  52: import java.awt.event.ActionEvent;
  53: import java.awt.event.ActionListener;
  54: import java.awt.event.ComponentAdapter;
  55: import java.awt.event.ComponentEvent;
  56: import java.awt.event.ComponentListener;
  57: import java.awt.event.FocusEvent;
  58: import java.awt.event.FocusListener;
  59: import java.awt.event.InputEvent;
  60: import java.awt.event.KeyAdapter;
  61: import java.awt.event.KeyEvent;
  62: import java.awt.event.KeyListener;
  63: import java.awt.event.MouseAdapter;
  64: import java.awt.event.MouseEvent;
  65: import java.awt.event.MouseListener;
  66: import java.awt.event.MouseMotionListener;
  67: import java.beans.PropertyChangeEvent;
  68: import java.beans.PropertyChangeListener;
  69: import java.util.Enumeration;
  70: import java.util.Hashtable;
  71: 
  72: import javax.swing.AbstractAction;
  73: import javax.swing.Action;
  74: import javax.swing.ActionMap;
  75: import javax.swing.CellRendererPane;
  76: import javax.swing.Icon;
  77: import javax.swing.InputMap;
  78: import javax.swing.JComponent;
  79: import javax.swing.JScrollBar;
  80: import javax.swing.JScrollPane;
  81: import javax.swing.JTree;
  82: import javax.swing.LookAndFeel;
  83: import javax.swing.SwingUtilities;
  84: import javax.swing.Timer;
  85: import javax.swing.UIManager;
  86: import javax.swing.event.CellEditorListener;
  87: import javax.swing.event.ChangeEvent;
  88: import javax.swing.event.MouseInputListener;
  89: import javax.swing.event.TreeExpansionEvent;
  90: import javax.swing.event.TreeExpansionListener;
  91: import javax.swing.event.TreeModelEvent;
  92: import javax.swing.event.TreeModelListener;
  93: import javax.swing.event.TreeSelectionEvent;
  94: import javax.swing.event.TreeSelectionListener;
  95: import javax.swing.plaf.ActionMapUIResource;
  96: import javax.swing.plaf.ComponentUI;
  97: import javax.swing.plaf.TreeUI;
  98: import javax.swing.tree.AbstractLayoutCache;
  99: import javax.swing.tree.DefaultTreeCellEditor;
 100: import javax.swing.tree.DefaultTreeCellRenderer;
 101: import javax.swing.tree.TreeCellEditor;
 102: import javax.swing.tree.TreeCellRenderer;
 103: import javax.swing.tree.TreeModel;
 104: import javax.swing.tree.TreeNode;
 105: import javax.swing.tree.TreePath;
 106: import javax.swing.tree.TreeSelectionModel;
 107: import javax.swing.tree.VariableHeightLayoutCache;
 108: 
 109: /**
 110:  * A delegate providing the user interface for <code>JTree</code> according to
 111:  * the Basic look and feel.
 112:  * 
 113:  * @see javax.swing.JTree
 114:  * @author Lillian Angel (langel@redhat.com)
 115:  * @author Sascha Brawer (brawer@dandelis.ch)
 116:  * @author Audrius Meskauskas (audriusa@bioinformatics.org)
 117:  */
 118: public class BasicTreeUI
 119:   extends TreeUI
 120: {
 121:   /**
 122:    * The tree cell editing may be started by the single mouse click on the
 123:    * selected cell. To separate it from the double mouse click, the editing
 124:    * session starts after this time (in ms) after that single click, and only no
 125:    * other clicks were performed during that time.
 126:    */
 127:   static int WAIT_TILL_EDITING = 900;
 128: 
 129:   /** Collapse Icon for the tree. */
 130:   protected transient Icon collapsedIcon;
 131: 
 132:   /** Expanded Icon for the tree. */
 133:   protected transient Icon expandedIcon;
 134: 
 135:   /** Distance between left margin and where vertical dashes will be drawn. */
 136:   protected int leftChildIndent;
 137: 
 138:   /**
 139:    * Distance between leftChildIndent and where cell contents will be drawn.
 140:    */
 141:   protected int rightChildIndent;
 142: 
 143:   /**
 144:    * Total fistance that will be indented. The sum of leftChildIndent and
 145:    * rightChildIndent .
 146:    */
 147:   protected int totalChildIndent;
 148: 
 149:   /** Index of the row that was last selected. */
 150:   protected int lastSelectedRow;
 151: 
 152:   /** Component that we're going to be drawing onto. */
 153:   protected JTree tree;
 154: 
 155:   /** Renderer that is being used to do the actual cell drawing. */
 156:   protected transient TreeCellRenderer currentCellRenderer;
 157: 
 158:   /**
 159:    * Set to true if the renderer that is currently in the tree was created by
 160:    * this instance.
 161:    */
 162:   protected boolean createdRenderer;
 163: 
 164:   /** Editor for the tree. */
 165:   protected transient TreeCellEditor cellEditor;
 166: 
 167:   /**
 168:    * Set to true if editor that is currently in the tree was created by this
 169:    * instance.
 170:    */
 171:   protected boolean createdCellEditor;
 172: 
 173:   /**
 174:    * Set to false when editing and shouldSelectCall() returns true meaning the
 175:    * node should be selected before editing, used in completeEditing.
 176:    * GNU Classpath editing is implemented differently, so this value is not
 177:    * actually read anywhere. However it is always set correctly to maintain 
 178:    * interoperability with the derived classes that read this field.
 179:    */
 180:   protected boolean stopEditingInCompleteEditing;
 181: 
 182:   /** Used to paint the TreeCellRenderer. */
 183:   protected CellRendererPane rendererPane;
 184: 
 185:   /** Size needed to completely display all the nodes. */
 186:   protected Dimension preferredSize;
 187: 
 188:   /** Minimum size needed to completely display all the nodes. */
 189:   protected Dimension preferredMinSize;
 190: 
 191:   /** Is the preferredSize valid? */
 192:   protected boolean validCachedPreferredSize;
 193: 
 194:   /** Object responsible for handling sizing and expanded issues. */
 195:   protected AbstractLayoutCache treeState;
 196: 
 197:   /** Used for minimizing the drawing of vertical lines. */
 198:   protected Hashtable<TreePath, Boolean> drawingCache;
 199: 
 200:   /**
 201:    * True if doing optimizations for a largeModel. Subclasses that don't support
 202:    * this may wish to override createLayoutCache to not return a
 203:    * FixedHeightLayoutCache instance.
 204:    */
 205:   protected boolean largeModel;
 206: 
 207:   /** Responsible for telling the TreeState the size needed for a node. */
 208:   protected AbstractLayoutCache.NodeDimensions nodeDimensions;
 209: 
 210:   /** Used to determine what to display. */
 211:   protected TreeModel treeModel;
 212: 
 213:   /** Model maintaining the selection. */
 214:   protected TreeSelectionModel treeSelectionModel;
 215: 
 216:   /**
 217:    * How much the depth should be offset to properly calculate x locations. This
 218:    * is based on whether or not the root is visible, and if the root handles are
 219:    * visible.
 220:    */
 221:   protected int depthOffset;
 222: 
 223:   /**
 224:    * When editing, this will be the Component that is doing the actual editing.
 225:    */
 226:   protected Component editingComponent;
 227: 
 228:   /** Path that is being edited. */
 229:   protected TreePath editingPath;
 230: 
 231:   /**
 232:    * Row that is being edited. Should only be referenced if editingComponent is
 233:    * null.
 234:    */
 235:   protected int editingRow;
 236: 
 237:   /** Set to true if the editor has a different size than the renderer. */
 238:   protected boolean editorHasDifferentSize;
 239: 
 240:   /** Boolean to keep track of editing. */
 241:   boolean isEditing;
 242: 
 243:   /** The current path of the visible nodes in the tree. */
 244:   TreePath currentVisiblePath;
 245: 
 246:   /** The gap between the icon and text. */
 247:   int gap = 4;
 248: 
 249:   /** The max height of the nodes in the tree. */
 250:   int maxHeight;
 251:   
 252:   /** The hash color. */
 253:   Color hashColor;
 254: 
 255:   /** Listeners */
 256:   PropertyChangeListener propertyChangeListener;
 257: 
 258:   FocusListener focusListener;
 259: 
 260:   TreeSelectionListener treeSelectionListener;
 261: 
 262:   MouseListener mouseListener;
 263: 
 264:   KeyListener keyListener;
 265: 
 266:   PropertyChangeListener selectionModelPropertyChangeListener;
 267: 
 268:   ComponentListener componentListener;
 269: 
 270:   CellEditorListener cellEditorListener;
 271: 
 272:   TreeExpansionListener treeExpansionListener;
 273: 
 274:   TreeModelListener treeModelListener;
 275: 
 276:   /**
 277:    * The zero size icon, used for expand controls, if they are not visible.
 278:    */
 279:   static Icon nullIcon;
 280: 
 281:   /**
 282:    * The special value of the mouse event is sent indicating that this is not
 283:    * just the mouse click, but the mouse click on the selected node. Sending
 284:    * such event forces to start the cell editing session.
 285:    */
 286:   static final MouseEvent EDIT = new MouseEvent(new Label(), 7, 7, 7, 7, 7, 7,
 287:                                                 false);
 288: 
 289:   /**
 290:    * Creates a new BasicTreeUI object.
 291:    */
 292:   public BasicTreeUI()
 293:   {
 294:     validCachedPreferredSize = false;
 295:     drawingCache = new Hashtable();
 296:     nodeDimensions = createNodeDimensions();
 297:     configureLayoutCache();
 298: 
 299:     editingRow = - 1;
 300:     lastSelectedRow = - 1;
 301:   }
 302: 
 303:   /**
 304:    * Returns an instance of the UI delegate for the specified component.
 305:    * 
 306:    * @param c the <code>JComponent</code> for which we need a UI delegate for.
 307:    * @return the <code>ComponentUI</code> for c.
 308:    */
 309:   public static ComponentUI createUI(JComponent c)
 310:   {
 311:     return new BasicTreeUI();
 312:   }
 313: 
 314:   /**
 315:    * Returns the Hash color.
 316:    * 
 317:    * @return the <code>Color</code> of the Hash.
 318:    */
 319:   protected Color getHashColor()
 320:   {
 321:     return hashColor;
 322:   }
 323: 
 324:   /**
 325:    * Sets the Hash color.
 326:    * 
 327:    * @param color the <code>Color</code> to set the Hash to.
 328:    */
 329:   protected void setHashColor(Color color)
 330:   {
 331:     hashColor = color;
 332:   }
 333: 
 334:   /**
 335:    * Sets the left child's indent value.
 336:    * 
 337:    * @param newAmount is the new indent value for the left child.
 338:    */
 339:   public void setLeftChildIndent(int newAmount)
 340:   {
 341:     leftChildIndent = newAmount;
 342:   }
 343: 
 344:   /**
 345:    * Returns the indent value for the left child.
 346:    * 
 347:    * @return the indent value for the left child.
 348:    */
 349:   public int getLeftChildIndent()
 350:   {
 351:     return leftChildIndent;
 352:   }
 353: 
 354:   /**
 355:    * Sets the right child's indent value.
 356:    * 
 357:    * @param newAmount is the new indent value for the right child.
 358:    */
 359:   public void setRightChildIndent(int newAmount)
 360:   {
 361:     rightChildIndent = newAmount;
 362:   }
 363: 
 364:   /**
 365:    * Returns the indent value for the right child.
 366:    * 
 367:    * @return the indent value for the right child.
 368:    */
 369:   public int getRightChildIndent()
 370:   {
 371:     return rightChildIndent;
 372:   }
 373: 
 374:   /**
 375:    * Sets the expanded icon.
 376:    * 
 377:    * @param newG is the new expanded icon.
 378:    */
 379:   public void setExpandedIcon(Icon newG)
 380:   {
 381:     expandedIcon = newG;
 382:   }
 383: 
 384:   /**
 385:    * Returns the current expanded icon.
 386:    * 
 387:    * @return the current expanded icon.
 388:    */
 389:   public Icon getExpandedIcon()
 390:   {
 391:     return expandedIcon;
 392:   }
 393: 
 394:   /**
 395:    * Sets the collapsed icon.
 396:    * 
 397:    * @param newG is the new collapsed icon.
 398:    */
 399:   public void setCollapsedIcon(Icon newG)
 400:   {
 401:     collapsedIcon = newG;
 402:   }
 403: 
 404:   /**
 405:    * Returns the current collapsed icon.
 406:    * 
 407:    * @return the current collapsed icon.
 408:    */
 409:   public Icon getCollapsedIcon()
 410:   {
 411:     return collapsedIcon;
 412:   }
 413: 
 414:   /**
 415:    * Updates the componentListener, if necessary.
 416:    * 
 417:    * @param largeModel sets this.largeModel to it.
 418:    */
 419:   protected void setLargeModel(boolean largeModel)
 420:   {
 421:     if (largeModel != this.largeModel)
 422:       {
 423:         completeEditing();
 424:         tree.removeComponentListener(componentListener);
 425:         this.largeModel = largeModel;
 426:         tree.addComponentListener(componentListener);
 427:       }
 428:   }
 429: 
 430:   /**
 431:    * Returns true if largeModel is set
 432:    * 
 433:    * @return true if largeModel is set, otherwise false.
 434:    */
 435:   protected boolean isLargeModel()
 436:   {
 437:     return largeModel;
 438:   }
 439: 
 440:   /**
 441:    * Sets the row height.
 442:    * 
 443:    * @param rowHeight is the height to set this.rowHeight to.
 444:    */
 445:   protected void setRowHeight(int rowHeight)
 446:   {
 447:     completeEditing();
 448:     if (rowHeight == 0)
 449:       rowHeight = getMaxHeight(tree);
 450:     treeState.setRowHeight(rowHeight);
 451:   }
 452: 
 453:   /**
 454:    * Returns the current row height.
 455:    * 
 456:    * @return current row height.
 457:    */
 458:   protected int getRowHeight()
 459:   {
 460:     return tree.getRowHeight();
 461:   }
 462: 
 463:   /**
 464:    * Sets the TreeCellRenderer to <code>tcr</code>. This invokes
 465:    * <code>updateRenderer</code>.
 466:    * 
 467:    * @param tcr is the new TreeCellRenderer.
 468:    */
 469:   protected void setCellRenderer(TreeCellRenderer tcr)
 470:   {
 471:     // Finish editing before changing the renderer.
 472:     completeEditing();
 473: 
 474:     // The renderer is set in updateRenderer.
 475:     updateRenderer();
 476: 
 477:     // Refresh the layout if necessary.
 478:     if (treeState != null)
 479:       {
 480:     treeState.invalidateSizes();
 481:     updateSize();
 482:       }
 483:   }
 484: 
 485:   /**
 486:    * Return currentCellRenderer, which will either be the trees renderer, or
 487:    * defaultCellRenderer, which ever was not null.
 488:    * 
 489:    * @return the current Cell Renderer
 490:    */
 491:   protected TreeCellRenderer getCellRenderer()
 492:   {
 493:     if (currentCellRenderer != null)
 494:       return currentCellRenderer;
 495: 
 496:     return createDefaultCellRenderer();
 497:   }
 498: 
 499:   /**
 500:    * Sets the tree's model.
 501:    * 
 502:    * @param model to set the treeModel to.
 503:    */
 504:   protected void setModel(TreeModel model)
 505:   {
 506:     completeEditing();
 507: 
 508:     if (treeModel != null && treeModelListener != null)
 509:       treeModel.removeTreeModelListener(treeModelListener);
 510: 
 511:     treeModel = tree.getModel();
 512: 
 513:     if (treeModel != null && treeModelListener != null)
 514:       treeModel.addTreeModelListener(treeModelListener);
 515: 
 516:     if (treeState != null)
 517:       {
 518:         treeState.setModel(treeModel);
 519:         updateLayoutCacheExpandedNodes();
 520:         updateSize();
 521:       }
 522:   }
 523: 
 524:   /**
 525:    * Returns the tree's model
 526:    * 
 527:    * @return treeModel
 528:    */
 529:   protected TreeModel getModel()
 530:   {
 531:     return treeModel;
 532:   }
 533: 
 534:   /**
 535:    * Sets the root to being visible.
 536:    * 
 537:    * @param newValue sets the visibility of the root
 538:    */
 539:   protected void setRootVisible(boolean newValue)
 540:   {
 541:     completeEditing();
 542:     tree.setRootVisible(newValue);
 543:   }
 544: 
 545:   /**
 546:    * Returns true if the root is visible.
 547:    * 
 548:    * @return true if the root is visible.
 549:    */
 550:   protected boolean isRootVisible()
 551:   {
 552:     return tree.isRootVisible();
 553:   }
 554: 
 555:   /**
 556:    * Determines whether the node handles are to be displayed.
 557:    * 
 558:    * @param newValue sets whether or not node handles should be displayed.
 559:    */
 560:   protected void setShowsRootHandles(boolean newValue)
 561:   {
 562:     completeEditing();
 563:     updateDepthOffset();
 564:     if (treeState != null)
 565:       {
 566:         treeState.invalidateSizes();
 567:         updateSize();
 568:       }
 569:   }
 570: 
 571:   /**
 572:    * Returns true if the node handles are to be displayed.
 573:    * 
 574:    * @return true if the node handles are to be displayed.
 575:    */
 576:   protected boolean getShowsRootHandles()
 577:   {
 578:     return tree.getShowsRootHandles();
 579:   }
 580: 
 581:   /**
 582:    * Sets the cell editor.
 583:    * 
 584:    * @param editor to set the cellEditor to.
 585:    */
 586:   protected void setCellEditor(TreeCellEditor editor)
 587:   {
 588:     updateCellEditor();
 589:   }
 590: 
 591:   /**
 592:    * Returns the <code>TreeCellEditor</code> for this tree.
 593:    * 
 594:    * @return the cellEditor for this tree.
 595:    */
 596:   protected TreeCellEditor getCellEditor()
 597:   {
 598:     return cellEditor;
 599:   }
 600: 
 601:   /**
 602:    * Configures the receiver to allow, or not allow, editing.
 603:    * 
 604:    * @param newValue sets the receiver to allow editing if true.
 605:    */
 606:   protected void setEditable(boolean newValue)
 607:   {
 608:     updateCellEditor();
 609:   }
 610: 
 611:   /**
 612:    * Returns true if the receiver allows editing.
 613:    * 
 614:    * @return true if the receiver allows editing.
 615:    */
 616:   protected boolean isEditable()
 617:   {
 618:     return tree.isEditable();
 619:   }
 620: 
 621:   /**
 622:    * Resets the selection model. The appropriate listeners are installed on the
 623:    * model.
 624:    * 
 625:    * @param newLSM resets the selection model.
 626:    */
 627:   protected void setSelectionModel(TreeSelectionModel newLSM)
 628:   {
 629:     completeEditing();
 630:     if (newLSM != null)
 631:       {
 632:         treeSelectionModel = newLSM;
 633:         tree.setSelectionModel(treeSelectionModel);
 634:       }
 635:   }
 636: 
 637:   /**
 638:    * Returns the current selection model.
 639:    * 
 640:    * @return the current selection model.
 641:    */
 642:   protected TreeSelectionModel getSelectionModel()
 643:   {
 644:     return treeSelectionModel;
 645:   }
 646: 
 647:   /**
 648:    * Returns the Rectangle enclosing the label portion that the last item in
 649:    * path will be drawn to. Will return null if any component in path is
 650:    * currently valid.
 651:    * 
 652:    * @param tree is the current tree the path will be drawn to.
 653:    * @param path is the current path the tree to draw to.
 654:    * @return the Rectangle enclosing the label portion that the last item in the
 655:    *         path will be drawn to.
 656:    */
 657:   public Rectangle getPathBounds(JTree tree, TreePath path)
 658:   {
 659:     Rectangle bounds = null;
 660:     if (tree != null && treeState != null)
 661:       {
 662:         bounds = treeState.getBounds(path, null);
 663:         Insets i = tree.getInsets();
 664:         if (bounds != null && i != null)
 665:           {
 666:             bounds.x += i.left;
 667:             bounds.y += i.top;
 668:           }
 669:       }
 670:     return bounds;
 671:   }
 672: 
 673:   /**
 674:    * Returns the max height of all the nodes in the tree.
 675:    * 
 676:    * @param tree - the current tree
 677:    * @return the max height.
 678:    */
 679:   int getMaxHeight(JTree tree)
 680:   {
 681:     if (maxHeight != 0)
 682:       return maxHeight;
 683: 
 684:     Icon e = UIManager.getIcon("Tree.openIcon");
 685:     Icon c = UIManager.getIcon("Tree.closedIcon");
 686:     Icon l = UIManager.getIcon("Tree.leafIcon");
 687:     int rc = getRowCount(tree);
 688:     int iconHeight = 0;
 689: 
 690:     for (int row = 0; row < rc; row++)
 691:       {
 692:         if (isLeaf(row))
 693:           iconHeight = l.getIconHeight();
 694:         else if (tree.isExpanded(row))
 695:           iconHeight = e.getIconHeight();
 696:         else
 697:           iconHeight = c.getIconHeight();
 698: 
 699:         maxHeight = Math.max(maxHeight, iconHeight + gap);
 700:       }
 701:      
 702:     treeState.setRowHeight(maxHeight);
 703:     return maxHeight;
 704:   }
 705:   
 706:   /**
 707:    * Get the tree node icon.
 708:    */
 709:   Icon getNodeIcon(TreePath path)
 710:   {
 711:     Object node = path.getLastPathComponent();
 712:     if (treeModel.isLeaf(node))
 713:       return UIManager.getIcon("Tree.leafIcon");
 714:     else if (treeState.getExpandedState(path))
 715:       return UIManager.getIcon("Tree.openIcon");
 716:     else
 717:       return UIManager.getIcon("Tree.closedIcon");
 718:   }
 719: 
 720:   /**
 721:    * Returns the path for passed in row. If row is not visible null is returned.
 722:    * 
 723:    * @param tree is the current tree to return path for.
 724:    * @param row is the row number of the row to return.
 725:    * @return the path for passed in row. If row is not visible null is returned.
 726:    */
 727:   public TreePath getPathForRow(JTree tree, int row)
 728:   {
 729:     return treeState.getPathForRow(row);
 730:   }
 731: 
 732:   /**
 733:    * Returns the row that the last item identified in path is visible at. Will
 734:    * return -1 if any of the elments in the path are not currently visible.
 735:    * 
 736:    * @param tree is the current tree to return the row for.
 737:    * @param path is the path used to find the row.
 738:    * @return the row that the last item identified in path is visible at. Will
 739:    *         return -1 if any of the elments in the path are not currently
 740:    *         visible.
 741:    */
 742:   public int getRowForPath(JTree tree, TreePath path)
 743:   {
 744:     return treeState.getRowForPath(path);
 745:   }
 746: 
 747:   /**
 748:    * Returns the number of rows that are being displayed.
 749:    * 
 750:    * @param tree is the current tree to return the number of rows for.
 751:    * @return the number of rows being displayed.
 752:    */
 753:   public int getRowCount(JTree tree)
 754:   {
 755:     return treeState.getRowCount();
 756:   }
 757: 
 758:   /**
 759:    * Returns the path to the node that is closest to x,y. If there is nothing
 760:    * currently visible this will return null, otherwise it'll always return a
 761:    * valid path. If you need to test if the returned object is exactly at x,y
 762:    * you should get the bounds for the returned path and test x,y against that.
 763:    * 
 764:    * @param tree the tree to search for the closest path
 765:    * @param x is the x coordinate of the location to search
 766:    * @param y is the y coordinate of the location to search
 767:    * @return the tree path closes to x,y.
 768:    */
 769:   public TreePath getClosestPathForLocation(JTree tree, int x, int y)
 770:   {
 771:     return treeState.getPathClosestTo(x, y);
 772:   }
 773: 
 774:   /**
 775:    * Returns true if the tree is being edited. The item that is being edited can
 776:    * be returned by getEditingPath().
 777:    * 
 778:    * @param tree is the tree to check for editing.
 779:    * @return true if the tree is being edited.
 780:    */
 781:   public boolean isEditing(JTree tree)
 782:   {
 783:     return isEditing;
 784:   }
 785: 
 786:   /**
 787:    * Stops the current editing session. This has no effect if the tree is not
 788:    * being edited. Returns true if the editor allows the editing session to
 789:    * stop.
 790:    * 
 791:    * @param tree is the tree to stop the editing on
 792:    * @return true if the editor allows the editing session to stop.
 793:    */
 794:   public boolean stopEditing(JTree tree)
 795:   {
 796:     boolean ret = false;
 797:     if (editingComponent != null && cellEditor.stopCellEditing())
 798:       {
 799:         completeEditing(false, false, true);
 800:         ret = true;
 801:       }
 802:     return ret;
 803:   }
 804: 
 805:   /**
 806:    * Cancels the current editing session.
 807:    * 
 808:    * @param tree is the tree to cancel the editing session on.
 809:    */
 810:   public void cancelEditing(JTree tree)
 811:   {
 812:     // There is no need to send the cancel message to the editor,
 813:     // as the cancellation event itself arrives from it. This would
 814:     // only be necessary when cancelling the editing programatically.
 815:     if (editingComponent != null)
 816:       completeEditing(false, true, false);
 817:   }
 818: 
 819:   /**
 820:    * Selects the last item in path and tries to edit it. Editing will fail if
 821:    * the CellEditor won't allow it for the selected item.
 822:    * 
 823:    * @param tree is the tree to edit on.
 824:    * @param path is the path in tree to edit on.
 825:    */
 826:   public void startEditingAtPath(JTree tree, TreePath path)
 827:   {
 828:     tree.scrollPathToVisible(path);
 829:     if (path != null && tree.isVisible(path))
 830:       startEditing(path, null);
 831:   }
 832: 
 833:   /**
 834:    * Returns the path to the element that is being editted.
 835:    * 
 836:    * @param tree is the tree to get the editing path from.
 837:    * @return the path that is being edited.
 838:    */
 839:   public TreePath getEditingPath(JTree tree)
 840:   {
 841:     return editingPath;
 842:   }
 843: 
 844:   /**
 845:    * Invoked after the tree instance variable has been set, but before any
 846:    * default/listeners have been installed.
 847:    */
 848:   protected void prepareForUIInstall()
 849:   {
 850:     lastSelectedRow = -1;
 851:     preferredSize = new Dimension();
 852:     largeModel = tree.isLargeModel();
 853:     preferredSize = new Dimension();
 854:     stopEditingInCompleteEditing = true;
 855:     setModel(tree.getModel());
 856:   }
 857: 
 858:   /**
 859:    * Invoked from installUI after all the defaults/listeners have been
 860:    * installed.
 861:    */
 862:   protected void completeUIInstall()
 863:   {
 864:     setShowsRootHandles(tree.getShowsRootHandles());
 865:     updateRenderer();
 866:     updateDepthOffset();
 867:     setSelectionModel(tree.getSelectionModel());
 868:     configureLayoutCache();
 869:     treeState.setRootVisible(tree.isRootVisible()); 
 870:     treeSelectionModel.setRowMapper(treeState);
 871:     updateSize();
 872:   }
 873: 
 874:   /**
 875:    * Invoked from uninstallUI after all the defaults/listeners have been
 876:    * uninstalled.
 877:    */
 878:   protected void completeUIUninstall()
 879:   {
 880:     tree = null;
 881:   }
 882: 
 883:   /**
 884:    * Installs the subcomponents of the tree, which is the renderer pane.
 885:    */
 886:   protected void installComponents()
 887:   {
 888:     currentCellRenderer = createDefaultCellRenderer();
 889:     rendererPane = createCellRendererPane();
 890:     createdRenderer = true;
 891:     setCellRenderer(currentCellRenderer);
 892:   }
 893: 
 894:   /**
 895:    * Creates an instance of NodeDimensions that is able to determine the size of
 896:    * a given node in the tree. The node dimensions must be created before
 897:    * configuring the layout cache.
 898:    * 
 899:    * @return the NodeDimensions of a given node in the tree
 900:    */
 901:   protected AbstractLayoutCache.NodeDimensions createNodeDimensions()
 902:   {
 903:     return new NodeDimensionsHandler();
 904:   }
 905: 
 906:   /**
 907:    * Creates a listener that is reponsible for the updates the UI based on how
 908:    * the tree changes.
 909:    * 
 910:    * @return the PropertyChangeListener that is reposnsible for the updates
 911:    */
 912:   protected PropertyChangeListener createPropertyChangeListener()
 913:   {
 914:     return new PropertyChangeHandler();
 915:   }
 916: 
 917:   /**
 918:    * Creates the listener responsible for updating the selection based on mouse
 919:    * events.
 920:    * 
 921:    * @return the MouseListener responsible for updating.
 922:    */
 923:   protected MouseListener createMouseListener()
 924:   {
 925:     return new MouseHandler();
 926:   }
 927: 
 928:   /**
 929:    * Creates the listener that is responsible for updating the display when
 930:    * focus is lost/grained.
 931:    * 
 932:    * @return the FocusListener responsible for updating.
 933:    */
 934:   protected FocusListener createFocusListener()
 935:   {
 936:     return new FocusHandler();
 937:   }
 938: 
 939:   /**
 940:    * Creates the listener reponsible for getting key events from the tree.
 941:    * 
 942:    * @return the KeyListener responsible for getting key events.
 943:    */
 944:   protected KeyListener createKeyListener()
 945:   {
 946:     return new KeyHandler();
 947:   }
 948: 
 949:   /**
 950:    * Creates the listener responsible for getting property change events from
 951:    * the selection model.
 952:    * 
 953:    * @returns the PropertyChangeListener reponsible for getting property change
 954:    *          events from the selection model.
 955:    */
 956:   protected PropertyChangeListener createSelectionModelPropertyChangeListener()
 957:   {
 958:     return new SelectionModelPropertyChangeHandler();
 959:   }
 960: 
 961:   /**
 962:    * Creates the listener that updates the display based on selection change
 963:    * methods.
 964:    * 
 965:    * @return the TreeSelectionListener responsible for updating.
 966:    */
 967:   protected TreeSelectionListener createTreeSelectionListener()
 968:   {
 969:     return new TreeSelectionHandler();
 970:   }
 971: 
 972:   /**
 973:    * Creates a listener to handle events from the current editor
 974:    * 
 975:    * @return the CellEditorListener that handles events from the current editor
 976:    */
 977:   protected CellEditorListener createCellEditorListener()
 978:   {
 979:     return new CellEditorHandler();
 980:   }
 981: 
 982:   /**
 983:    * Creates and returns a new ComponentHandler. This is used for the large
 984:    * model to mark the validCachedPreferredSize as invalid when the component
 985:    * moves.
 986:    * 
 987:    * @return a new ComponentHandler.
 988:    */
 989:   protected ComponentListener createComponentListener()
 990:   {
 991:     return new ComponentHandler();
 992:   }
 993: 
 994:   /**
 995:    * Creates and returns the object responsible for updating the treestate when
 996:    * a nodes expanded state changes.
 997:    * 
 998:    * @return the TreeExpansionListener responsible for updating the treestate
 999:    */
1000:   protected TreeExpansionListener createTreeExpansionListener()
1001:   {
1002:     return new TreeExpansionHandler();
1003:   }
1004: 
1005:   /**
1006:    * Creates the object responsible for managing what is expanded, as well as
1007:    * the size of nodes.
1008:    * 
1009:    * @return the object responsible for managing what is expanded.
1010:    */
1011:   protected AbstractLayoutCache createLayoutCache()
1012:   {
1013:     return new VariableHeightLayoutCache();
1014:   }
1015: 
1016:   /**
1017:    * Returns the renderer pane that renderer components are placed in.
1018:    * 
1019:    * @return the rendererpane that render components are placed in.
1020:    */
1021:   protected CellRendererPane createCellRendererPane()
1022:   {
1023:     return new CellRendererPane();
1024:   }
1025: 
1026:   /**
1027:    * Creates a default cell editor.
1028:    * 
1029:    * @return the default cell editor.
1030:    */
1031:   protected TreeCellEditor createDefaultCellEditor()
1032:   {
1033:     DefaultTreeCellEditor ed;
1034:     if (currentCellRenderer != null
1035:         && currentCellRenderer instanceof DefaultTreeCellRenderer)
1036:       ed = new DefaultTreeCellEditor(tree,
1037:                                 (DefaultTreeCellRenderer) currentCellRenderer);
1038:     else
1039:       ed = new DefaultTreeCellEditor(tree, null);
1040:     return ed;
1041:   }
1042: 
1043:   /**
1044:    * Returns the default cell renderer that is used to do the stamping of each
1045:    * node.
1046:    * 
1047:    * @return the default cell renderer that is used to do the stamping of each
1048:    *         node.
1049:    */
1050:   protected TreeCellRenderer createDefaultCellRenderer()
1051:   {
1052:     return new DefaultTreeCellRenderer();
1053:   }
1054: 
1055:   /**
1056:    * Returns a listener that can update the tree when the model changes.
1057:    * 
1058:    * @return a listener that can update the tree when the model changes.
1059:    */
1060:   protected TreeModelListener createTreeModelListener()
1061:   {
1062:     return new TreeModelHandler();
1063:   }
1064: 
1065:   /**
1066:    * Uninstall all registered listeners
1067:    */
1068:   protected void uninstallListeners()
1069:   {
1070:     tree.removePropertyChangeListener(propertyChangeListener);
1071:     tree.removeFocusListener(focusListener);
1072:     tree.removeTreeSelectionListener(treeSelectionListener);
1073:     tree.removeMouseListener(mouseListener);
1074:     tree.removeKeyListener(keyListener);
1075:     tree.removePropertyChangeListener(selectionModelPropertyChangeListener);
1076:     tree.removeComponentListener(componentListener);
1077:     tree.removeTreeExpansionListener(treeExpansionListener);
1078: 
1079:     TreeCellEditor tce = tree.getCellEditor();
1080:     if (tce != null)
1081:       tce.removeCellEditorListener(cellEditorListener);
1082:     if (treeModel != null)
1083:       treeModel.removeTreeModelListener(treeModelListener);
1084:   }
1085: 
1086:   /**
1087:    * Uninstall all keyboard actions.
1088:    */
1089:   protected void uninstallKeyboardActions()
1090:   {
1091:     tree.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).setParent(
1092:                                                                               null);
1093:     tree.getActionMap().setParent(null);
1094:   }
1095: 
1096:   /**
1097:    * Uninstall the rendererPane.
1098:    */
1099:   protected void uninstallComponents()
1100:   {
1101:     currentCellRenderer = null;
1102:     rendererPane = null;
1103:     createdRenderer = false;
1104:     setCellRenderer(currentCellRenderer);
1105:   }
1106: 
1107:   /**
1108:    * The vertical element of legs between nodes starts at the bottom of the
1109:    * parent node by default. This method makes the leg start below that.
1110:    * 
1111:    * @return the vertical leg buffer
1112:    */
1113:   protected int getVerticalLegBuffer()
1114:   {
1115:     return getRowHeight() / 2;
1116:   }
1117: 
1118:   /**
1119:    * The horizontal element of legs between nodes starts at the right of the
1120:    * left-hand side of the child node by default. This method makes the leg end
1121:    * before that.
1122:    * 
1123:    * @return the horizontal leg buffer
1124:    */
1125:   protected int getHorizontalLegBuffer()
1126:   {
1127:     return rightChildIndent / 2;
1128:   }
1129: 
1130:   /**
1131:    * Make all the nodes that are expanded in JTree expanded in LayoutCache. This
1132:    * invokes updateExpandedDescendants with the root path.
1133:    */
1134:   protected void updateLayoutCacheExpandedNodes()
1135:   {
1136:     if (treeModel != null && treeModel.getRoot() != null)
1137:       updateExpandedDescendants(new TreePath(treeModel.getRoot()));
1138:   }
1139: 
1140:   /**
1141:    * Updates the expanded state of all the descendants of the <code>path</code>
1142:    * by getting the expanded descendants from the tree and forwarding to the
1143:    * tree state.
1144:    * 
1145:    * @param path the path used to update the expanded states
1146:    */
1147:   protected void updateExpandedDescendants(TreePath path)
1148:   {
1149:     completeEditing();
1150:     Enumeration expanded = tree.getExpandedDescendants(path);
1151:     while (expanded.hasMoreElements())
1152:       treeState.setExpandedState((TreePath) expanded.nextElement(), true);
1153:   }
1154: 
1155:   /**
1156:    * Returns a path to the last child of <code>parent</code>
1157:    * 
1158:    * @param parent is the topmost path to specified
1159:    * @return a path to the last child of parent
1160:    */
1161:   protected TreePath getLastChildPath(TreePath parent)
1162:   {
1163:     return (TreePath) parent.getLastPathComponent();
1164:   }
1165: 
1166:   /**
1167:    * Updates how much each depth should be offset by.
1168:    */
1169:   protected void updateDepthOffset()
1170:   {
1171:     depthOffset += getVerticalLegBuffer();
1172:   }
1173: 
1174:   /**
1175:    * Updates the cellEditor based on editability of the JTree that we're
1176:    * contained in. If the tree is editable but doesn't have a cellEditor, a
1177:    * basic one will be used.
1178:    */
1179:   protected void updateCellEditor()
1180:   {
1181:     completeEditing();
1182:     TreeCellEditor newEd = null;
1183:     if (tree != null && tree.isEditable())
1184:       {
1185:         newEd = tree.getCellEditor();
1186:         if (newEd == null)
1187:           {
1188:             newEd = createDefaultCellEditor();
1189:             if (newEd != null)
1190:               {
1191:                 tree.setCellEditor(newEd);
1192:                 createdCellEditor = true;
1193:               }
1194:           }
1195:       }
1196:     // Update listeners.
1197:     if (newEd != cellEditor)
1198:       {
1199:         if (cellEditor != null && cellEditorListener != null)
1200:           cellEditor.removeCellEditorListener(cellEditorListener);
1201:         cellEditor = newEd;
1202:         if (cellEditorListener == null)
1203:           cellEditorListener = createCellEditorListener();
1204:         if (cellEditor != null && cellEditorListener != null)
1205:           cellEditor.addCellEditorListener(cellEditorListener);
1206:         createdCellEditor = false;
1207:       }
1208:   }
1209: 
1210:   /**
1211:    * Messaged from the tree we're in when the renderer has changed.
1212:    */
1213:   protected void updateRenderer()
1214:   {
1215:     if (tree != null)
1216:       {
1217:     TreeCellRenderer rend = tree.getCellRenderer();
1218:     if (rend != null)
1219:       {
1220:         createdRenderer = false;
1221:         currentCellRenderer = rend;
1222:         if (createdCellEditor)
1223:           tree.setCellEditor(null);
1224:       }
1225:     else
1226:       {
1227:         tree.setCellRenderer(createDefaultCellRenderer());
1228:         createdRenderer = true;
1229:       }
1230:       }
1231:     else
1232:       {
1233:     currentCellRenderer = null;
1234:     createdRenderer = false;
1235:       }
1236: 
1237:     updateCellEditor();
1238:   }
1239: 
1240:   /**
1241:    * Resets the treeState instance based on the tree we're providing the look
1242:    * and feel for. The node dimensions handler is required and must be created
1243:    * in advance.
1244:    */
1245:   protected void configureLayoutCache()
1246:   {
1247:     treeState = createLayoutCache();
1248:     treeState.setNodeDimensions(nodeDimensions);
1249:   }
1250: 
1251:   /**
1252:    * Marks the cached size as being invalid, and messages the tree with
1253:    * <code>treeDidChange</code>.
1254:    */
1255:   protected void updateSize()
1256:   {
1257:     preferredSize = null;
1258:     updateCachedPreferredSize();
1259:     tree.treeDidChange();
1260:   }
1261: 
1262:   /**
1263:    * Updates the <code>preferredSize</code> instance variable, which is
1264:    * returned from <code>getPreferredSize()</code>.
1265:    */
1266:   protected void updateCachedPreferredSize()
1267:   {
1268:     validCachedPreferredSize = false;
1269:   }
1270: 
1271:   /**
1272:    * Messaged from the VisibleTreeNode after it has been expanded.
1273:    * 
1274:    * @param path is the path that has been expanded.
1275:    */
1276:   protected void pathWasExpanded(TreePath path)
1277:   {
1278:     validCachedPreferredSize = false;
1279:     treeState.setExpandedState(path, true);
1280:     tree.repaint();
1281:   }
1282: 
1283:   /**
1284:    * Messaged from the VisibleTreeNode after it has collapsed
1285:    */
1286:   protected void pathWasCollapsed(TreePath path)
1287:   {
1288:     validCachedPreferredSize = false;
1289:     treeState.setExpandedState(path, false);
1290:     tree.repaint();
1291:   }
1292: 
1293:   /**
1294:    * Install all defaults for the tree.
1295:    */
1296:   protected void installDefaults()
1297:   {
1298:     LookAndFeel.installColorsAndFont(tree, "Tree.background",
1299:                                      "Tree.foreground", "Tree.font");
1300:     
1301:     hashColor = UIManager.getColor("Tree.hash");
1302:     if (hashColor == null)
1303:       hashColor = Color.black;
1304:     
1305:     tree.setOpaque(true);
1306: 
1307:     rightChildIndent = UIManager.getInt("Tree.rightChildIndent");
1308:     leftChildIndent = UIManager.getInt("Tree.leftChildIndent");
1309:     totalChildIndent = rightChildIndent + leftChildIndent;
1310:     setRowHeight(UIManager.getInt("Tree.rowHeight"));
1311:     tree.setRowHeight(getRowHeight());
1312:     tree.setScrollsOnExpand(UIManager.getBoolean("Tree.scrollsOnExpand"));
1313:     setExpandedIcon(UIManager.getIcon("Tree.expandedIcon"));
1314:     setCollapsedIcon(UIManager.getIcon("Tree.collapsedIcon"));
1315:   }
1316: 
1317:   /**
1318:    * Install all keyboard actions for this
1319:    */
1320:   protected void installKeyboardActions()
1321:   {
1322:     InputMap focusInputMap =
1323:       (InputMap) SharedUIDefaults.get("Tree.focusInputMap");
1324:     SwingUtilities.replaceUIInputMap(tree, JComponent.WHEN_FOCUSED,
1325:                                      focusInputMap);
1326:     InputMap ancestorInputMap =
1327:       (InputMap) SharedUIDefaults.get("Tree.ancestorInputMap");
1328:     SwingUtilities.replaceUIInputMap(tree,
1329:                                  JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1330:                                  ancestorInputMap);
1331: 
1332:     SwingUtilities.replaceUIActionMap(tree, getActionMap());
1333:   }
1334: 
1335:   /**
1336:    * Creates and returns the shared action map for JTrees.
1337:    *
1338:    * @return the shared action map for JTrees
1339:    */
1340:   private ActionMap getActionMap()
1341:   {
1342:     ActionMap am = (ActionMap) UIManager.get("Tree.actionMap");
1343:     if (am == null)
1344:       {
1345:         am = createDefaultActions();
1346:         UIManager.getLookAndFeelDefaults().put("Tree.actionMap", am);
1347:       }
1348:     return am;
1349:   }
1350: 
1351:   /**
1352:    * Creates the default actions when there are none specified by the L&F.
1353:    *
1354:    * @return the default actions
1355:    */
1356:   private ActionMap createDefaultActions()
1357:   {
1358:     ActionMapUIResource am = new ActionMapUIResource();
1359:     Action action;
1360: 
1361:     // TreeHomeAction.
1362:     action = new TreeHomeAction(-1, "selectFirst");
1363:     am.put(action.getValue(Action.NAME), action);
1364:     action = new TreeHomeAction(-1, "selectFirstChangeLead");
1365:     am.put(action.getValue(Action.NAME), action);
1366:     action = new TreeHomeAction(-1, "selectFirstExtendSelection");
1367:     am.put(action.getValue(Action.NAME), action);
1368:     action = new TreeHomeAction(1, "selectLast");
1369:     am.put(action.getValue(Action.NAME), action);
1370:     action = new TreeHomeAction(1, "selectLastChangeLead");
1371:     am.put(action.getValue(Action.NAME), action);
1372:     action = new TreeHomeAction(1, "selectLastExtendSelection");
1373:     am.put(action.getValue(Action.NAME), action);
1374: 
1375:     // TreeIncrementAction.
1376:     action = new TreeIncrementAction(-1, "selectPrevious");
1377:     am.put(action.getValue(Action.NAME), action);
1378:     action = new TreeIncrementAction(-1, "selectPreviousExtendSelection");
1379:     am.put(action.getValue(Action.NAME), action);
1380:     action = new TreeIncrementAction(-1, "selectPreviousChangeLead");
1381:     am.put(action.getValue(Action.NAME), action);
1382:     action = new TreeIncrementAction(1, "selectNext");
1383:     am.put(action.getValue(Action.NAME), action);
1384:     action = new TreeIncrementAction(1, "selectNextExtendSelection");
1385:     am.put(action.getValue(Action.NAME), action);
1386:     action = new TreeIncrementAction(1, "selectNextChangeLead");
1387:     am.put(action.getValue(Action.NAME), action);
1388: 
1389:     // TreeTraverseAction.
1390:     action = new TreeTraverseAction(-1, "selectParent");
1391:     am.put(action.getValue(Action.NAME), action);
1392:     action = new TreeTraverseAction(1, "selectChild");
1393:     am.put(action.getValue(Action.NAME), action);
1394:     
1395:     // TreeToggleAction.
1396:     action = new TreeToggleAction("toggleAndAnchor");
1397:     am.put(action.getValue(Action.NAME), action);
1398: 
1399:     // TreePageAction.
1400:     action = new TreePageAction(-1, "scrollUpChangeSelection");
1401:     am.put(action.getValue(Action.NAME), action);
1402:     action = new TreePageAction(-1, "scrollUpExtendSelection");
1403:     am.put(action.getValue(Action.NAME), action);
1404:     action = new TreePageAction(-1, "scrollUpChangeLead");
1405:     am.put(action.getValue(Action.NAME), action);
1406:     action = new TreePageAction(1, "scrollDownChangeSelection");
1407:     am.put(action.getValue(Action.NAME), action);
1408:     action = new TreePageAction(1, "scrollDownExtendSelection");
1409:     am.put(action.getValue(Action.NAME), action);
1410:     action = new TreePageAction(1, "scrollDownChangeLead");
1411:     am.put(action.getValue(Action.NAME), action);
1412:     
1413:     // Tree editing actions
1414:     action = new TreeStartEditingAction("startEditing");
1415:     am.put(action.getValue(Action.NAME), action);
1416:     action = new TreeCancelEditingAction("cancel");
1417:     am.put(action.getValue(Action.NAME), action);
1418:     
1419: 
1420:     return am;
1421:   }
1422: 
1423:   /**
1424:    * Converts the modifiers.
1425:    * 
1426:    * @param mod - modifier to convert
1427:    * @returns the new modifier
1428:    */
1429:   private int convertModifiers(int mod)
1430:   {
1431:     if ((mod & KeyEvent.SHIFT_DOWN_MASK) != 0)
1432:       {
1433:         mod |= KeyEvent.SHIFT_MASK;
1434:         mod &= ~ KeyEvent.SHIFT_DOWN_MASK;
1435:       }
1436:     if ((mod & KeyEvent.CTRL_DOWN_MASK) != 0)
1437:       {
1438:         mod |= KeyEvent.CTRL_MASK;
1439:         mod &= ~ KeyEvent.CTRL_DOWN_MASK;
1440:       }
1441:     if ((mod & KeyEvent.META_DOWN_MASK) != 0)
1442:       {
1443:         mod |= KeyEvent.META_MASK;
1444:         mod &= ~ KeyEvent.META_DOWN_MASK;
1445:       }
1446:     if ((mod & KeyEvent.ALT_DOWN_MASK) != 0)
1447:       {
1448:         mod |= KeyEvent.ALT_MASK;
1449:         mod &= ~ KeyEvent.ALT_DOWN_MASK;
1450:       }
1451:     if ((mod & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0)
1452:       {
1453:         mod |= KeyEvent.ALT_GRAPH_MASK;
1454:         mod &= ~ KeyEvent.ALT_GRAPH_DOWN_MASK;
1455:       }
1456:     return mod;
1457:   }
1458: 
1459:   /**
1460:    * Install all listeners for this
1461:    */
1462:   protected void installListeners()
1463:   {
1464:     propertyChangeListener = createPropertyChangeListener();
1465:     tree.addPropertyChangeListener(propertyChangeListener);
1466: 
1467:     focusListener = createFocusListener();
1468:     tree.addFocusListener(focusListener);
1469: 
1470:     treeSelectionListener = createTreeSelectionListener();
1471:     tree.addTreeSelectionListener(treeSelectionListener);
1472: 
1473:     mouseListener = createMouseListener();
1474:     tree.addMouseListener(mouseListener);
1475: 
1476:     keyListener = createKeyListener();
1477:     tree.addKeyListener(keyListener);
1478: 
1479:     selectionModelPropertyChangeListener =
1480:       createSelectionModelPropertyChangeListener();
1481:     if (treeSelectionModel != null
1482:         && selectionModelPropertyChangeListener != null)
1483:       {
1484:         treeSelectionModel.addPropertyChangeListener(
1485:             selectionModelPropertyChangeListener);
1486:       }
1487: 
1488:     componentListener = createComponentListener();
1489:     tree.addComponentListener(componentListener);
1490: 
1491:     treeExpansionListener = createTreeExpansionListener();
1492:     tree.addTreeExpansionListener(treeExpansionListener);
1493: 
1494:     treeModelListener = createTreeModelListener();
1495:     if (treeModel != null)
1496:       treeModel.addTreeModelListener(treeModelListener);
1497: 
1498:     cellEditorListener = createCellEditorListener();
1499:   }
1500: 
1501:   /**
1502:    * Install the UI for the component
1503:    * 
1504:    * @param c the component to install UI for
1505:    */
1506:   public void installUI(JComponent c)
1507:   {
1508:     tree = (JTree) c;
1509: 
1510:     prepareForUIInstall();
1511:     installDefaults();
1512:     installComponents();
1513:     installKeyboardActions();
1514:     installListeners();
1515:     completeUIInstall();
1516:   }
1517:   
1518:   /**
1519:    * Uninstall the defaults for the tree
1520:    */
1521:   protected void uninstallDefaults()
1522:   {
1523:     tree.setFont(null);
1524:     tree.setForeground(null);
1525:     tree.setBackground(null);
1526:   }
1527: 
1528:   /**
1529:    * Uninstall the UI for the component
1530:    * 
1531:    * @param c the component to uninstall UI for
1532:    */
1533:   public void uninstallUI(JComponent c)
1534:   {
1535:     completeEditing();
1536: 
1537:     prepareForUIUninstall();
1538:     uninstallDefaults();
1539:     uninstallKeyboardActions();
1540:     uninstallListeners();
1541:     uninstallComponents();
1542:     completeUIUninstall();
1543:   }
1544: 
1545:   /**
1546:    * Paints the specified component appropriate for the look and feel. This
1547:    * method is invoked from the ComponentUI.update method when the specified
1548:    * component is being painted. Subclasses should override this method and use
1549:    * the specified Graphics object to render the content of the component.
1550:    * 
1551:    * @param g the Graphics context in which to paint
1552:    * @param c the component being painted; this argument is often ignored, but
1553:    *          might be used if the UI object is stateless and shared by multiple
1554:    *          components
1555:    */
1556:   public void paint(Graphics g, JComponent c)
1557:   {
1558:     JTree tree = (JTree) c;
1559:     
1560:     int rows = treeState.getRowCount();
1561:     
1562:     if (rows == 0)
1563:       // There is nothing to do if the tree is empty.
1564:       return;
1565: 
1566:     Rectangle clip = g.getClipBounds();
1567: 
1568:     Insets insets = tree.getInsets();
1569: 
1570:     if (clip != null && treeModel != null)
1571:       {
1572:         int startIndex = tree.getClosestRowForLocation(clip.x, clip.y);
1573:         int endIndex = tree.getClosestRowForLocation(clip.x + clip.width,
1574:                                                      clip.y + clip.height);
1575:         // Also paint dashes to the invisible nodes below.
1576:         // These should be painted first, otherwise they may cover
1577:         // the control icons.
1578:         if (endIndex < rows)
1579:           for (int i = endIndex + 1; i < rows; i++)
1580:             {
1581:               TreePath path = treeState.getPathForRow(i);
1582:               if (isLastChild(path))
1583:                 paintVerticalPartOfLeg(g, clip, insets, path);
1584:             }
1585: 
1586:         // The two loops are required to ensure that the lines are not
1587:         // painted over the other tree components.
1588: 
1589:         int n = endIndex - startIndex + 1;
1590:         Rectangle[] bounds = new Rectangle[n];
1591:         boolean[] isLeaf = new boolean[n];
1592:         boolean[] isExpanded = new boolean[n];
1593:         TreePath[] path = new TreePath[n];
1594:         int k;
1595: 
1596:         k = 0;
1597:         for (int i = startIndex; i <= endIndex; i++, k++)
1598:           {
1599:             path[k] = treeState.getPathForRow(i);
1600:             if (path[k] != null)
1601:               {
1602:                 isLeaf[k] = treeModel.isLeaf(path[k].getLastPathComponent());
1603:                 isExpanded[k] = tree.isExpanded(path[k]);
1604:                 bounds[k] = getPathBounds(tree, path[k]);
1605: 
1606:                 paintHorizontalPartOfLeg(g, clip, insets, bounds[k], path[k],
1607:                                          i, isExpanded[k], false, isLeaf[k]);
1608:               }
1609:             if (isLastChild(path[k]))
1610:               paintVerticalPartOfLeg(g, clip, insets, path[k]);
1611:           }
1612: 
1613:         k = 0;
1614:         for (int i = startIndex; i <= endIndex; i++, k++)
1615:           {
1616:             if (path[k] != null)
1617:               paintRow(g, clip, insets, bounds[k], path[k], i, isExpanded[k],
1618:                        false, isLeaf[k]);
1619:           }
1620:       }
1621:   }
1622: 
1623:   /**
1624:    * Check if the path is referring to the last child of some parent.
1625:    */
1626:   private boolean isLastChild(TreePath path)
1627:   {
1628:     if (path == null)
1629:       return false;
1630:     else if (path instanceof GnuPath)
1631:       {
1632:         // Except the seldom case when the layout cache is changed, this
1633:         // optimized code will be executed.
1634:         return ((GnuPath) path).isLastChild;
1635:       }
1636:     else
1637:       {
1638:         // Non optimized general case.
1639:         TreePath parent = path.getParentPath();
1640:         if (parent == null)
1641:           return false;
1642:         int childCount = treeState.getVisibleChildCount(parent);
1643:         int p = treeModel.getIndexOfChild(parent, path.getLastPathComponent());
1644:         return p == childCount - 1;
1645:       }
1646:   }
1647: 
1648:   /**
1649:    * Ensures that the rows identified by beginRow through endRow are visible.
1650:    * 
1651:    * @param beginRow is the first row
1652:    * @param endRow is the last row
1653:    */
1654:   protected void ensureRowsAreVisible(int beginRow, int endRow)
1655:   {
1656:     if (beginRow < endRow)
1657:       {
1658:         int temp = endRow;
1659:         endRow = beginRow;
1660:         beginRow = temp;
1661:       }
1662: 
1663:     for (int i = beginRow; i < endRow; i++)
1664:       {
1665:         TreePath path = getPathForRow(tree, i);
1666:         if (! tree.isVisible(path))
1667:           tree.makeVisible(path);
1668:       }
1669:   }
1670: 
1671:   /**
1672:    * Sets the preferred minimum size.
1673:    * 
1674:    * @param newSize is the new preferred minimum size.
1675:    */
1676:   public void setPreferredMinSize(Dimension newSize)
1677:   {
1678:     preferredMinSize = newSize;
1679:   }
1680: 
1681:   /**
1682:    * Gets the preferred minimum size.
1683:    * 
1684:    * @returns the preferred minimum size.
1685:    */
1686:   public Dimension getPreferredMinSize()
1687:   {
1688:     if (preferredMinSize == null)
1689:       return getPreferredSize(tree);
1690:     else
1691:       return preferredMinSize;
1692:   }
1693: 
1694:   /**
1695:    * Returns the preferred size to properly display the tree, this is a cover
1696:    * method for getPreferredSize(c, false).
1697:    * 
1698:    * @param c the component whose preferred size is being queried; this argument
1699:    *          is often ignored but might be used if the UI object is stateless
1700:    *          and shared by multiple components
1701:    * @return the preferred size
1702:    */
1703:   public Dimension getPreferredSize(JComponent c)
1704:   {
1705:     return getPreferredSize(c, false);
1706:   }
1707: 
1708:   /**
1709:    * Returns the preferred size to represent the tree in c. If checkConsistancy
1710:    * is true, checkConsistancy is messaged first.
1711:    * 
1712:    * @param c the component whose preferred size is being queried.
1713:    * @param checkConsistancy if true must check consistancy
1714:    * @return the preferred size
1715:    */
1716:   public Dimension getPreferredSize(JComponent c, boolean checkConsistancy)
1717:   {
1718:     if (! validCachedPreferredSize)
1719:       {
1720:         Rectangle size = tree.getBounds();
1721:         // Add the scrollbar dimensions to the preferred size.
1722:         preferredSize = new Dimension(treeState.getPreferredWidth(size),
1723:                                       treeState.getPreferredHeight());
1724:         validCachedPreferredSize = true;
1725:       }
1726:     return preferredSize;
1727:   }
1728: 
1729:   /**
1730:    * Returns the minimum size for this component. Which will be the min
1731:    * preferred size or (0,0).
1732:    * 
1733:    * @param c the component whose min size is being queried.
1734:    * @returns the preferred size or null
1735:    */
1736:   public Dimension getMinimumSize(JComponent c)
1737:   {
1738:     return preferredMinSize = getPreferredSize(c);
1739:   }
1740: 
1741:   /**
1742:    * Returns the maximum size for the component, which will be the preferred
1743:    * size if the instance is currently in JTree or (0,0).
1744:    * 
1745:    * @param c the component whose preferred size is being queried
1746:    * @return the max size or null
1747:    */
1748:   public Dimension getMaximumSize(JComponent c)
1749:   {
1750:     return getPreferredSize(c);
1751:   }
1752: 
1753:   /**
1754:    * Messages to stop the editing session. If the UI the receiver is providing
1755:    * the look and feel for returns true from
1756:    * <code>getInvokesStopCellEditing</code>, stopCellEditing will be invoked
1757:    * on the current editor. Then completeEditing will be messaged with false,
1758:    * true, false to cancel any lingering editing.
1759:    */
1760:   protected void completeEditing()
1761:   {
1762:     if (tree.getInvokesStopCellEditing() && stopEditingInCompleteEditing
1763:         && editingComponent != null)
1764:       cellEditor.stopCellEditing();
1765: 
1766:     completeEditing(false, true, false);
1767:   }
1768: 
1769:   /**
1770:    * Stops the editing session. If messageStop is true, the editor is messaged
1771:    * with stopEditing, if messageCancel is true the editor is messaged with
1772:    * cancelEditing. If messageTree is true, the treeModel is messaged with
1773:    * valueForPathChanged.
1774:    * 
1775:    * @param messageStop message to stop editing
1776:    * @param messageCancel message to cancel editing
1777:    * @param messageTree message to treeModel
1778:    */
1779:   protected void completeEditing(boolean messageStop, boolean messageCancel,
1780:                                  boolean messageTree)
1781:   {
1782:     // Make no attempt to complete the non existing editing session.
1783:     if (stopEditingInCompleteEditing && editingComponent != null)
1784:       {
1785:         Component comp = editingComponent;
1786:         TreePath p = editingPath;
1787:         editingComponent = null;
1788:         editingPath = null;
1789:         if (messageStop)
1790:           cellEditor.stopCellEditing();
1791:         else if (messageCancel)
1792:           cellEditor.cancelCellEditing();
1793: 
1794:         tree.remove(comp);
1795: 
1796:         if (editorHasDifferentSize)
1797:           {
1798:             treeState.invalidatePathBounds(p);
1799:             updateSize();
1800:           }
1801:         else
1802:           {
1803:             // Need to refresh the tree.
1804:             Rectangle b = getPathBounds(tree, p);
1805:             tree.repaint(0, b.y, tree.getWidth(), b.height);
1806:           }
1807: 
1808:         if (messageTree)
1809:           {
1810:             Object value = cellEditor.getCellEditorValue();
1811:             treeModel.valueForPathChanged(p, value);
1812:           }
1813:       }
1814:   }
1815: 
1816:   /**
1817:    * Will start editing for node if there is a cellEditor and shouldSelectCall
1818:    * returns true. This assumes that path is valid and visible.
1819:    * 
1820:    * @param path is the path to start editing
1821:    * @param event is the MouseEvent performed on the path
1822:    * @return true if successful
1823:    */
1824:   protected boolean startEditing(TreePath path, MouseEvent event)
1825:   {
1826:     // Maybe cancel editing.
1827:     if (isEditing(tree) && tree.getInvokesStopCellEditing()
1828:         && ! stopEditing(tree))
1829:       return false;
1830: 
1831:     completeEditing();
1832:     TreeCellEditor ed = cellEditor;
1833:     if (ed != null && tree.isPathEditable(path))
1834:       {
1835:         if (ed.isCellEditable(event))
1836:           {
1837:             editingRow = getRowForPath(tree, path); 
1838:             Object value = path.getLastPathComponent();
1839:             boolean isSelected = tree.isPathSelected(path);
1840:             boolean isExpanded = tree.isExpanded(editingPath);
1841:             boolean isLeaf = treeModel.isLeaf(value);
1842:             editingComponent = ed.getTreeCellEditorComponent(tree, value,
1843:                                                              isSelected,
1844:                                                              isExpanded,
1845:                                                              isLeaf,
1846:                                                              editingRow);
1847: 
1848:             Rectangle bounds = getPathBounds(tree, path);
1849: 
1850:             Dimension size = editingComponent.getPreferredSize();
1851:             int rowHeight = getRowHeight();
1852:             if (size.height != bounds.height && rowHeight > 0)
1853:               size.height = rowHeight;
1854: 
1855:             if (size.width != bounds.width || size.height != bounds.height)
1856:               {
1857:                 editorHasDifferentSize = true;
1858:                 treeState.invalidatePathBounds(path);
1859:                 updateSize();
1860:               }
1861:             else
1862:               editorHasDifferentSize = false;
1863:             
1864:             // The editing component must be added to its container. We add the
1865:             // container, not the editing component itself.
1866:             tree.add(editingComponent);
1867:             editingComponent.setBounds(bounds.x, bounds.y, size.width,
1868:                                        size.height);
1869:             editingComponent.validate();
1870:             editingPath = path;
1871: 
1872:             if (ed.shouldSelectCell(event))
1873:               {
1874:                 stopEditingInCompleteEditing = false;
1875:                 tree.setSelectionRow(editingRow);
1876:                 stopEditingInCompleteEditing = true;
1877:               }
1878: 
1879:             editorRequestFocus(editingComponent);
1880:             // Register MouseInputHandler to redispatch initial mouse events
1881:             // correctly.
1882:             if (event instanceof MouseEvent)
1883:               {
1884:                 Point p = SwingUtilities.convertPoint(tree, event.getX(), event.getY(),
1885:                                                       editingComponent);
1886:                 Component active =
1887:                   SwingUtilities.getDeepestComponentAt(editingComponent, p.x, p.y);
1888:                 if (active != null)
1889:                   {
1890:                     MouseInputHandler ih = new MouseInputHandler(tree, active, event);
1891:                     
1892:                   }
1893:               }
1894: 
1895:             return true;
1896:           }
1897:         else
1898:           editingComponent = null;
1899:       }
1900:     return false;
1901:   }
1902: 
1903:   /**
1904:    * Requests focus on the editor. The method is necessary since the
1905:    * DefaultTreeCellEditor returns a container that contains the
1906:    * actual editor, and we want to request focus on the editor, not the
1907:    * container.
1908:    */
1909:   private void editorRequestFocus(Component c)
1910:   {
1911:     if (c instanceof Container)
1912:       {
1913:         // TODO: Maybe do something more reasonable here, like queriying the
1914:         // FocusTraversalPolicy.
1915:         Container cont = (Container) c;
1916:         if (cont.getComponentCount() > 0)
1917:           cont.getComponent(0).requestFocus();
1918:       }
1919:     else if (c.isFocusable())
1920:       c.requestFocus();
1921:       
1922:   }
1923: 
1924:   /**
1925:    * If the <code>mouseX</code> and <code>mouseY</code> are in the expand or
1926:    * collapse region of the row, this will toggle the row.
1927:    * 
1928:    * @param path the path we are concerned with
1929:    * @param mouseX is the cursor's x position
1930:    * @param mouseY is the cursor's y position
1931:    */
1932:   protected void checkForClickInExpandControl(TreePath path, int mouseX,
1933:                                               int mouseY)
1934:   {
1935:     if (isLocationInExpandControl(path, mouseX, mouseY))
1936:       handleExpandControlClick(path, mouseX, mouseY);
1937:   }
1938: 
1939:   /**
1940:    * Returns true if the <code>mouseX</code> and <code>mouseY</code> fall in
1941:    * the area of row that is used to expand/collpse the node and the node at row
1942:    * does not represent a leaf.
1943:    * 
1944:    * @param path the path we are concerned with
1945:    * @param mouseX is the cursor's x position
1946:    * @param mouseY is the cursor's y position
1947:    * @return true if the <code>mouseX</code> and <code>mouseY</code> fall in
1948:    *         the area of row that is used to expand/collpse the node and the
1949:    *         node at row does not represent a leaf.
1950:    */
1951:   protected boolean isLocationInExpandControl(TreePath path, int mouseX,
1952:                                               int mouseY)
1953:   {
1954:     boolean cntlClick = false;
1955:     if (! treeModel.isLeaf(path.getLastPathComponent()))
1956:       {
1957:         int width;
1958:         Icon expandedIcon = getExpandedIcon();
1959:         if (expandedIcon != null)
1960:           width = expandedIcon.getIconWidth();
1961:         else
1962:           // Only guessing. This is the width of
1963:           // the tree control icon in Metal L&F.
1964:           width = 18;
1965: 
1966:         Insets i = tree.getInsets();
1967:         
1968:         int depth;
1969:         if (isRootVisible())
1970:           depth = path.getPathCount()-1;
1971:         else
1972:           depth = path.getPathCount()-2;
1973:         
1974:         int left = getRowX(tree.getRowForPath(path), depth)
1975:                    - width + i.left;
1976:         cntlClick = mouseX >= left && mouseX <= left + width;
1977:       }
1978:     return cntlClick;
1979:   }
1980: 
1981:   /**
1982:    * Messaged when the user clicks the particular row, this invokes
1983:    * toggleExpandState.
1984:    * 
1985:    * @param path the path we are concerned with
1986:    * @param mouseX is the cursor's x position
1987:    * @param mouseY is the cursor's y position
1988:    */
1989:   protected void handleExpandControlClick(TreePath path, int mouseX, int mouseY)
1990:   {
1991:     toggleExpandState(path);
1992:   }
1993: 
1994:   /**
1995:    * Expands path if it is not expanded, or collapses row if it is expanded. If
1996:    * expanding a path and JTree scroll on expand, ensureRowsAreVisible is
1997:    * invoked to scroll as many of the children to visible as possible (tries to
1998:    * scroll to last visible descendant of path).
1999:    * 
2000:    * @param path the path we are concerned with
2001:    */
2002:   protected void toggleExpandState(TreePath path)
2003:   {
2004:     // tree.isExpanded(path) would do the same, but treeState knows faster.
2005:     if (treeState.isExpanded(path))
2006:       tree.collapsePath(path);
2007:     else
2008:       tree.expandPath(path);
2009:   }
2010: 
2011:   /**
2012:    * Returning true signifies a mouse event on the node should toggle the
2013:    * selection of only the row under the mouse. The BasisTreeUI treats the
2014:    * event as "toggle selection event" if the CTRL button was pressed while
2015:    * clicking. The event is not counted as toggle event if the associated
2016:    * tree does not support the multiple selection.
2017:    * 
2018:    * @param event is the MouseEvent performed on the row.
2019:    * @return true signifies a mouse event on the node should toggle the
2020:    *         selection of only the row under the mouse.
2021:    */
2022:   protected boolean isToggleSelectionEvent(MouseEvent event)
2023:   {
2024:     return 
2025:       (tree.getSelectionModel().getSelectionMode() != 
2026:         TreeSelectionModel.SINGLE_TREE_SELECTION) &&
2027:       ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);  
2028:   }
2029: 
2030:   /**
2031:    * Returning true signifies a mouse event on the node should select from the
2032:    * anchor point. The BasisTreeUI treats the event as "multiple selection
2033:    * event" if the SHIFT button was pressed while clicking. The event is not
2034:    * counted as multiple selection event if the associated tree does not support
2035:    * the multiple selection.
2036:    * 
2037:    * @param event is the MouseEvent performed on the node.
2038:    * @return true signifies a mouse event on the node should select from the
2039:    *         anchor point.
2040:    */
2041:   protected boolean isMultiSelectEvent(MouseEvent event)
2042:   {
2043:     return 
2044:       (tree.getSelectionModel().getSelectionMode() != 
2045:         TreeSelectionModel.SINGLE_TREE_SELECTION) &&
2046:       ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);  
2047:   }
2048: 
2049:   /**
2050:    * Returning true indicates the row under the mouse should be toggled based on
2051:    * the event. This is invoked after checkForClickInExpandControl, implying the
2052:    * location is not in the expand (toggle) control.
2053:    * 
2054:    * @param event is the MouseEvent performed on the row.
2055:    * @return true indicates the row under the mouse should be toggled based on
2056:    *         the event.
2057:    */
2058:   protected boolean isToggleEvent(MouseEvent event)
2059:   {
2060:     boolean toggle = false;
2061:     if (SwingUtilities.isLeftMouseButton(event))
2062:       {
2063:         int clickCount = tree.getToggleClickCount();
2064:         if (clickCount > 0 && event.getClickCount() == clickCount)
2065:           toggle = true;
2066:       }
2067:     return toggle;
2068:   }
2069: 
2070:   /**
2071:    * Messaged to update the selection based on a MouseEvent over a particular
2072:    * row. If the even is a toggle selection event, the row is either selected,
2073:    * or deselected. If the event identifies a multi selection event, the
2074:    * selection is updated from the anchor point. Otherwise, the row is selected,
2075:    * and the previous selection is cleared.</p>
2076:    * 
2077:    * @param path is the path selected for an event
2078:    * @param event is the MouseEvent performed on the path.
2079:    * 
2080:    * @see #isToggleSelectionEvent(MouseEvent)
2081:    * @see #isMultiSelectEvent(MouseEvent)
2082:    */
2083:   protected void selectPathForEvent(TreePath path, MouseEvent event)
2084:   {
2085:     if (isToggleSelectionEvent(event))
2086:       {
2087:         // The event selects or unselects the clicked row.
2088:         if (tree.isPathSelected(path))
2089:           tree.removeSelectionPath(path);
2090:         else
2091:           {
2092:             tree.addSelectionPath(path);
2093:             tree.setAnchorSelectionPath(path);
2094:           }
2095:       }
2096:     else if (isMultiSelectEvent(event))
2097:       {
2098:         // The event extends selection form anchor till the clicked row.
2099:         TreePath anchor = tree.getAnchorSelectionPath();
2100:         if (anchor != null)
2101:           {
2102:             int aRow = getRowForPath(tree, anchor);
2103:             tree.addSelectionInterval(aRow, getRowForPath(tree, path));
2104:           }
2105:         else
2106:           tree.addSelectionPath(path);
2107:       }
2108:     else
2109:       {
2110:         // This is an ordinary event that just selects the clicked row.
2111:         tree.setSelectionPath(path);
2112:         if (isToggleEvent(event))
2113:           toggleExpandState(path);
2114:       }
2115:   }
2116: 
2117:   /**
2118:    * Returns true if the node at <code>row</code> is a leaf.
2119:    * 
2120:    * @param row is the row we are concerned with.
2121:    * @return true if the node at <code>row</code> is a leaf.
2122:    */
2123:   protected boolean isLeaf(int row)
2124:   {
2125:     TreePath pathForRow = getPathForRow(tree, row);
2126:     if (pathForRow == null)
2127:       return true;
2128: 
2129:     Object node = pathForRow.getLastPathComponent();
2130:     return treeModel.isLeaf(node);
2131:   }
2132:   
2133:   /**
2134:    * The action to start editing at the current lead selection path.
2135:    */
2136:   class TreeStartEditingAction
2137:       extends AbstractAction
2138:   {
2139:     /**
2140:      * Creates the new tree cancel editing action.
2141:      * 
2142:      * @param name the name of the action (used in toString).
2143:      */
2144:     public TreeStartEditingAction(String name)
2145:     {
2146:       super(name);
2147:     }    
2148:     
2149:     /**
2150:      * Start editing at the current lead selection path.
2151:      * 
2152:      * @param e the ActionEvent that caused this action.
2153:      */
2154:     public void actionPerformed(ActionEvent e)
2155:     {
2156:       TreePath lead = tree.getLeadSelectionPath();
2157:       if (!tree.isEditing()) 
2158:         tree.startEditingAtPath(lead);
2159:     }
2160:   }  
2161: 
2162:   /**
2163:    * Updates the preferred size when scrolling, if necessary.
2164:    */
2165:   public class ComponentHandler
2166:       extends ComponentAdapter
2167:       implements ActionListener
2168:   {
2169:     /**
2170:      * Timer used when inside a scrollpane and the scrollbar is adjusting
2171:      */
2172:     protected Timer timer;
2173: 
2174:     /** ScrollBar that is being adjusted */
2175:     protected JScrollBar scrollBar;
2176: 
2177:     /**
2178:      * Constructor
2179:      */
2180:     public ComponentHandler()
2181:     {
2182:       // Nothing to do here.
2183:     }
2184: 
2185:     /**
2186:      * Invoked when the component's position changes.
2187:      * 
2188:      * @param e the event that occurs when moving the component
2189:      */
2190:     public void componentMoved(ComponentEvent e)
2191:     {
2192:       if (timer == null)
2193:         {
2194:           JScrollPane scrollPane = getScrollPane();
2195:           if (scrollPane == null)
2196:             updateSize();
2197:           else
2198:             {
2199:               // Determine the scrollbar that is adjusting, if any, and
2200:               // start the timer for that. If no scrollbar is adjusting,
2201:               // we simply call updateSize().
2202:               scrollBar = scrollPane.getVerticalScrollBar();
2203:               if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2204:                 {
2205:                   // It's not the vertical scrollbar, try the horizontal one.
2206:                   scrollBar = scrollPane.getHorizontalScrollBar();
2207:                   if (scrollBar != null && scrollBar.getValueIsAdjusting())
2208:                     startTimer();
2209:                   else
2210:                     updateSize();
2211:                 }
2212:               else
2213:                 {
2214:                   startTimer();
2215:                 }
2216:             }
2217:         }
2218:     }
2219: 
2220:     /**
2221:      * Creates, if necessary, and starts a Timer to check if needed to resize
2222:      * the bounds
2223:      */
2224:     protected void startTimer()
2225:     {
2226:       if (timer == null)
2227:         {
2228:           timer = new Timer(200, this);
2229:           timer.setRepeats(true);
2230:         }
2231:       timer.start();
2232:     }
2233: 
2234:     /**
2235:      * Returns the JScrollPane housing the JTree, or null if one isn't found.
2236:      * 
2237:      * @return JScrollPane housing the JTree, or null if one isn't found.
2238:      */
2239:     protected JScrollPane getScrollPane()
2240:     {
2241:       JScrollPane found = null;
2242:       Component p = tree.getParent();
2243:       while (p != null && !(p instanceof JScrollPane))
2244:         p = p.getParent();
2245:       if (p instanceof JScrollPane)
2246:         found = (JScrollPane) p;
2247:       return found;
2248:     }
2249: 
2250:     /**
2251:      * Public as a result of Timer. If the scrollBar is null, or not adjusting,
2252:      * this stops the timer and updates the sizing.
2253:      * 
2254:      * @param ae is the action performed
2255:      */
2256:     public void actionPerformed(ActionEvent ae)
2257:     {
2258:       if (scrollBar == null || !scrollBar.getValueIsAdjusting())
2259:         {
2260:           if (timer != null)
2261:             timer.stop();
2262:           updateSize();
2263:           timer = null;
2264:           scrollBar = null;
2265:         }
2266:     }
2267:   }
2268: 
2269:   /**
2270:    * Listener responsible for getting cell editing events and updating the tree
2271:    * accordingly.
2272:    */
2273:   public class CellEditorHandler
2274:       implements CellEditorListener
2275:   {
2276:     /**
2277:      * Constructor
2278:      */
2279:     public CellEditorHandler()
2280:     {
2281:       // Nothing to do here.
2282:     }
2283: 
2284:     /**
2285:      * Messaged when editing has stopped in the tree. Tells the listeners
2286:      * editing has stopped.
2287:      * 
2288:      * @param e is the notification event
2289:      */
2290:     public void editingStopped(ChangeEvent e)
2291:     {
2292:       completeEditing(false, false, true);
2293:     }
2294: 
2295:     /**
2296:      * Messaged when editing has been canceled in the tree. This tells the
2297:      * listeners the editor has canceled editing.
2298:      * 
2299:      * @param e is the notification event
2300:      */
2301:     public void editingCanceled(ChangeEvent e)
2302:     {
2303:       completeEditing(false, false, false);
2304:     }
2305:   } // CellEditorHandler
2306: 
2307:   /**
2308:    * Repaints the lead selection row when focus is lost/grained.
2309:    */
2310:   public class FocusHandler
2311:       implements FocusListener
2312:   {
2313:     /**
2314:      * Constructor
2315:      */
2316:     public FocusHandler()
2317:     {
2318:       // Nothing to do here.
2319:     }
2320: 
2321:     /**
2322:      * Invoked when focus is activated on the tree we're in, redraws the lead
2323:      * row. Invoked when a component gains the keyboard focus. The method
2324:      * repaints the lead row that is shown differently when the tree is in
2325:      * focus.
2326:      * 
2327:      * @param e is the focus event that is activated
2328:      */
2329:     public void focusGained(FocusEvent e)
2330:     {
2331:       repaintLeadRow();
2332:     }
2333: 
2334:     /**
2335:      * Invoked when focus is deactivated on the tree we're in, redraws the lead
2336:      * row. Invoked when a component loses the keyboard focus. The method
2337:      * repaints the lead row that is shown differently when the tree is in
2338:      * focus.
2339:      * 
2340:      * @param e is the focus event that is deactivated
2341:      */
2342:     public void focusLost(FocusEvent e)
2343:     {
2344:       repaintLeadRow();
2345:     }
2346: 
2347:     /**
2348:      * Repaint the lead row.
2349:      */
2350:     void repaintLeadRow()
2351:     {
2352:       TreePath lead = tree.getLeadSelectionPath();
2353:       if (lead != null)
2354:         tree.repaint(tree.getPathBounds(lead));
2355:     }
2356:   }
2357: 
2358:   /**
2359:    * This is used to get multiple key down events to appropriately genereate
2360:    * events.
2361:    */
2362:   public class KeyHandler
2363:       extends KeyAdapter
2364:   {
2365:     /** Key code that is being generated for. */
2366:     protected Action repeatKeyAction;
2367: 
2368:     /** Set to true while keyPressed is active */
2369:     protected boolean isKeyDown;
2370: 
2371:     /**
2372:      * Constructor
2373:      */
2374:     public KeyHandler()
2375:     {
2376:       // Nothing to do here.
2377:     }
2378: 
2379:     /**
2380:      * Invoked when a key has been typed. Moves the keyboard focus to the first
2381:      * element whose first letter matches the alphanumeric key pressed by the
2382:      * user. Subsequent same key presses move the keyboard focus to the next
2383:      * object that starts with the same letter.
2384:      * 
2385:      * @param e the key typed
2386:      */
2387:     public void keyTyped(KeyEvent e)
2388:     {
2389:       char typed = Character.toLowerCase(e.getKeyChar());
2390:       for (int row = tree.getLeadSelectionRow() + 1;
2391:         row < tree.getRowCount(); row++)
2392:         {
2393:            if (checkMatch(row, typed))
2394:              {
2395:                tree.setSelectionRow(row);
2396:                tree.scrollRowToVisible(row);
2397:                return;
2398:              }
2399:         }
2400:       
2401:       // Not found below, search above:
2402:       for (int row = 0; row < tree.getLeadSelectionRow(); row++)
2403:         {
2404:            if (checkMatch(row, typed))
2405:              {
2406:                tree.setSelectionRow(row);
2407:                tree.scrollRowToVisible(row);               
2408:                return;
2409:              }
2410:         }
2411:     }
2412:     
2413:     /**
2414:      * Check if the given tree row starts with this character
2415:      * 
2416:      * @param row the tree row
2417:      * @param typed the typed char, must be converted to lowercase
2418:      * @return true if the given tree row starts with this character
2419:      */
2420:     boolean checkMatch(int row, char typed)
2421:     {
2422:       TreePath path = treeState.getPathForRow(row);
2423:       String node = path.getLastPathComponent().toString();
2424:       if (node.length() > 0)
2425:         {
2426:           char x = node.charAt(0);
2427:           if (typed == Character.toLowerCase(x))
2428:             return true;
2429:         }
2430:       return false;
2431:     }
2432: 
2433:     /**
2434:      * Invoked when a key has been pressed.
2435:      * 
2436:      * @param e the key pressed
2437:      */
2438:     public void keyPressed(KeyEvent e)
2439:     {
2440:       // Nothing to do here.
2441:     }
2442: 
2443:     /**
2444:      * Invoked when a key has been released
2445:      * 
2446:      * @param e the key released
2447:      */
2448:     public void keyReleased(KeyEvent e)
2449:     {
2450:       // Nothing to do here.
2451:     }
2452:   }
2453: 
2454:   /**
2455:    * MouseListener is responsible for updating the selection based on mouse
2456:    * events.
2457:    */
2458:   public class MouseHandler
2459:     extends MouseAdapter
2460:     implements MouseMotionListener
2461:   {
2462:     
2463:     /**
2464:      * If the cell has been selected on mouse press.
2465:      */
2466:     private boolean selectedOnPress;
2467: 
2468:     /**
2469:      * Constructor
2470:      */
2471:     public MouseHandler()
2472:     {
2473:       // Nothing to do here.
2474:     }
2475: 
2476:     /**
2477:      * Invoked when a mouse button has been pressed on a component.
2478:      * 
2479:      * @param e is the mouse event that occured
2480:      */
2481:     public void mousePressed(MouseEvent e)
2482:     {
2483:       if (! e.isConsumed())
2484:         {
2485:           handleEvent(e);
2486:           selectedOnPress = true;
2487:         }
2488:       else
2489:         {
2490:           selectedOnPress = false;
2491:         }
2492:     }
2493: 
2494:     /**
2495:      * Invoked when a mouse button is pressed on a component and then dragged.
2496:      * MOUSE_DRAGGED events will continue to be delivered to the component where
2497:      * the drag originated until the mouse button is released (regardless of
2498:      * whether the mouse position is within the bounds of the component).
2499:      * 
2500:      * @param e is the mouse event that occured
2501:      */
2502:     public void mouseDragged(MouseEvent e)
2503:     {
2504:       // Nothing to do here.
2505:     }
2506: 
2507:     /**
2508:      * Invoked when the mouse button has been moved on a component (with no
2509:      * buttons no down).
2510:      * 
2511:      * @param e the mouse event that occured
2512:      */
2513:     public void mouseMoved(MouseEvent e)
2514:     {
2515:       // Nothing to do here.
2516:     }
2517: 
2518:     /**
2519:      * Invoked when a mouse button has been released on a component.
2520:      * 
2521:      * @param e is the mouse event that occured
2522:      */
2523:     public void mouseReleased(MouseEvent e)
2524:     {
2525:       if (! e.isConsumed() && ! selectedOnPress)
2526:         handleEvent(e);
2527:     }
2528: 
2529:     /**
2530:      * Handles press and release events.
2531:      *
2532:      * @param e the mouse event
2533:      */
2534:     private void handleEvent(MouseEvent e)
2535:     {
2536:       if (tree != null && tree.isEnabled())
2537:         {
2538:           // Maybe stop editing.
2539:           if (isEditing(tree) && tree.getInvokesStopCellEditing()
2540:               && ! stopEditing(tree))
2541:             return;
2542: 
2543:           // Explicitly request focus.
2544:           tree.requestFocusInWindow();
2545: 
2546:           int x = e.getX();
2547:           int y = e.getY();
2548:           TreePath path = getClosestPathForLocation(tree, x, y);
2549:           if (path != null)
2550:             {
2551:               Rectangle b = getPathBounds(tree, path);
2552:               if (y <= b.y + b.height)
2553:                 {
2554:                   if (SwingUtilities.isLeftMouseButton(e))
2555:                     checkForClickInExpandControl(path, x, y);
2556:                   if (x > b.x && x <= b.x + b.width)
2557:                     {
2558:                       if (! startEditing(path, e))
2559:                         selectPathForEvent(path, e);
2560:                     }
2561:                 }
2562:             }
2563:         }
2564:     }
2565:   }
2566: 
2567:   /**
2568:    * MouseInputHandler handles passing all mouse events, including mouse motion
2569:    * events, until the mouse is released to the destination it is constructed
2570:    * with.
2571:    */
2572:   public class MouseInputHandler
2573:       implements MouseInputListener
2574:   {
2575:     /** Source that events are coming from */
2576:     protected Component source;
2577: 
2578:     /** Destination that receives all events. */
2579:     protected Component destination;
2580: 
2581:     /**
2582:      * Constructor
2583:      * 
2584:      * @param source that events are coming from
2585:      * @param destination that receives all events
2586:      * @param e is the event received
2587:      */
2588:     public MouseInputHandler(Component source, Component destination,
2589:                              MouseEvent e)
2590:     {
2591:       this.source = source;
2592:       this.destination = destination;
2593:       source.addMouseListener(this);
2594:       source.addMouseMotionListener(this);
2595:       dispatch(e);
2596:     }
2597: 
2598:     /**
2599:      * Invoked when the mouse button has been clicked (pressed and released) on
2600:      * a component.
2601:      * 
2602:      * @param e mouse event that occured
2603:      */
2604:     public void mouseClicked(MouseEvent e)
2605:     {
2606:       dispatch(e);
2607:     }
2608: 
2609:     /**
2610:      * Invoked when a mouse button has been pressed on a component.
2611:      * 
2612:      * @param e mouse event that occured
2613:      */
2614:     public void mousePressed(MouseEvent e)
2615:     {
2616:       // Nothing to do here.
2617:     }
2618: 
2619:     /**
2620:      * Invoked when a mouse button has been released on a component.
2621:      * 
2622:      * @param e mouse event that occured
2623:      */
2624:     public void mouseReleased(MouseEvent e)
2625:     {
2626:       dispatch(e);
2627:       removeFromSource();
2628:     }
2629: 
2630:     /**
2631:      * Invoked when the mouse enters a component.
2632:      * 
2633:      * @param e mouse event that occured
2634:      */
2635:     public void mouseEntered(MouseEvent e)
2636:     {
2637:       if (! SwingUtilities.isLeftMouseButton(e))
2638:         removeFromSource();
2639:     }
2640: 
2641:     /**
2642:      * Invoked when the mouse exits a component.
2643:      * 
2644:      * @param e mouse event that occured
2645:      */
2646:     public void mouseExited(MouseEvent e)
2647:     {
2648:       if (! SwingUtilities.isLeftMouseButton(e))
2649:         removeFromSource();
2650:     }
2651: 
2652:     /**
2653:      * Invoked when a mouse button is pressed on a component and then dragged.
2654:      * MOUSE_DRAGGED events will continue to be delivered to the component where
2655:      * the drag originated until the mouse button is released (regardless of
2656:      * whether the mouse position is within the bounds of the component).
2657:      * 
2658:      * @param e mouse event that occured
2659:      */
2660:     public void mouseDragged(MouseEvent e)
2661:     {
2662:       dispatch(e);
2663:     }
2664: 
2665:     /**
2666:      * Invoked when the mouse cursor has been moved onto a component but no
2667:      * buttons have been pushed.
2668:      * 
2669:      * @param e mouse event that occured
2670:      */
2671:     public void mouseMoved(MouseEvent e)
2672:     {
2673:       removeFromSource();
2674:     }
2675: 
2676:     /**
2677:      * Removes event from the source
2678:      */
2679:     protected void removeFromSource()
2680:     {
2681:       if (source != null)
2682:         {
2683:           source.removeMouseListener(this);
2684:           source.removeMouseMotionListener(this);
2685:         }
2686:       source = null;
2687:       destination = null;
2688:     }
2689: 
2690:     /**
2691:      * Redispatches mouse events to the destination.
2692:      *
2693:      * @param e the mouse event to redispatch
2694:      */
2695:     private void dispatch(MouseEvent e)
2696:     {
2697:       if (destination != null)
2698:         {
2699:           MouseEvent e2 = SwingUtilities.convertMouseEvent(source, e,
2700:                                                            destination);
2701:           destination.dispatchEvent(e2);
2702:         }
2703:     }
2704:   }
2705: 
2706:   /**
2707:    * Class responsible for getting size of node, method is forwarded to
2708:    * BasicTreeUI method. X location does not include insets, that is handled in
2709:    * getPathBounds.
2710:    */
2711:   public class NodeDimensionsHandler
2712:       extends AbstractLayoutCache.NodeDimensions
2713:   {
2714:     /**
2715:      * Constructor
2716:      */
2717:     public NodeDimensionsHandler()
2718:     {
2719:       // Nothing to do here.
2720:     }
2721: 
2722:     /**
2723:      * Returns, by reference in bounds, the size and x origin to place value at.
2724:      * The calling method is responsible for determining the Y location. If
2725:      * bounds is null, a newly created Rectangle should be returned, otherwise
2726:      * the value should be placed in bounds and returned.
2727:      * 
2728:      * @param cell the value to be represented
2729:      * @param row row being queried
2730:      * @param depth the depth of the row
2731:      * @param expanded true if row is expanded
2732:      * @param size a Rectangle containing the size needed to represent value
2733:      * @return containing the node dimensions, or null if node has no dimension
2734:      */
2735:     public Rectangle getNodeDimensions(Object cell, int row, int depth,
2736:                                        boolean expanded, Rectangle size)
2737:     {
2738:       Dimension prefSize;
2739:       if (editingComponent != null && editingRow == row)
2740:         {
2741:           // Editing, ask editor for preferred size.
2742:           prefSize = editingComponent.getPreferredSize();
2743:           int rowHeight = getRowHeight();
2744:           if (rowHeight > 0 && rowHeight != prefSize.height)
2745:             prefSize.height = rowHeight;
2746:         }
2747:       else
2748:         {
2749:           // Not editing, ask renderer for preferred size.
2750:           Component rend =
2751:             currentCellRenderer.getTreeCellRendererComponent(tree, cell,
2752:                                                        tree.isRowSelected(row),
2753:                                                        expanded,
2754:                                                        treeModel.isLeaf(cell),
2755:                                                        row, false);
2756:           // Make sure the layout is valid.
2757:           rendererPane.add(rend);
2758:           rend.validate();
2759:           prefSize = rend.getPreferredSize();
2760:         }
2761:       if (size != null)
2762:         {
2763:           size.x = getRowX(row, depth);
2764:           // FIXME: This should be handled by the layout cache.
2765:           size.y = prefSize.height * row;
2766:           size.width =  prefSize.width;
2767:           size.height = prefSize.height;
2768:         }
2769:       else
2770:         // FIXME: The y should be handled by the layout cache.
2771:         size = new Rectangle(getRowX(row, depth), prefSize.height * row, prefSize.width,
2772:                              prefSize.height);
2773:       
2774:       return size;
2775:     }
2776: 
2777:     /**
2778:      * Returns the amount to indent the given row
2779:      * 
2780:      * @return amount to indent the given row.
2781:      */
2782:     protected int getRowX(int row, int depth)
2783:     {
2784:       return BasicTreeUI.this.getRowX(row, depth);
2785:     }
2786:   } // NodeDimensionsHandler
2787: 
2788:   /**
2789:    * PropertyChangeListener for the tree. Updates the appropriate variable, or
2790:    * TreeState, based on what changes.
2791:    */
2792:   public class PropertyChangeHandler
2793:       implements PropertyChangeListener
2794:   {
2795: 
2796:     /**
2797:      * Constructor
2798:      */
2799:     public PropertyChangeHandler()
2800:     {
2801:       // Nothing to do here.
2802:     }
2803: 
2804:     /**
2805:      * This method gets called when a bound property is changed.
2806:      * 
2807:      * @param event A PropertyChangeEvent object describing the event source and
2808:      *          the property that has changed.
2809:      */
2810:     public void propertyChange(PropertyChangeEvent event)
2811:     {
2812:       String property = event.getPropertyName();
2813:       if (property.equals(JTree.ROOT_VISIBLE_PROPERTY))
2814:         {
2815:           validCachedPreferredSize = false;
2816:           treeState.setRootVisible(tree.isRootVisible());
2817:           tree.repaint();
2818:         }
2819:       else if (property.equals(JTree.SELECTION_MODEL_PROPERTY))
2820:         {
2821:           treeSelectionModel = tree.getSelectionModel();
2822:           treeSelectionModel.setRowMapper(treeState);
2823:         }
2824:       else if (property.equals(JTree.TREE_MODEL_PROPERTY))
2825:         {
2826:           setModel(tree.getModel());
2827:         }
2828:       else if (property.equals(JTree.CELL_RENDERER_PROPERTY))
2829:         {
2830:           setCellRenderer(tree.getCellRenderer());
2831:           // Update layout.
2832:           if (treeState != null)
2833:             treeState.invalidateSizes();
2834:         }
2835:       else if (property.equals(JTree.EDITABLE_PROPERTY))
2836:         setEditable(((Boolean) event.getNewValue()).booleanValue());
2837:         
2838:     }
2839:   }
2840: 
2841:   /**
2842:    * Listener on the TreeSelectionModel, resets the row selection if any of the
2843:    * properties of the model change.
2844:    */
2845:   public class SelectionModelPropertyChangeHandler
2846:     implements PropertyChangeListener
2847:   {
2848: 
2849:     /**
2850:      * Constructor
2851:      */
2852:     public SelectionModelPropertyChangeHandler()
2853:     {
2854:       // Nothing to do here.
2855:     }
2856: 
2857:     /**
2858:      * This method gets called when a bound property is changed.
2859:      * 
2860:      * @param event A PropertyChangeEvent object describing the event source and
2861:      *          the property that has changed.
2862:      */
2863:     public void propertyChange(PropertyChangeEvent event)
2864:     {
2865:       treeSelectionModel.resetRowSelection();
2866:     }
2867:   }
2868: 
2869:   /**
2870:    * The action to cancel editing on this tree.
2871:    */
2872:   public class TreeCancelEditingAction
2873:       extends AbstractAction
2874:   {
2875:     /**
2876:      * Creates the new tree cancel editing action.
2877:      * 
2878:      * @param name the name of the action (used in toString).
2879:      */
2880:     public TreeCancelEditingAction(String name)
2881:     {
2882:       super(name);
2883:     }
2884: 
2885:     /**
2886:      * Invoked when an action occurs, cancels the cell editing (if the
2887:      * tree cell is being edited). 
2888:      * 
2889:      * @param e event that occured
2890:      */
2891:     public void actionPerformed(ActionEvent e)
2892:     {
2893:       if (isEnabled() && tree.isEditing())
2894:         tree.cancelEditing();
2895:     }
2896:   }
2897: 
2898:   /**
2899:    * Updates the TreeState in response to nodes expanding/collapsing.
2900:    */
2901:   public class TreeExpansionHandler
2902:       implements TreeExpansionListener
2903:   {
2904: 
2905:     /**
2906:      * Constructor
2907:      */
2908:     public TreeExpansionHandler()
2909:     {
2910:       // Nothing to do here.
2911:     }
2912: 
2913:     /**
2914:      * Called whenever an item in the tree has been expanded.
2915:      * 
2916:      * @param event is the event that occured
2917:      */
2918:     public void treeExpanded(TreeExpansionEvent event)
2919:     {
2920:       validCachedPreferredSize = false;
2921:       treeState.setExpandedState(event.getPath(), true);
2922:       // The maximal cell height may change
2923:       maxHeight = 0;
2924:       tree.revalidate();
2925:       tree.repaint();
2926:     }
2927: 
2928:     /**
2929:      * Called whenever an item in the tree has been collapsed.
2930:      * 
2931:      * @param event is the event that occured
2932:      */
2933:     public void treeCollapsed(TreeExpansionEvent event)
2934:     {
2935:       completeEditing();
2936:       validCachedPreferredSize = false;
2937:       treeState.setExpandedState(event.getPath(), false);
2938:       // The maximal cell height may change
2939:       maxHeight = 0;
2940:       tree.revalidate();
2941:       tree.repaint();
2942:     }
2943:   } // TreeExpansionHandler
2944: 
2945:   /**
2946:    * TreeHomeAction is used to handle end/home actions. Scrolls either the first
2947:    * or last cell to be visible based on direction.
2948:    */
2949:   public class TreeHomeAction
2950:       extends AbstractAction
2951:   {
2952: 
2953:     /** The direction, either home or end */
2954:     protected int direction;
2955: 
2956:     /**
2957:      * Creates a new TreeHomeAction instance.
2958:      * 
2959:      * @param dir the direction to go to, <code>-1</code> for home,
2960:      *        <code>1</code> for end
2961:      * @param name the name of the action
2962:      */
2963:     public TreeHomeAction(int dir, String name)
2964:     {
2965:       direction = dir;
2966:       putValue(Action.NAME, name);
2967:     }
2968: 
2969:     /**
2970:      * Invoked when an action occurs.
2971:      * 
2972:      * @param e is the event that occured
2973:      */
2974:     public void actionPerformed(ActionEvent e)
2975:     {
2976:       if (tree != null)
2977:         {
2978:           String command = (String) getValue(Action.NAME);
2979:           if (command.equals("selectFirst"))
2980:             {
2981:               ensureRowsAreVisible(0, 0);
2982:               tree.setSelectionInterval(0, 0);
2983:             }
2984:           if (command.equals("selectFirstChangeLead"))
2985:             {
2986:               ensureRowsAreVisible(0, 0);
2987:               tree.setLeadSelectionPath(getPathForRow(tree, 0));
2988:             }
2989:           if (command.equals("selectFirstExtendSelection"))
2990:             {
2991:               ensureRowsAreVisible(0, 0);
2992:               TreePath anchorPath = tree.getAnchorSelectionPath();
2993:               if (anchorPath == null)
2994:                 tree.setSelectionInterval(0, 0);
2995:               else
2996:                 {
2997:                   int anchorRow = getRowForPath(tree, anchorPath);
2998:                   tree.setSelectionInterval(0, anchorRow);
2999:                   tree.setAnchorSelectionPath(anchorPath);
3000:                   tree.setLeadSelectionPath(getPathForRow(tree, 0));
3001:                 }
3002:             }
3003:           else if (command.equals("selectLast"))
3004:             {
3005:               int end = getRowCount(tree) - 1;
3006:               ensureRowsAreVisible(end, end);
3007:               tree.setSelectionInterval(end, end);
3008:             }
3009:           else if (command.equals("selectLastChangeLead"))
3010:             {
3011:               int end = getRowCount(tree) - 1;
3012:               ensureRowsAreVisible(end, end);
3013:               tree.setLeadSelectionPath(getPathForRow(tree, end));
3014:             }
3015:           else if (command.equals("selectLastExtendSelection"))
3016:             {
3017:               int end = getRowCount(tree) - 1;
3018:               ensureRowsAreVisible(end, end);
3019:               TreePath anchorPath = tree.getAnchorSelectionPath();
3020:               if (anchorPath == null)
3021:                 tree.setSelectionInterval(end, end);
3022:               else
3023:                 {
3024:                   int anchorRow = getRowForPath(tree, anchorPath);
3025:                   tree.setSelectionInterval(end, anchorRow);
3026:                   tree.setAnchorSelectionPath(anchorPath);
3027:                   tree.setLeadSelectionPath(getPathForRow(tree, end));
3028:                 }
3029:             }
3030:         }
3031: 
3032:       // Ensure that the lead path is visible after the increment action.
3033:       tree.scrollPathToVisible(tree.getLeadSelectionPath());
3034:     }
3035: 
3036:     /**
3037:      * Returns true if the action is enabled.
3038:      * 
3039:      * @return true if the action is enabled.
3040:      */
3041:     public boolean isEnabled()
3042:     {
3043:       return (tree != null) && tree.isEnabled();
3044:     }
3045:   }
3046: 
3047:   /**
3048:    * TreeIncrementAction is used to handle up/down actions. Selection is moved
3049:    * up or down based on direction.
3050:    */
3051:   public class TreeIncrementAction
3052:     extends AbstractAction
3053:   {
3054: 
3055:     /**
3056:      * Specifies the direction to adjust the selection by.
3057:      */
3058:     protected int direction;
3059: 
3060:     /**
3061:      * Creates a new TreeIncrementAction.
3062:      * 
3063:      * @param dir up or down, <code>-1</code> for up, <code>1</code> for down
3064:      * @param name is the name of the direction
3065:      */
3066:     public TreeIncrementAction(int dir, String name)
3067:     {
3068:       direction = dir;
3069:       putValue(Action.NAME, name);
3070:     }
3071: 
3072:     /**
3073:      * Invoked when an action occurs.
3074:      * 
3075:      * @param e is the event that occured
3076:      */
3077:     public void actionPerformed(ActionEvent e)
3078:     {
3079:       TreePath currentPath = tree.getLeadSelectionPath();
3080:       int currentRow;
3081: 
3082:       if (currentPath != null)
3083:         currentRow = treeState.getRowForPath(currentPath);
3084:       else
3085:         currentRow = 0;
3086: 
3087:       int rows = treeState.getRowCount();
3088: 
3089:       int nextRow = currentRow + 1;
3090:       int prevRow = currentRow - 1;
3091:       boolean hasNext = nextRow < rows;
3092:       boolean hasPrev = prevRow >= 0 && rows > 0;
3093:       TreePath newPath;
3094:       String command = (String) getValue(Action.NAME);
3095: 
3096:       if (command.equals("selectPreviousChangeLead") && hasPrev)
3097:         {
3098:           newPath = treeState.getPathForRow(prevRow);
3099:           tree.setSelectionPath(newPath);
3100:           tree.setAnchorSelectionPath(newPath);
3101:           tree.setLeadSelectionPath(newPath);
3102:         }
3103:       else if (command.equals("selectPreviousExtendSelection") && hasPrev)
3104:         {
3105:           newPath = treeState.getPathForRow(prevRow);
3106: 
3107:           // If the new path is already selected, the selection shrinks,
3108:           // unselecting the previously current path.
3109:           if (tree.isPathSelected(newPath))
3110:             tree.getSelectionModel().removeSelectionPath(currentPath);
3111: 
3112:           // This must be called in any case because it updates the model
3113:           // lead selection index.
3114:           tree.addSelectionPath(newPath);
3115:           tree.setLeadSelectionPath(newPath);
3116:         }
3117:       else if (command.equals("selectPrevious") && hasPrev)
3118:         {
3119:           newPath = treeState.getPathForRow(prevRow);
3120:           tree.setSelectionPath(newPath);
3121:         }
3122:       else if (command.equals("selectNext") && hasNext)
3123:         {
3124:           newPath = treeState.getPathForRow(nextRow);
3125:           tree.setSelectionPath(newPath);
3126:         }
3127:       else if (command.equals("selectNextExtendSelection") && hasNext)
3128:         {
3129:           newPath = treeState.getPathForRow(nextRow);
3130: 
3131:           // If the new path is already selected, the selection shrinks,
3132:           // unselecting the previously current path.
3133:           if (tree.isPathSelected(newPath))
3134:             tree.getSelectionModel().removeSelectionPath(currentPath);
3135: 
3136:           // This must be called in any case because it updates the model
3137:           // lead selection index.
3138:           tree.addSelectionPath(newPath);
3139: 
3140:           tree.setLeadSelectionPath(newPath);
3141:         }
3142:       else if (command.equals("selectNextChangeLead") && hasNext)
3143:         {
3144:           newPath = treeState.getPathForRow(nextRow);
3145:           tree.setSelectionPath(newPath);
3146:           tree.setAnchorSelectionPath(newPath);
3147:           tree.setLeadSelectionPath(newPath);
3148:         }
3149:       
3150:       // Ensure that the lead path is visible after the increment action.
3151:       tree.scrollPathToVisible(tree.getLeadSelectionPath());
3152:     }
3153: 
3154:     /**
3155:      * Returns true if the action is enabled.
3156:      * 
3157:      * @return true if the action is enabled.
3158:      */
3159:     public boolean isEnabled()
3160:     {
3161:       return (tree != null) && tree.isEnabled();
3162:     }
3163:   }
3164: 
3165:   /**
3166:    * Forwards all TreeModel events to the TreeState.
3167:    */
3168:   public class TreeModelHandler
3169:       implements TreeModelListener
3170:   {
3171:     /**
3172:      * Constructor
3173:      */
3174:     public TreeModelHandler()
3175:     {
3176:       // Nothing to do here.
3177:     }
3178: 
3179:     /**
3180:      * Invoked after a node (or a set of siblings) has changed in some way. The
3181:      * node(s) have not changed locations in the tree or altered their children
3182:      * arrays, but other attributes have changed and may affect presentation.
3183:      * Example: the name of a file has changed, but it is in the same location
3184:      * in the file system. To indicate the root has changed, childIndices and
3185:      * children will be null. Use e.getPath() to get the parent of the changed
3186:      * node(s). e.getChildIndices() returns the index(es) of the changed
3187:      * node(s).
3188:      * 
3189:      * @param e is the event that occured
3190:      */
3191:     public void treeNodesChanged(TreeModelEvent e)
3192:     {
3193:       validCachedPreferredSize = false;
3194:       treeState.treeNodesChanged(e);
3195:       tree.repaint();
3196:     }
3197: 
3198:     /**
3199:      * Invoked after nodes have been inserted into the tree. Use e.getPath() to
3200:      * get the parent of the new node(s). e.getChildIndices() returns the
3201:      * index(es) of the new node(s) in ascending order.
3202:      * 
3203:      * @param e is the event that occured
3204:      */
3205:     public void treeNodesInserted(TreeModelEvent e)
3206:     {
3207:       validCachedPreferredSize = false;
3208:       treeState.treeNodesInserted(e);
3209:       tree.repaint();
3210:     }
3211: 
3212:     /**
3213:      * Invoked after nodes have been removed from the tree. Note that if a
3214:      * subtree is removed from the tree, this method may only be invoked once
3215:      * for the root of the removed subtree, not once for each individual set of
3216:      * siblings removed. Use e.getPath() to get the former parent of the deleted
3217:      * node(s). e.getChildIndices() returns, in ascending order, the index(es)
3218:      * the node(s) had before being deleted.
3219:      * 
3220:      * @param e is the event that occured
3221:      */
3222:     public void treeNodesRemoved(TreeModelEvent e)
3223:     {
3224:       validCachedPreferredSize = false;
3225:       treeState.treeNodesRemoved(e);
3226:       tree.repaint();
3227:     }
3228: 
3229:     /**
3230:      * Invoked after the tree has drastically changed structure from a given
3231:      * node down. If the path returned by e.getPath() is of length one and the
3232:      * first element does not identify the current root node the first element
3233:      * should become the new root of the tree. Use e.getPath() to get the path
3234:      * to the node. e.getChildIndices() returns null.
3235:      * 
3236:      * @param e is the event that occured
3237:      */
3238:     public void treeStructureChanged(TreeModelEvent e)
3239:     {
3240:       if (e.getPath().length == 1
3241:           && ! e.getPath()[0].equals(treeModel.getRoot()))
3242:         tree.expandPath(new TreePath(treeModel.getRoot()));
3243:       validCachedPreferredSize = false;
3244:       treeState.treeStructureChanged(e);
3245:       tree.repaint();
3246:     }
3247:   } // TreeModelHandler
3248: 
3249:   /**
3250:    * TreePageAction handles page up and page down events.
3251:    */
3252:   public class TreePageAction
3253:       extends AbstractAction
3254:   {
3255:     /** Specifies the direction to adjust the selection by. */
3256:     protected int direction;
3257: 
3258:     /**
3259:      * Constructor
3260:      * 
3261:      * @param direction up or down
3262:      * @param name is the name of the direction
3263:      */
3264:     public TreePageAction(int direction, String name)
3265:     {
3266:       this.direction = direction;
3267:       putValue(Action.NAME, name);
3268:     }
3269: 
3270:     /**
3271:      * Invoked when an action occurs.
3272:      * 
3273:      * @param e is the event that occured
3274:      */
3275:     public void actionPerformed(ActionEvent e)
3276:     {
3277:       String command = (String) getValue(Action.NAME);
3278:       boolean extendSelection = command.equals("scrollUpExtendSelection")
3279:                                 || command.equals("scrollDownExtendSelection");
3280:       boolean changeSelection = command.equals("scrollUpChangeSelection")
3281:                                 || command.equals("scrollDownChangeSelection");
3282: 
3283:       // Disable change lead, unless we are in discontinuous mode.
3284:       if (!extendSelection && !changeSelection
3285:           && tree.getSelectionModel().getSelectionMode() !=
3286:             TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
3287:         {
3288:           changeSelection = true;
3289:         }
3290: 
3291:       int rowCount = getRowCount(tree);
3292:       if (rowCount > 0 && treeSelectionModel != null)
3293:         {
3294:           Dimension maxSize = tree.getSize();
3295:           TreePath lead = tree.getLeadSelectionPath();
3296:           TreePath newPath = null;
3297:           Rectangle visible = tree.getVisibleRect();
3298:           if (direction == -1) // The RI handles -1 as up.
3299:             {
3300:               newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3301:               if (newPath.equals(lead)) // Corner case, adjust one page up.
3302:                 {
3303:                   visible.y = Math.max(0, visible.y - visible.height);
3304:                   newPath = getClosestPathForLocation(tree, visible.x,
3305:                                                       visible.y);
3306:                 }
3307:             }
3308:           else // +1 is down.
3309:             {
3310:               visible.y = Math.min(maxSize.height,
3311:                                    visible.y + visible.height - 1);
3312:               newPath = getClosestPathForLocation(tree, visible.x, visible.y);
3313:               if (newPath.equals(lead)) // Corner case, adjust one page down.
3314:                 {
3315:                   visible.y = Math.min(maxSize.height,
3316:                                        visible.y + visible.height - 1);
3317:                   newPath = getClosestPathForLocation(tree, visible.x,
3318:                                                       visible.y);
3319:                 }
3320:             }
3321: 
3322:           // Determine new visible rect.
3323:           Rectangle newVisible = getPathBounds(tree, newPath);
3324:           newVisible.x = visible.x;
3325:           newVisible.width = visible.width;
3326:           if (direction == -1)
3327:             {
3328:               newVisible.height = visible.height;
3329:             }
3330:           else
3331:             {
3332:               newVisible.y -= visible.height - newVisible.height;
3333:               newVisible.height = visible.height;
3334:             }
3335: 
3336:           if (extendSelection)
3337:             {
3338:               // Extend selection.
3339:               TreePath anchorPath = tree.getAnchorSelectionPath();
3340:               if (anchorPath == null)
3341:                 {
3342:                   tree.setSelectionPath(newPath);
3343:                 }
3344:               else
3345:                 {
3346:                   int newIndex = getRowForPath(tree, newPath);
3347:                   int anchorIndex = getRowForPath(tree, anchorPath);
3348:                   tree.setSelectionInterval(Math.min(anchorIndex, newIndex),
3349:                                             Math.max(anchorIndex, newIndex));
3350:                   tree.setAnchorSelectionPath(anchorPath);
3351:                   tree.setLeadSelectionPath(newPath);
3352:                 }
3353:             }
3354:           else if (changeSelection)
3355:             {
3356:               tree.setSelectionPath(newPath);
3357:             }
3358:           else // Change lead.
3359:             {
3360:               tree.setLeadSelectionPath(newPath);
3361:             }
3362: 
3363:           tree.scrollRectToVisible(newVisible);
3364:         }
3365:     }
3366: 
3367:     /**
3368:      * Returns true if the action is enabled.
3369:      * 
3370:      * @return true if the action is enabled.
3371:      */
3372:     public boolean isEnabled()
3373:     {
3374:       return (tree != null) && tree.isEnabled();
3375:     }
3376:   } // TreePageAction
3377: 
3378:   /**
3379:    * Listens for changes in the selection model and updates the display
3380:    * accordingly.
3381:    */
3382:   public class TreeSelectionHandler
3383:       implements TreeSelectionListener
3384:   {
3385:     /**
3386:      * Constructor
3387:      */
3388:     public TreeSelectionHandler()
3389:     {
3390:       // Nothing to do here.
3391:     }
3392: 
3393:     /**
3394:      * Messaged when the selection changes in the tree we're displaying for.
3395:      * Stops editing, messages super and displays the changed paths.
3396:      * 
3397:      * @param event the event that characterizes the change.
3398:      */
3399:     public void valueChanged(TreeSelectionEvent event)
3400:     {
3401:       completeEditing();
3402: 
3403:       TreePath op = event.getOldLeadSelectionPath();
3404:       TreePath np = event.getNewLeadSelectionPath();
3405:       
3406:       // Repaint of the changed lead selection path.
3407:       if (op != np)
3408:         {
3409:           Rectangle o = treeState.getBounds(event.getOldLeadSelectionPath(), 
3410:                                            new Rectangle());
3411:           Rectangle n = treeState.getBounds(event.getNewLeadSelectionPath(), 
3412:                                            new Rectangle());
3413:           
3414:           if (o != null)
3415:             tree.repaint(o);
3416:           if (n != null)
3417:             tree.repaint(n);
3418:         }
3419:     }
3420:   } // TreeSelectionHandler
3421: 
3422:   /**
3423:    * For the first selected row expandedness will be toggled.
3424:    */
3425:   public class TreeToggleAction
3426:       extends AbstractAction
3427:   {
3428:     /**
3429:      * Creates a new TreeToggleAction.
3430:      * 
3431:      * @param name is the name of <code>Action</code> field
3432:      */
3433:     public TreeToggleAction(String name)
3434:     {
3435:       putValue(Action.NAME, name);
3436:     }
3437: 
3438:     /**
3439:      * Invoked when an action occurs.
3440:      * 
3441:      * @param e the event that occured
3442:      */
3443:     public void actionPerformed(ActionEvent e)
3444:     {
3445:       int selected = tree.getLeadSelectionRow();
3446:       if (selected != -1 && isLeaf(selected))
3447:         {
3448:           TreePath anchorPath = tree.getAnchorSelectionPath();
3449:           TreePath leadPath = tree.getLeadSelectionPath();
3450:           toggleExpandState(getPathForRow(tree, selected));
3451:           // Need to do this, so that the toggling doesn't mess up the lead
3452:           // and anchor.
3453:           tree.setLeadSelectionPath(leadPath);
3454:           tree.setAnchorSelectionPath(anchorPath);
3455: 
3456:           // Ensure that the lead path is visible after the increment action.
3457:           tree.scrollPathToVisible(tree.getLeadSelectionPath());
3458:         }
3459:     }
3460: 
3461:     /**
3462:      * Returns true if the action is enabled.
3463:      * 
3464:      * @return true if the action is enabled, false otherwise
3465:      */
3466:     public boolean isEnabled()
3467:     {
3468:       return (tree != null) && tree.isEnabled();
3469:     }
3470:   } // TreeToggleAction
3471: 
3472:   /**
3473:    * TreeTraverseAction is the action used for left/right keys. Will toggle the
3474:    * expandedness of a node, as well as potentially incrementing the selection.
3475:    */
3476:   public class TreeTraverseAction
3477:       extends AbstractAction
3478:   {
3479:     /**
3480:      * Determines direction to traverse, 1 means expand, -1 means collapse.
3481:      */
3482:     protected int direction;
3483: 
3484:     /**
3485:      * Constructor
3486:      * 
3487:      * @param direction to traverse
3488:      * @param name is the name of the direction
3489:      */
3490:     public TreeTraverseAction(int direction, String name)
3491:     {
3492:       this.direction = direction;
3493:       putValue(Action.NAME, name);
3494:     }
3495: 
3496:     /**
3497:      * Invoked when an action occurs.
3498:      * 
3499:      * @param e the event that occured
3500:      */
3501:     public void actionPerformed(ActionEvent e)
3502:     {
3503:       TreePath current = tree.getLeadSelectionPath();
3504:       if (current == null)
3505:         return;
3506: 
3507:       String command = (String) getValue(Action.NAME);
3508:       if (command.equals("selectParent"))
3509:         {
3510:           if (current == null)
3511:             return;
3512: 
3513:           if (tree.isExpanded(current))
3514:             {
3515:               tree.collapsePath(current);
3516:             }
3517:           else
3518:             {
3519:               // If the node is not expanded (also, if it is a leaf node),
3520:               // we just select the parent. We do not select the root if it
3521:               // is not visible.
3522:               TreePath parent = current.getParentPath();
3523:               if (parent != null && 
3524:                   ! (parent.getPathCount() == 1 && ! tree.isRootVisible()))
3525:                 tree.setSelectionPath(parent);
3526:             }
3527:         }
3528:       else if (command.equals("selectChild"))
3529:         {
3530:           Object node = current.getLastPathComponent();
3531:           int nc = treeModel.getChildCount(node);
3532:           if (nc == 0 || treeState.isExpanded(current))
3533:             {
3534:               // If the node is leaf or it is already expanded,
3535:               // we just select the next row.
3536:               int nextRow = tree.getLeadSelectionRow() + 1;
3537:               if (nextRow <= tree.getRowCount())
3538:                 tree.setSelectionRow(nextRow);
3539:             }
3540:           else
3541:             {
3542:               tree.expandPath(current);
3543:             }
3544:         }
3545:       
3546:       // Ensure that the lead path is visible after the increment action.
3547:       tree.scrollPathToVisible(tree.getLeadSelectionPath());
3548:     }
3549: 
3550:     /**
3551:      * Returns true if the action is enabled.
3552:      * 
3553:      * @return true if the action is enabled, false otherwise
3554:      */
3555:     public boolean isEnabled()
3556:     {
3557:       return (tree != null) && tree.isEnabled();
3558:     }
3559:   }
3560: 
3561:   /**
3562:    * Returns true if the LookAndFeel implements the control icons. Package
3563:    * private for use in inner classes.
3564:    * 
3565:    * @returns true if there are control icons
3566:    */
3567:   boolean hasControlIcons()
3568:   {
3569:     if (expandedIcon != null || collapsedIcon != null)
3570:       return true;
3571:     return false;
3572:   }
3573: 
3574:   /**
3575:    * Returns control icon. It is null if the LookAndFeel does not implements the
3576:    * control icons. Package private for use in inner classes.
3577:    * 
3578:    * @return control icon if it exists.
3579:    */
3580:   Icon getCurrentControlIcon(TreePath path)
3581:   {
3582:     if (hasControlIcons())
3583:       {
3584:         if (tree.isExpanded(path))
3585:           return expandedIcon;
3586:         else
3587:           return collapsedIcon;
3588:       }
3589:     else
3590:       {
3591:         if (nullIcon == null)
3592:           nullIcon = new Icon()
3593:           {
3594:             public int getIconHeight()
3595:             {
3596:               return 0;
3597:             }
3598: 
3599:             public int getIconWidth()
3600:             {
3601:               return 0;
3602:             }
3603: 
3604:             public void paintIcon(Component c, Graphics g, int x, int y)
3605:             {
3606:               // No action here.
3607:             }
3608:           };
3609:         return nullIcon;
3610:       }
3611:   }
3612: 
3613:   /**
3614:    * Returns the parent of the current node
3615:    * 
3616:    * @param root is the root of the tree
3617:    * @param node is the current node
3618:    * @return is the parent of the current node
3619:    */
3620:   Object getParent(Object root, Object node)
3621:   {
3622:     if (root == null || node == null || root.equals(node))
3623:       return null;
3624: 
3625:     if (node instanceof TreeNode)
3626:       return ((TreeNode) node).getParent();
3627:     return findNode(root, node);
3628:   }
3629: 
3630:   /**
3631:    * Recursively checks the tree for the specified node, starting at the root.
3632:    * 
3633:    * @param root is starting node to start searching at.
3634:    * @param node is the node to search for
3635:    * @return the parent node of node
3636:    */
3637:   private Object findNode(Object root, Object node)
3638:   {
3639:     if (! treeModel.isLeaf(root) && ! root.equals(node))
3640:       {
3641:         int size = treeModel.getChildCount(root);
3642:         for (int j = 0; j < size; j++)
3643:           {
3644:             Object child = treeModel.getChild(root, j);
3645:             if (node.equals(child))
3646:               return root;
3647: 
3648:             Object n = findNode(child, node);
3649:             if (n != null)
3650:               return n;
3651:           }
3652:       }
3653:     return null;
3654:   }
3655: 
3656:   /**
3657:    * Selects the specified path in the tree depending on modes. Package private
3658:    * for use in inner classes.
3659:    * 
3660:    * @param tree is the tree we are selecting the path in
3661:    * @param path is the path we are selecting
3662:    */
3663:   void selectPath(JTree tree, TreePath path)
3664:   {
3665:     if (path != null)
3666:       {
3667:         tree.setSelectionPath(path);
3668:         tree.setLeadSelectionPath(path);        
3669:         tree.makeVisible(path);
3670:         tree.scrollPathToVisible(path);
3671:       }
3672:   }
3673: 
3674:   /**
3675:    * Returns the path from node to the root. Package private for use in inner
3676:    * classes.
3677:    * 
3678:    * @param node the node to get the path to
3679:    * @param depth the depth of the tree to return a path for
3680:    * @return an array of tree nodes that represent the path to node.
3681:    */
3682:   Object[] getPathToRoot(Object node, int depth)
3683:   {
3684:     if (node == null)
3685:       {
3686:         if (depth == 0)
3687:           return null;
3688: 
3689:         return new Object[depth];
3690:       }
3691: 
3692:     Object[] path = getPathToRoot(getParent(treeModel.getRoot(), node),
3693:                                   depth + 1);
3694:     path[path.length - depth - 1] = node;
3695:     return path;
3696:   }
3697: 
3698:   /**
3699:    * Draws a vertical line using the given graphic context
3700:    * 
3701:    * @param g is the graphic context
3702:    * @param c is the component the new line will belong to
3703:    * @param x is the horizonal position
3704:    * @param top specifies the top of the line
3705:    * @param bottom specifies the bottom of the line
3706:    */
3707:   protected void paintVerticalLine(Graphics g, JComponent c, int x, int top,
3708:                                    int bottom)
3709:   {
3710:     // FIXME: Check if drawing a dashed line or not.
3711:     g.setColor(getHashColor());
3712:     g.drawLine(x, top, x, bottom);
3713:   }
3714: 
3715:   /**
3716:    * Draws a horizontal line using the given graphic context
3717:    * 
3718:    * @param g is the graphic context
3719:    * @param c is the component the new line will belong to
3720:    * @param y is the vertical position
3721:    * @param left specifies the left point of the line
3722:    * @param right specifies the right point of the line
3723:    */
3724:   protected void paintHorizontalLine(Graphics g, JComponent c, int y, int left,
3725:                                      int right)
3726:   {
3727:     // FIXME: Check if drawing a dashed line or not.
3728:     g.setColor(getHashColor());
3729:     g.drawLine(left, y, right, y);
3730:   }
3731: 
3732:   /**
3733:    * Draws an icon at around a specific position
3734:    * 
3735:    * @param c is the component the new line will belong to
3736:    * @param g is the graphic context
3737:    * @param icon is the icon which will be drawn
3738:    * @param x is the center position in x-direction
3739:    * @param y is the center position in y-direction
3740:    */
3741:   protected void drawCentered(Component c, Graphics g, Icon icon, int x, int y)
3742:   {
3743:     x -= icon.getIconWidth() / 2;
3744:     y -= icon.getIconHeight() / 2;
3745: 
3746:     if (x < 0)
3747:       x = 0;
3748:     if (y < 0)
3749:       y = 0;
3750: 
3751:     icon.paintIcon(c, g, x, y);
3752:   }
3753: 
3754:   /**
3755:    * Draws a dashed horizontal line.
3756:    * 
3757:    * @param g - the graphics configuration.
3758:    * @param y - the y location to start drawing at
3759:    * @param x1 - the x location to start drawing at
3760:    * @param x2 - the x location to finish drawing at
3761:    */
3762:   protected void drawDashedHorizontalLine(Graphics g, int y, int x1, int x2)
3763:   {
3764:     g.setColor(getHashColor());
3765:     for (int i = x1; i < x2; i += 2)
3766:       g.drawLine(i, y, i + 1, y);
3767:   }
3768: 
3769:   /**
3770:    * Draws a dashed vertical line.
3771:    * 
3772:    * @param g - the graphics configuration.
3773:    * @param x - the x location to start drawing at
3774:    * @param y1 - the y location to start drawing at
3775:    * @param y2 - the y location to finish drawing at
3776:    */
3777:   protected void drawDashedVerticalLine(Graphics g, int x, int y1, int y2)
3778:   {
3779:     g.setColor(getHashColor());
3780:     for (int i = y1; i < y2; i += 2)
3781:       g.drawLine(x, i, x, i + 1);
3782:   }
3783: 
3784:   /**
3785:    * Paints the expand (toggle) part of a row. The receiver should NOT modify
3786:    * clipBounds, or insets.
3787:    * 
3788:    * @param g - the graphics configuration
3789:    * @param clipBounds -
3790:    * @param insets -
3791:    * @param bounds - bounds of expand control
3792:    * @param path - path to draw control for
3793:    * @param row - row to draw control for
3794:    * @param isExpanded - is the row expanded
3795:    * @param hasBeenExpanded - has the row already been expanded
3796:    * @param isLeaf - is the path a leaf
3797:    */
3798:   protected void paintExpandControl(Graphics g, Rectangle clipBounds,
3799:                                     Insets insets, Rectangle bounds,
3800:                                     TreePath path, int row, boolean isExpanded,
3801:                                     boolean hasBeenExpanded, boolean isLeaf)
3802:   {
3803:     if (shouldPaintExpandControl(path, row, isExpanded, hasBeenExpanded, isLeaf))
3804:       {
3805:         Icon icon = getCurrentControlIcon(path);
3806:         int iconW = icon.getIconWidth();
3807:         int x = bounds.x - iconW - gap;
3808:         icon.paintIcon(tree, g, x, bounds.y + bounds.height / 2
3809:                                    - icon.getIconHeight() / 2);
3810:       }
3811:   }
3812: 
3813:   /**
3814:    * Paints the horizontal part of the leg. The receiver should NOT modify
3815:    * clipBounds, or insets. NOTE: parentRow can be -1 if the root is not
3816:    * visible.
3817:    * 
3818:    * @param g - the graphics configuration
3819:    * @param clipBounds -
3820:    * @param insets -
3821:    * @param bounds - bounds of the cell
3822:    * @param path - path to draw leg for
3823:    * @param row - row to start drawing at
3824:    * @param isExpanded - is the row expanded
3825:    * @param hasBeenExpanded - has the row already been expanded
3826:    * @param isLeaf - is the path a leaf
3827:    */
3828:   protected void paintHorizontalPartOfLeg(Graphics g, Rectangle clipBounds,
3829:                                           Insets insets, Rectangle bounds,
3830:                                           TreePath path, int row,
3831:                                           boolean isExpanded,
3832:                                           boolean hasBeenExpanded,
3833:                                           boolean isLeaf)
3834:   {
3835:     if (row != 0)
3836:       {
3837:         paintHorizontalLine(g, tree, bounds.y + bounds.height / 2,
3838:                             bounds.x - leftChildIndent - gap, bounds.x - gap);
3839:       }
3840:   }
3841: 
3842:   /**
3843:    * Paints the vertical part of the leg. The receiver should NOT modify
3844:    * clipBounds, insets.
3845:    * 
3846:    * @param g - the graphics configuration.
3847:    * @param clipBounds -
3848:    * @param insets -
3849:    * @param path - the path to draw the vertical part for.
3850:    */
3851:   protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
3852:                                         Insets insets, TreePath path)
3853:   {
3854:     Rectangle bounds = getPathBounds(tree, path);
3855:     TreePath parent = path.getParentPath();
3856:     
3857:     boolean paintLine;
3858:     if (isRootVisible())
3859:       paintLine = parent != null;
3860:     else
3861:       paintLine = parent != null && parent.getPathCount() > 1;
3862:     if (paintLine)
3863:       {
3864:         Rectangle parentBounds = getPathBounds(tree, parent);
3865:         paintVerticalLine(g, tree, parentBounds.x + 2 * gap, 
3866:                           parentBounds.y + parentBounds.height / 2,
3867:                           bounds.y + bounds.height / 2);
3868:       }
3869:   }
3870: 
3871:   /**
3872:    * Paints the renderer part of a row. The receiver should NOT modify
3873:    * clipBounds, or insets.
3874:    * 
3875:    * @param g - the graphics configuration
3876:    * @param clipBounds -
3877:    * @param insets -
3878:    * @param bounds - bounds of expand control
3879:    * @param path - path to draw control for
3880:    * @param row - row to draw control for
3881:    * @param isExpanded - is the row expanded
3882:    * @param hasBeenExpanded - has the row already been expanded
3883:    * @param isLeaf - is the path a leaf
3884:    */
3885:   protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
3886:                           Rectangle bounds, TreePath path, int row,
3887:                           boolean isExpanded, boolean hasBeenExpanded,
3888:                           boolean isLeaf)
3889:   {
3890:     boolean selected = tree.isPathSelected(path);
3891:     boolean hasIcons = false;
3892:     Object node = path.getLastPathComponent();
3893: 
3894:     paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded,
3895:                        hasBeenExpanded, isLeaf);
3896: 
3897:     TreeCellRenderer dtcr = currentCellRenderer;
3898: 
3899:     boolean focused = false;
3900:     if (treeSelectionModel != null)
3901:       focused = treeSelectionModel.getLeadSelectionRow() == row
3902:                 && tree.isFocusOwner();
3903: 
3904:     Component c = dtcr.getTreeCellRendererComponent(tree, node, selected,
3905:                                                     isExpanded, isLeaf, row,
3906:                                                     focused);
3907: 
3908:     rendererPane.paintComponent(g, c, c.getParent(), bounds);
3909:   }
3910: 
3911:   /**
3912:    * Prepares for the UI to uninstall.
3913:    */
3914:   protected void prepareForUIUninstall()
3915:   {
3916:     // Nothing to do here yet.
3917:   }
3918: 
3919:   /**
3920:    * Returns true if the expand (toggle) control should be drawn for the
3921:    * specified row.
3922:    * 
3923:    * @param path - current path to check for.
3924:    * @param row - current row to check for.
3925:    * @param isExpanded - true if the path is expanded
3926:    * @param hasBeenExpanded - true if the path has been expanded already
3927:    * @param isLeaf - true if the row is a lead
3928:    */
3929:   protected boolean shouldPaintExpandControl(TreePath path, int row,
3930:                                              boolean isExpanded,
3931:                                              boolean hasBeenExpanded,
3932:                                              boolean isLeaf)
3933:   {
3934:     Object node = path.getLastPathComponent();
3935:     return ! isLeaf && hasControlIcons();
3936:   }
3937: 
3938:   /**
3939:    * Returns the amount to indent the given row
3940:    * 
3941:    * @return amount to indent the given row.
3942:    */
3943:   protected int getRowX(int row, int depth)
3944:   {
3945:     return depth * totalChildIndent;
3946:   }
3947: } // BasicTreeUI