Source code: com/flexstor/common/awt/tree/Tree.java
1 /*
2 * Tree.java
3 *
4 * Copyright $Date: 2003/08/11 02:22:36 $ FLEXSTOR.net Inc.
5 *
6 * This work is licensed for use and distribution under license terms found at
7 * http://www.flexstor.org/license.html
8 *
9 */
10
11 package com.flexstor.common.awt.tree;
12
13 import java.awt.Color;
14 import java.awt.Dimension;
15 import java.awt.Graphics;
16 import java.awt.Image;
17 import java.awt.Panel;
18 import java.awt.Rectangle;
19 import java.awt.Scrollbar;
20 import java.awt.SystemColor;
21 import java.awt.event.ActionListener;
22 import java.awt.event.AdjustmentEvent;
23 import java.awt.event.AdjustmentListener;
24 import java.awt.event.InputEvent;
25 import java.awt.event.KeyEvent;
26 import java.awt.event.KeyListener;
27 import java.awt.event.MouseEvent;
28 import java.awt.event.MouseListener;
29 import java.util.ArrayList;
30
31 import com.flexstor.common.awt.ActionMulticaster;
32
33
34 /**
35 * The ProtoView Tree control is designed to display and navigate through a hierarchical
36 * list of items. Items in the tree are referred to as nodes. The FlexTreeNode class which
37 * accompanies the Tree is used to refer to individual nodes of the tree.
38 *
39 * @version 3.0
40 * @author Don Preuninger (Proto View)
41 * @author Viktor Snezhko (Proto View)
42 * @author Dan Schroeder (Rorke Data/FLEXSTOR.net)
43 */
44 public class Tree
45 extends Panel
46 implements KeyListener, AdjustmentListener, MouseListener
47 {
48 /**
49 * HitTest constant returned by getHitTestFlags when the x,y coordinates to
50 * the HitTest method were not over a populated area of the tree.
51 */
52 public static final int HITTEST_NONE = 1;
53
54 /**
55 * HitTest constant returned by getHitTestFlags when the x,y coordinates to
56 * the HitTest method were over the margin area of a node.
57 */
58 public static final int HITTEST_MARGIN = 2;
59
60 /**
61 * HitTest constant returned by getHitTestFlags when the x,y coordinates to
62 * the HitTest method were over a plus/minus button of a node.
63 */
64 public static final int HITTEST_BUTTON = 4;
65
66 /**
67 * HitTest constant returned by getHitTestFlags when the x,y coordinates to
68 * the HitTest method were over the image area of a node.
69 */
70 public static final int HITTEST_IMAGE = 8;
71
72 /**
73 * HitTest constant returned by getHitTestFlags when the x,y coordinates to
74 * the HitTest method were over the text area of a node.
75 */
76 public static final int HITTEST_TEXT = 16;
77
78 /**
79 * HitTest constant returned by getHitTestFlags when the x,y coordinates to
80 * the HitTest method were over the area to the right of the node text.
81 */
82 public static final int HITTEST_TEXT_RIGHT = 32;
83
84 /**
85 * Filler Panel to server as RightBottom corner of tree panel when both
86 * scrollbars are visible
87 */
88 private Panel pCorner = new Panel();
89
90 private Scrollbar m_vscroll;
91 private Scrollbar m_hscroll;
92 protected TreeSelectionI selection;
93 protected ActionMulticaster actionMulticaster;
94
95 protected Image m_imPlus = null;
96 protected Image m_imMinus = null;
97 private Image m_im = null;
98 private FlexTreeNode rootNode = null;
99 protected FlexTreeNode firstVisNode = null;
100 private FlexTreeNode lastVisNode = null;
101
102 private int nTreeId = -1;
103 private int m_iVisCount = 0;
104 protected int m_iLinesPerPage = 0;
105 protected int m_iHScroll = 0;
106 private int m_iVScroll = 0;
107 private int m_iMaxLineLen = 0;
108 private int nHitTest = 0;
109 protected int m_iIndent = 17; // The horizontal shift of a child node from its parent.
110 protected int m_iLineHeight = 18;
111 private long nLastScrollTime = -1;
112 private boolean bShowLines = true;
113 private boolean bAllowPaint = true;
114 private boolean bLayoutInProgress = false;
115 private boolean bPaintInProgress = false;
116
117 private Color bkgndClr = null;
118
119 /**
120 * Constuctor.
121 */
122 public Tree()
123 {
124 setLayout ( null );
125 setMultiSelection ( true );
126
127 m_vscroll = new Scrollbar ( Scrollbar.VERTICAL );
128 m_vscroll.setUnitIncrement ( 1 );
129 m_vscroll.setVisible ( false );
130 m_vscroll.addAdjustmentListener ( this );
131 add ( m_vscroll );
132
133 m_hscroll = new Scrollbar ( Scrollbar.HORIZONTAL );
134 m_hscroll.setUnitIncrement ( 5 );
135 m_hscroll.setBlockIncrement ( 70 );
136 m_hscroll.setVisible ( false );
137 m_hscroll.addAdjustmentListener ( this );
138 add ( m_hscroll );
139 add(pCorner);
140
141 super.addKeyListener ( this );
142 m_vscroll.addKeyListener ( this );
143 m_hscroll.addKeyListener ( this );
144
145 addMouseListener ( this );
146
147 nTreeId = TreeNotifier.registerTree ( this );
148 actionMulticaster = new ActionMulticaster();
149
150 rootNode = new FlexTreeNode ( null, null, null );
151 rootNode.setAsTrueRoot ( nTreeId );
152 firstVisNode = null;
153
154 setBackgroundColor ( SystemColor.control );
155 setForeground ( Color.black );
156 setPlusImage ( null );
157 setMinusImage ( null );
158 }
159
160 protected int getTreeId ( )
161 {
162 return nTreeId;
163 }
164
165 public void setBackgroundColor(Color bkgnd)
166 {
167 bkgndClr = bkgnd;
168 }
169
170 /**
171 * Ensures that the first visible node does not have its removed flag set to true.
172 * This can happen when the first visible item in the tree is delted (Bug #6565)
173 */
174 public void checkFirstVisibleNode ( )
175 {
176 if ( (firstVisNode != null) && (firstVisNode.getRemoved()) )
177 {
178 if ( rootNode == null )
179 firstVisNode = null;
180 else
181 firstVisNode = rootNode.getFirstChild();
182 }
183 }
184
185 public void removeNotify()
186 {
187 super.removeNotify();
188 TreeNotifier.unregisterTree ( this );
189
190 if ( actionMulticaster != null )
191 {
192 actionMulticaster.removeAll();
193 actionMulticaster = null;
194 }
195 }
196
197 public void setMultiSelection ( boolean bState )
198 {
199 if ( bState )
200 selection = new TreeMultiSelection(this);
201 else
202 selection = new TreeSingleSelection();
203 }
204
205 /**
206 * Get the first visible node of the tree. This is the first
207 * node that is visible if the tree is scrolled all the way to the top.
208 * This method will return null if there are no nodes in the tree. Visible nodes
209 * are nodes which are at the root level, or child nodes whose anscestors are all
210 * expanded.
211 * @return First visible node.
212 * @see #getNextVisibleNode
213 */
214 public FlexTreeNode getFirstVisibleNode()
215 {
216 return rootNode.nFirstChild;
217 }
218
219 /**
220 * Set the first node that is visible at the top of the tree window to the
221 * specified node. Here, visible is meant literally as the first line that the
222 * user can see at the top of the component.
223 * @param Valid node.
224 * @see #getSelectedNode
225 * @see #getRootNode
226 */
227 public void setFirstVisible(FlexTreeNode node)
228 {
229 if(node == null)
230 return;
231 firstVisNode = node;
232 repaint ( false );
233 }
234
235 /**
236 * Get the root node of the tree. There may be multiple nodes at the root level,
237 * in which case this method returns the first one. The method will return null if
238 * the tree is empty.
239 * @return Root node.
240 * @see #getSelectedNode
241 */
242 public FlexTreeNode getRootNode()
243 {
244 return rootNode.nFirstChild;
245 }
246
247 protected FlexTreeNode getTrueRootNode()
248 {
249 return rootNode;
250 }
251
252 /**
253 * Return true if node is visible at the time.
254 */
255 public boolean isNodeVisible(FlexTreeNode node)
256 {
257 if(node == null)
258 return false;
259 FlexTreeNode n = firstVisNode;
260 for(int i = 0; i < m_iLinesPerPage; i++)
261 {
262 if(n == node)
263 return true;
264 if(n == null)
265 return false;
266 n = n.getNextVisible();
267 }
268 return false;
269 }
270
271 /**
272 * Make the specified node to be visible in the window.
273 * @param node instance of FlexTreeNode.
274 * @return The visible index of a node starting from the very top of tree.
275 */
276 public int ensureVisible(FlexTreeNode node)
277 {
278 if(node == null)
279 return -1;
280 FlexTreeNode n = rootNode;
281 //-------------------
282 // visibility of selected node
283 int i;
284 int iFirstVis = -1;
285 //-------------------
286 // find matching node
287 for(i = 0; i < m_iVisCount; i++)
288 {
289 if((n = n.getNextVisible()) == null)
290 break;
291 if(n == firstVisNode)
292 iFirstVis = 0;
293 if(iFirstVis >= 0)
294 iFirstVis++;
295 //-------------------
296 // find match
297 if(n == node)
298 break;
299 }
300 i--;
301 //-------------------
302 // check and adjust visibility of node
303 if(iFirstVis >= 0 && iFirstVis <= m_iLinesPerPage)
304 return i;
305 if(iFirstVis < 0)
306 {
307 while(firstVisNode != node)
308 {
309 if(firstVisNode.getPrevVisible() == null)
310 break;
311 firstVisNode = firstVisNode.getPrevVisible();
312 if(m_iVScroll > 0)
313 m_vscroll.setValue(--m_iVScroll);
314 if(lastVisNode.getPrevVisible() != null)
315 lastVisNode = lastVisNode.getPrevVisible();
316 }
317 }
318 else
319 {
320 iFirstVis -= m_iLinesPerPage;
321 while(iFirstVis-- > 0)
322 {
323 if(lastVisNode.getNextVisible() == null)
324 break;
325 lastVisNode = lastVisNode.getNextVisible();
326 if(firstVisNode.getNextVisible() == null)
327 break;
328 firstVisNode = firstVisNode.getNextVisible();
329 if(m_iVScroll + m_iLinesPerPage < m_iVisCount)
330 m_vscroll.setValue(++m_iVScroll);
331 }
332 }
333
334 repaint ( false );
335 return i;
336 }
337
338 /**
339 * Perform mouse position testing to determine where the mouse pointer
340 * is located with respect to the nodes in the tree.
341 * @param x the horizontal position to test for
342 * @param y the veritcal position to test for
343 * @return The node that is at the x,y coordinates. The null is returned if the coordinates are not
344 * contained on a line with any node.
345 */
346 public FlexTreeNode hitTest(int x, int y)
347 {
348 nHitTest = 0;
349 int line = y / m_iLineHeight + 1;
350
351 if ( firstVisNode == null )
352 {
353 nHitTest = HITTEST_NONE;
354 return null;
355 }
356
357 FlexTreeNode node = firstVisNode.nodeAtLine( line );
358 if ( node != null )
359 {
360 int nodeline = node.lineFromNode( this );
361 if ( line != nodeline )
362 {
363 nHitTest = HITTEST_NONE;
364 return null;
365 }
366
367 int margin = m_iIndent;
368 margin *= node.getLevel();
369 margin -= m_iHScroll;
370
371 if ( x <= margin )
372 {
373 nHitTest = HITTEST_MARGIN;
374 return node;
375 }
376
377 if ( node.getAlwaysShowPlusMinus() || node.nFirstChild != null )
378 {
379 if( x < ( margin + 16 ) )
380 {
381 nHitTest = HITTEST_BUTTON;
382 return node;
383 }
384 }
385 else if ( x < ( margin + 16 ) )
386 {
387 nHitTest = HITTEST_MARGIN;
388 return node;
389 }
390
391 margin += 11 + m_imPlus.getWidth( null );
392
393 Image image = node.isExpanded() ? node.getExpandedImage() : node.getCollapsedImage();
394 int nImageWidth = image.getWidth( this );
395 if ( x <= ( margin + nImageWidth ) )
396 {
397 nHitTest = HITTEST_IMAGE;
398 return node;
399 }
400
401 margin += nImageWidth;
402
403 if ( x > margin )
404 {
405 if ( x > ( margin + 5 + getFontMetrics( getFont() ).stringWidth( node.getLabel() ) ) )
406 nHitTest = HITTEST_TEXT_RIGHT;
407 else
408 nHitTest = HITTEST_TEXT;
409
410 return node;
411 }
412 }
413
414 return null;
415 }
416
417 /**
418 * Set an option to display lines between nodes and images.
419 * @param enable indicates whether node connecting lines should be displayed or not.
420 */
421 public void setLines ( boolean enable )
422 {
423 bShowLines = enable;
424 }
425
426 /**
427 * Get an option to display lines between nodes and images.
428 * @return An option.
429 */
430 public boolean getLines()
431 {
432 return bShowLines;
433 }
434
435 /**
436 * Get an option to redraw tree on any modification of tree structure.
437 * @return An option.
438 */
439 public boolean getRedraw()
440 {
441 return bAllowPaint;
442 }
443
444 /**
445 * Set an option to redraw tree on any modification of tree structure.
446 * This property can be used when performing a large number of
447 * insertions or deletions of nodes by setting this property to false.
448 * @param enable an option.
449 */
450 public void setRedraw(boolean enable)
451 {
452 bAllowPaint = enable;
453 }
454
455 /**
456 * Get the number of items that are expanded and visible when scrolling
457 * through tree. It is not just the number of nodes visible on the screen
458 * at the current moment.
459 * @return Number of visible nodes.
460 * @see #getCount
461 */
462 public int getVisibleCount()
463 {
464 int count = 0;
465 FlexTreeNode n = rootNode;
466 if(n.nFirstChild != null)
467 {
468 n = n.nFirstChild;
469 while(n != null)
470 {
471 n = n.getNextVisible();
472 count++;
473 }
474 }
475 return count;
476 }
477
478 private int nodeWidth(FlexTreeNode node)
479 {
480 Image image = node.isExpanded() ? node.getExpandedImage() : node.getCollapsedImage();
481 int length = 16 + image.getWidth(this) +(node.getLevel() * m_iIndent) + m_imPlus.getWidth(this);
482
483 if(node.getLabel() != null)
484 length += getFontMetrics(getFont()).stringWidth(node.getLabel());
485 return length;
486 }
487
488 /**
489 * Set the image of the "plus" button.
490 * @param image new image.
491 * @see #setMinusImage
492 */
493 public void setPlusImage ( Image image )
494 {
495 if ( image == null )
496 m_imPlus = (new DefaultTreeImages()).getPlusImage();
497 else
498 m_imPlus = image;
499 }
500
501 /**
502 * Set the image of the "minus" button.
503 * @param image new image
504 * @see #setPlusImage
505 */
506 public void setMinusImage ( Image image )
507 {
508 if ( image == null )
509 m_imMinus = (new DefaultTreeImages()).getMinusImage();
510 else
511 m_imMinus = image;
512 }
513
514 /**
515 * Add new node to level 0
516 * It is a wrapper for addNode(null, FlexTreeNode.LAST_SIBLING, text, iImage, iSelImage)
517 * @param text text of the node
518 * @param iImage id of image in the indexed image list that will be displayed when
519 * node is not selected. In case of -1, the image is calculated automatically: leaf node
520 * if node has no children or image at the index "0" if node has a child.
521 * @param iSelImage id of image in the indexed image list that will be displayed when
522 * node is selected. In case of -1, the image is calculated automatically: leaf node
523 * if node has no children or image at the index "1" if node has a child.
524 * @see #setDefaultImages
525 * @see #setDefaultImages
526 * @see #addNode(FlexTreeNode,int,String,int,int)
527 */
528 public void addRootNode ( FlexTreeNode root )
529 {
530 root.setAsRoot();
531 reset();
532
533 rootNode.addChild ( root, FlexTreeNode.LAST );
534
535 firstVisNode = root;
536 repaint ( true );
537 }
538
539 /**
540 * Remove all nodes from the tree.
541 * @see #removeNode
542 */
543 public void reset()
544 {
545 rootNode.remove();
546 rootNode = new FlexTreeNode(null, null, null);
547 rootNode.setAsTrueRoot ( nTreeId );
548 rootNode.setExpanded ( true, true );
549 firstVisNode = null;
550 lastVisNode = null;
551 selection.clearSelected();
552 repaint ( true );
553 }
554
555 /**
556 * Return the preferred size of the Tree control.
557 * If size was never set, then returned value is new Dimension(200,250).
558 */
559 public Dimension getPreferredSize()
560 {
561 Dimension d = getSize();
562 if(d.width == 0)
563 d.width = 200;
564 if(d.height == 0)
565 d.height = 250;
566 return d;
567 }
568
569 public ArrayList getSelectedItems ( boolean bSubItems )
570 {
571 ArrayList l = selection.getSelectedNodes();
572
573 if ( bSubItems )
574 {
575 FlexTreeNode node;
576 ArrayList resultList = new ArrayList();
577 resultList.addAll(l);
578
579 for ( int i = 0; i < l.size(); i++ )
580 {
581 node = (FlexTreeNode)l.get(i);
582 resultList.addAll ( node.getAllChildren() );
583 }
584 return resultList;
585 }
586 else
587 return l;
588
589 }
590
591 public void setCurrentNode ( FlexTreeNode n )
592 {
593 selection.setCurrentNode ( n );
594 }
595
596 public FlexTreeNode getCurrentNode ( )
597 {
598 return selection.getCurrentNode();
599 }
600
601 public void unselectAllNodes ( )
602 {
603 selection.clearSelected();
604 }
605
606 public int getSelectedCount ( )
607 {
608 return selection.getSelectedCount();
609 }
610
611 public boolean isSelected ( FlexTreeNode n )
612 {
613 return selection.isSelected(n);
614 }
615
616 public boolean isFocusTraversable()
617 {
618 return true;
619 }
620
621
622
623
624
625 // Event Processing code
626
627 public void addActionListener(ActionListener l)
628 {
629 if ( actionMulticaster != null )
630 actionMulticaster.add(l);
631 }
632
633 public void removeActionListener(ActionListener l)
634 {
635 if ( actionMulticaster != null )
636 actionMulticaster.remove(l);
637 }
638
639 private void fireActionEvent ( TreeActionEvent e )
640 {
641 if ( actionMulticaster != null )
642 actionMulticaster.dispatch ( e );
643 }
644
645 public void keyTyped ( KeyEvent e )
646 {
647 }
648
649 public void keyReleased ( KeyEvent e )
650 {
651 }
652
653 public void keyPressed ( KeyEvent e )
654 {
655 switch ( e.getKeyCode() )
656 {
657 case KeyEvent.VK_UP:
658 {
659 FlexTreeNode node = selection.getCurrentNode();
660 if ( node != null )
661 {
662 node = node.getPrevVisible();
663 {
664 if ( node != null )
665 {
666 TreeActionEvent ae = new TreeActionEvent ( this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
667 fireActionEvent ( ae );
668
669 if ( ae.getCancel() )
670 return;
671
672 selection.clearSelected();
673 selection.setCurrentNode ( node );
674 ensureVisible ( node );
675 repaint ( false );
676
677 m_vscroll.setValue ( m_iVScroll );
678
679 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
680 fireActionEvent ( ae );
681 }
682 }
683 }
684
685 break;
686 }
687
688 case KeyEvent.VK_DOWN:
689 {
690 FlexTreeNode node = selection.getCurrentNode();
691 if ( node != null )
692 {
693 node = node.getNextVisible();
694 {
695 if ( node != null )
696 {
697 TreeActionEvent ae = new TreeActionEvent ( this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
698 fireActionEvent ( ae );
699
700 if ( ae.getCancel() )
701 return;
702
703 selection.clearSelected();
704 selection.setCurrentNode ( node );
705 ensureVisible ( node );
706 repaint ( false );
707
708 m_vscroll.setValue ( m_iVScroll );
709
710 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
711 fireActionEvent ( ae );
712 }
713 }
714 }
715
716 break;
717 }
718
719
720 case KeyEvent.VK_PAGE_UP:
721 {
722 FlexTreeNode node = selection.getCurrentNode();
723
724 if ( firstVisNode != node )
725 {
726 node = firstVisNode;
727 }
728 else
729 {
730 FlexTreeNode prev;
731 for ( int i = 0; i < m_iLinesPerPage; i++ )
732 {
733 prev = node.getPrevVisible();
734 if ( prev == null )
735 break;
736 else
737 node = prev;
738 }
739 }
740
741 if ( node != null )
742 {
743 TreeActionEvent ae = new TreeActionEvent ( this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
744 fireActionEvent ( ae );
745
746 if ( ae.getCancel() )
747 return;
748
749 selection.clearSelected();
750 selection.setCurrentNode ( node );
751 ensureVisible ( node );
752 repaint ( false );
753
754 m_vscroll.setValue ( m_iVScroll );
755
756 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
757 fireActionEvent ( ae );
758 }
759
760 break;
761 }
762
763 case KeyEvent.VK_PAGE_DOWN:
764 {
765 FlexTreeNode node = selection.getCurrentNode();
766
767 if ( lastVisNode != node )
768 {
769 node = lastVisNode;
770 }
771 else
772 {
773 FlexTreeNode next;
774 for ( int i = 0; i < m_iLinesPerPage; i++ )
775 {
776 next = node.getNextVisible();
777 if ( next == null )
778 break;
779 else
780 node = next;
781 }
782 }
783
784 if ( node != null )
785 {
786 TreeActionEvent ae = new TreeActionEvent ( this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
787 fireActionEvent ( ae );
788
789 if ( ae.getCancel() )
790 return;
791
792 selection.clearSelected();
793 selection.setCurrentNode ( node );
794 ensureVisible ( node );
795 repaint ( false );
796
797 m_vscroll.setValue ( m_iVScroll );
798
799 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
800 fireActionEvent ( ae );
801 }
802
803 break;
804 }
805
806 case KeyEvent.VK_HOME:
807 {
808 FlexTreeNode node = rootNode.getFirstChild();
809
810 TreeActionEvent ae = new TreeActionEvent ( this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
811 fireActionEvent ( ae );
812
813 if ( ae.getCancel() )
814 return;
815
816 selection.clearSelected();
817 selection.setCurrentNode ( node );
818 firstVisNode = node;
819 repaint ( false );
820
821 m_iVScroll = 0;;
822 m_vscroll.setValue ( m_iVScroll );
823
824 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
825 fireActionEvent ( ae );
826
827 break;
828 }
829
830 case KeyEvent.VK_END:
831 {
832 FlexTreeNode node = rootNode.getLastVisible();
833
834 TreeActionEvent ae = new TreeActionEvent ( this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
835 fireActionEvent ( ae );
836
837 if ( ae.getCancel() )
838 return;
839
840 selection.clearSelected();
841 selection.setCurrentNode ( node );
842 ensureVisible ( node );
843 repaint ( false );
844
845 m_vscroll.setValue ( m_iVScroll );
846
847 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
848 fireActionEvent ( ae );
849
850 break;
851 }
852 }
853 }
854
855 public void mouseClicked ( MouseEvent e )
856 {
857 }
858
859 public void mousePressed ( MouseEvent e )
860 {
861 // Process only the left mouse button.
862 if( (e.getModifiers() & InputEvent.BUTTON3_MASK) != 0 )
863 return;
864
865 requestFocus();
866
867 // Do not process events if there is no visible first node.
868 if ( firstVisNode == null )
869 return;
870
871 int x = e.getX();
872 int y = e.getY();
873 FlexTreeNode node = hitTest(x, y);
874 int flags = nHitTest;
875
876 // Do not continue if user clicked on the background.
877 if ( (node == null) || (flags == HITTEST_MARGIN) || (flags == HITTEST_TEXT_RIGHT) )
878 return;
879
880 int margin = node.getLevel() * m_iIndent - m_iHScroll;
881
882 // If the node is not fully visible, then scroll down 1 row.
883 if ( !isNodeVisible(node) )
884 adjustmentValueChanged ( new AdjustmentEvent ( m_vscroll, AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, AdjustmentEvent.UNIT_INCREMENT, 0 ) );
885
886 // User clicked on - or + image.
887 if ( flags == HITTEST_BUTTON )
888 {
889 if ( node.getAlwaysShowPlusMinus() || node.nFirstChild != null )
890 {
891 int nBeginEvent = node.isExpanded() ? TreeActionEvent.BEGIN_NODE_COLLAPSE : TreeActionEvent.BEGIN_NODE_EXPAND;
892 int nEndEvent = node.isExpanded() ? TreeActionEvent.END_NODE_COLLAPSE : TreeActionEvent.END_NODE_EXPAND;
893
894 TreeActionEvent ae = new TreeActionEvent(this, nBeginEvent, e.getModifiers(), node);
895 fireActionEvent ( ae );
896
897 if ( ae.getCancel() )
898 return;
899
900 node.setExpanded(!node.isExpanded(), false);
901 repaint ( true );
902
903 ae = new TreeActionEvent(this, nEndEvent, e.getModifiers(), node);
904 fireActionEvent ( ae );
905 }
906 }
907
908 // Double click.
909 else if ( e.getClickCount() == 2 )
910 {
911 int nBeginEvent = node.isExpanded() ? TreeActionEvent.BEGIN_NODE_COLLAPSE : TreeActionEvent.BEGIN_NODE_EXPAND;
912 int nEndEvent = node.isExpanded() ? TreeActionEvent.END_NODE_COLLAPSE : TreeActionEvent.END_NODE_EXPAND;
913
914 TreeActionEvent ae = new TreeActionEvent(this, nBeginEvent, e.getModifiers(), node);
915 fireActionEvent ( ae );
916
917 if ( ae.getCancel() )
918 return;
919
920 node.setExpanded(!node.isExpanded(), false);
921 repaint ( true );
922
923 ae = new TreeActionEvent(this, nEndEvent, e.getModifiers(), node);
924 fireActionEvent ( ae );
925
926 ae = new TreeActionEvent(this, TreeActionEvent.NODE_DOUBLE_CLICKED, e.getModifiers(), node);
927 fireActionEvent ( ae );
928
929 return;
930 }
931
932 // Single click.
933 else if ( e.getClickCount() == 1 )
934 {
935 TreeActionEvent ae = new TreeActionEvent(this, TreeActionEvent.BEGIN_NODE_SELECT, e.getModifiers(), node);
936 fireActionEvent ( ae );
937
938 if ( ae.getCancel() )
939 return;
940
941 setRedraw ( false );
942 selection.handleMouseSelection ( node, e.isShiftDown(), e.isControlDown() );
943 setRedraw ( true );
944 repaint ( false );
945
946 ae = new TreeActionEvent(this, TreeActionEvent.END_NODE_SELECT, e.getModifiers(), node);
947 fireActionEvent ( ae );
948 }
949 }
950
951 public void mouseReleased ( MouseEvent e )
952 {
953 }
954
955 public void mouseEntered ( MouseEvent e )
956 {
957 }
958
959 public void mouseExited ( MouseEvent e )
960 {
961 }
962
963 public void adjustmentValueChanged ( AdjustmentEvent e )
964 {
965 if ( firstVisNode == null )
966 return;
967
968 if(e.getSource() == m_hscroll)
969 {
970 m_iHScroll = m_hscroll.getValue();
971 repaint ( false );
972 return;
973 }
974
975 switch(e.getAdjustmentType())
976 {
977 case AdjustmentEvent.UNIT_DECREMENT: // Scroll up one line.
978 {
979 if ( firstVisNode.getPrevVisible() != null )
980 {
981 firstVisNode = firstVisNode.getPrevVisible();
982 if(m_iVScroll > 0)
983 m_vscroll.setValue(--m_iVScroll);
984 if(lastVisNode.getPrevVisible() != null)
985 lastVisNode = lastVisNode.getPrevVisible();
986 repaint ( false );
987 }
988 break;
989 }
990
991 case AdjustmentEvent.UNIT_INCREMENT: // Scroll down one line.
992 {
993 if(lastVisNode.getNextVisible() != null)
994 {
995 lastVisNode = lastVisNode.getNextVisible();
996 if(firstVisNode.getNextVisible() != null)
997 firstVisNode = firstVisNode.getNextVisible();
998 repaint ( false );
999 }
1000 if(m_iVScroll + m_iLinesPerPage < m_iVisCount)
1001 m_vscroll.setValue(++m_iVScroll);
1002 break;
1003 }
1004
1005 case AdjustmentEvent.BLOCK_DECREMENT:
1006 {
1007 int i = m_iVScroll - m_iLinesPerPage + 1;
1008 if(i < 0)
1009 i = 0;
1010
1011 FlexTreeNode n = firstVisNode;
1012 while(m_iVScroll > i && firstVisNode != null)
1013 {
1014 m_iVScroll--;
1015 if(firstVisNode.getPrevVisible() != null)
1016 firstVisNode = firstVisNode.getPrevVisible();
1017 if(lastVisNode.getPrevVisible() != null)
1018 lastVisNode = lastVisNode.getPrevVisible();
1019 }
1020 repaint ( false );
1021 break;
1022 }
1023
1024 case AdjustmentEvent.BLOCK_INCREMENT :
1025 {
1026 int i = m_iVScroll + m_iLinesPerPage - 1;
1027 if(i + m_iLinesPerPage > m_iVisCount)
1028 i = m_iVisCount - m_iLinesPerPage;
1029 if(i < 0) i = 0;
1030 FlexTreeNode n = lastVisNode;
1031
1032 while(m_iVScroll < i && lastVisNode != null)
1033 {
1034 m_iVScroll++;
1035 if(firstVisNode.getNextVisible() != null)
1036 firstVisNode = firstVisNode.getNextVisible();
1037 if(lastVisNode.getNextVisible() != null)
1038 lastVisNode = lastVisNode.getNextVisible();
1039 }
1040 repaint ( false );
1041 break;
1042 }
1043
1044 case AdjustmentEvent.TRACK :
1045 {
1046 m_iVScroll = m_vscroll.getValue();
1047 int count = 0;
1048 FlexTreeNode n = rootNode.nFirstChild;
1049 while((n != null) && (count < m_iVScroll))
1050 {
1051 count++;
1052 n = n.getNextVisible();
1053 }
1054 if(n != null)
1055 {
1056 firstVisNode = n;
1057 repaint ( false );
1058 }
1059
1060 break;
1061 }
1062 }
1063 }
1064
1065
1066
1067
1068
1069// Paint code
1070
1071 public void invalidate()
1072 {
1073 super.invalidate();
1074 repaint ( true );
1075 }
1076
1077 public void repaint ( boolean bRecalculateLayout )
1078 {
1079 if ( bRecalculateLayout )
1080 doFullLayout();
1081 else
1082 paintImage();
1083 }
1084
1085 private void doFullLayout ( )
1086 {
1087 if ( bLayoutInProgress )
1088 return;
1089
1090 bLayoutInProgress = true;
1091
1092 Rectangle r = new Rectangle ( getSize() );
1093
1094 // Check client size
1095 if ( r.width < 16 || r.height < 16 )
1096 {
1097 m_vscroll.setVisible(false);
1098 m_hscroll.setVisible(false);
1099 bLayoutInProgress = false;
1100 return;
1101 }
1102
1103 // set vertical scroll bar
1104 m_iMaxLineLen = 10;
1105 FlexTreeNode n = rootNode;
1106 if(n != null)
1107 {
1108 n = n.nFirstChild;
1109 while(n != null)
1110 {
1111 m_iMaxLineLen = Math.max(m_iMaxLineLen, nodeWidth(n) + 3);
1112 n = n.getNextVisible();
1113 }
1114 }
1115 boolean bVisH = m_hscroll.isVisible();
1116 boolean bVisV = m_vscroll.isVisible();
1117 if((m_iMaxLineLen > (r.width - (bVisV ? 15 : 0))) != bVisH)
1118 m_hscroll.setVisible(bVisH = !bVisH);
1119
1120 m_iLinesPerPage = (r.height - (bVisH ? 15 : 0)) / m_iLineHeight;
1121
1122 // set vertical scroll bar
1123 m_iVisCount = getVisibleCount();
1124 if((m_iVisCount > m_iLinesPerPage) != bVisV)
1125 {
1126 if(bVisV)
1127 firstVisNode = rootNode.nFirstChild;
1128 m_vscroll.setVisible(bVisV = !bVisV);
1129 if((m_iMaxLineLen > (r.width - (bVisV ? 15 : 0))) != bVisH)
1130 m_hscroll.setVisible(bVisH = !bVisH);
1131 //--------------------------------
1132 // recalculate one more time
1133 //--------------------------------
1134 m_iLinesPerPage = (r.height - (bVisH ? 15 : 0)) / m_iLineHeight;
1135 if((m_iVisCount > m_iLinesPerPage) != bVisV)
1136 {
1137 if(bVisV)
1138 firstVisNode = rootNode.nFirstChild;
1139 m_vscroll.setVisible(bVisV = !bVisV);
1140 }
1141 }
1142
1143 if(firstVisNode != null)
1144 lastVisNode = firstVisNode.nodeAtLine(m_iLinesPerPage);
1145
1146 // adjust for scroll bars visibility
1147 if(bVisV)
1148 {
1149 m_vscroll.setValues(m_iVScroll, m_iLinesPerPage, 0, m_iVisCount);
1150 r.width -= 15;
1151 }
1152 else
1153 m_iVScroll = 0;
1154
1155 if(bVisH)
1156 {
1157 m_hscroll.setValues(m_iHScroll, r.width, 0, m_iMaxLineLen );
1158 r.height -= 15;
1159 }
1160 else
1161 m_iHScroll = 0;
1162
1163 if(bVisV)
1164 {
1165 Rectangle rr = m_vscroll.getBounds();
1166 if(r.x + r.width != rr.x || r.y != rr.y || 15 != rr.width || r.height != rr.height)
1167 m_vscroll.setBounds(r.x + r.width, r.y, 15, r.height);
1168 }
1169
1170 if(bVisH)
1171 {
1172 Rectangle rr = m_hscroll.getBounds();
1173 if(r.x != rr.x || r.y + r.height != rr.y || r.width != rr.width || 15 != rr.height)
1174 m_hscroll.setBounds(r.x, r.y + r.height, r.width, 15);
1175 }
1176
1177
1178 pCorner.setBounds(0,0,0,0);
1179 // If Both the ScrollBars are visible then add a filler Panel at RighBottom Corner of Panel
1180 // To solve Bug254
1181 if (bVisH && bVisV)
1182 {
1183 pCorner.setBounds(r.x+r.width, r.y+r.height, 15, 15);
1184 }
1185 repaint ( false );
1186 bLayoutInProgress = false;
1187 }
1188
1189 public void paint ( Graphics g )
1190 {
1191 Dimension d = getSize();
1192
1193 if ( m_im != null )
1194 if ( m_im.getHeight(this) < d.height || m_im.getWidth(this) < d.width )
1195 m_im = null;
1196
1197 try
1198 {
1199 if ( m_im == null )
1200 m_im = createImage ( d.width, d.height );
1201 }
1202 catch ( Exception e ) { }
1203
1204 paintImage();
1205 }
1206
1207 private void paintImage ( )
1208 {
1209 if ( bPaintInProgress || m_im == null || !bAllowPaint )
1210 return;
1211
1212 bPaintInProgress = true;
1213
1214 FlexTreeNode node = firstVisNode;
1215 Graphics ig = m_im.getGraphics();
1216 int total = m_iLinesPerPage + 1 ;
1217 int count = 1;
1218
1219 // Clear the background.
1220 ig.setColor ( bkgndClr );
1221 ig.fillRect ( 0, 0, getSize().width, getSize().height );
1222
1223 // Paint the nodes.
1224 while ( node != null && count <= total )
1225 {
1226 try
1227 {
1228 node._paint ( this, ig, count );
1229 }
1230 catch(NullPointerException npe)
1231 {
1232 AdjustmentEvent ae = new AdjustmentEvent(m_vscroll,AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED,AdjustmentEvent.TRACK,m_vscroll.getValue() );
1233 adjustmentValueChanged(ae);
1234 setSize(getSize().width+1,getSize().height+1);
1235 setSize(getSize().width-1,getSize().height-1);
1236 }
1237 if ( count == total-1 )
1238 lastVisNode = node;
1239 node = node.getNextVisible();
1240 count++;
1241 }
1242
1243 ig.dispose();
1244
1245 Graphics g = getGraphics();
1246
1247 if(g != null)
1248 {
1249 g.drawImage(m_im, 0, 0, this);
1250 g.dispose();
1251 }
1252
1253 bPaintInProgress = false;
1254 }
1255
1256 public void drawSelectedBorder ( Graphics g, int l, int t, int w, int h )
1257 {
1258 int d = w & 1;
1259 w += l;
1260 int i = t - 1;
1261
1262 g.setColor ( Color.yellow );
1263
1264 // left/right lines
1265 while((i += 2) <= h + t)
1266 {
1267 g.drawLine(l, i, l, i);
1268 g.drawLine(w, i + d, w, i + d);
1269 }
1270
1271 // top/bottom lines
1272 d = h & 1;
1273 h += t;
1274 i = l - 1;
1275 while((i += 2) <= w)
1276 {
1277 g.drawLine(i, t, i, t);
1278 g.drawLine(i + d, h, i + d, h);
1279 }
1280 }
1281
1282
1283}
1284