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