Source for javax.swing.plaf.basic.BasicTreeUI

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