Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)
Frames | No Frames

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

AltStyle によって変換されたページ (->オリジナル) /