OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libjava/] [classpath/] [javax/] [swing/] [plaf/] [basic/] [BasicTreeUI.java] - Blame information for rev 772

Details | Compare with Previous | View Log

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

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.