Source for javax.swing.plaf.basic.BasicTreeUI

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