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