Source for javax.swing.plaf.basic.BasicTreeUI

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