001package arcommon.ui.component;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.awt.event.KeyListener;
006import java.awt.event.MouseListener;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.HashMap;
010import java.util.Iterator;
011import java.util.List;
012import java.util.Map;
013import java.util.NoSuchElementException;
014
015import javax.swing.JComponent;
016import javax.swing.JTree;
017import javax.swing.SwingUtilities;
018import javax.swing.event.TreeSelectionEvent;
019import javax.swing.event.TreeSelectionListener;
020import javax.swing.plaf.metal.MetalTreeUI;
021import javax.swing.tree.DefaultMutableTreeNode;
022import javax.swing.tree.DefaultTreeSelectionModel;
023import javax.swing.tree.TreePath;
024
025import arcommon.DateUtil;
026import arcommon.MyTimer;
027import arcommon.ui.other.GUIUtil;
028
029/**
030 * A {@link javax.swing.JTree} that is wrapped in a {@link javax.swing.JScrollPane}.
031 */
032public class MyJTree extends AbstractDelegatingJScrollPane {
033
034    private class TreeSelectionModel extends DefaultTreeSelectionModel {
035        public void addSelectionPaths(TreePath[] paths) {
036            super.addSelectionPaths(paths);
037        }
038        
039        public void removeSelectionPaths(TreePath[] paths) {
040            super.removeSelectionPaths(paths);
041        }
042        
043        public void setSelectionPathsHack(TreePath[] pPaths) {
044            super.setSelectionPaths(pPaths);
045        }
046        
047        public void setSelectionPaths(TreePath[] pPaths) {
048            // HACK!!!! Doesn't set SelectionPaths. It just toggles those who came in the pPaths!!
049            ArrayList<TreePath> newPath = new ArrayList<TreePath>();
050            TreePath[] oldPath = tree.getSelectionPaths();
051            for (int i = 0; oldPath != null && i < oldPath.length; i++) {
052                newPath.add(oldPath[i]);
053            }
054            for (int i = 0; i < pPaths.length; i++) {
055                int index = newPath.indexOf(pPaths[i]);
056                if (index == -1) {
057                    newPath.add(pPaths[i]);
058                } else {
059                    newPath.remove(pPaths[i]);
060                }
061            }
062            TreePath[] result = new TreePath[newPath.size()];
063            for (int i = 0; i < newPath.size(); i++) {
064                result[i] = newPath.get(i);
065            }
066            super.setSelectionPaths(result);
067        }
068        
069        private static final long serialVersionUID = 5532569881521634626L;
070    }
071    
072    private JTree tree;
073    private Object treeSynchronizeObject;
074    private DefaultMutableTreeNode topNode;
075    private TreeSelectionModel treeSelectionModel;
076    private boolean multiSelect;
077    private ActionListener selectLastActionListener;
078    private boolean removeFirstLetterNavigation;
079    
080    public MyJTree() {
081        this(false);
082    }
083    
084    public MyJTree(boolean pmultiSelect) {
085        topNode = new DefaultMutableTreeNode();
086    
087        tree = new JTree(topNode) {
088            private static final long serialVersionUID = 6713466064903508999L;
089
090            public void addKeyListener(KeyListener keyListener) {
091                if (removeFirstLetterNavigation && isFirstLetterNavigationKeyListener(keyListener)) {
092                    return ;
093                }
094                super.addKeyListener(keyListener);
095            }
096        };
097        
098        initActionMap();
099        
100        setViewportView(tree);
101        
102        tree.setRootVisible(false);
103        
104        tree.setUI(new MetalTreeUI() {
105            // This is probably a hack, but it is unknown of what
106            protected MouseListener createMouseListener() {
107                return null;
108            }
109        });
110        
111        treeSynchronizeObject = new Object();
112        
113        setMultiSelection(pmultiSelect);
114    }
115
116    public void setToggleSelection(boolean toggleSelect) {
117        if (toggleSelect) {
118            treeSelectionModel = new TreeSelectionModel();
119            tree.setSelectionModel(treeSelectionModel);
120        } else {
121            setMultiSelection(multiSelect);
122        }
123    }
124    
125    public void setMultiSelection(boolean pmultiSelect) {
126        multiSelect = pmultiSelect;
127        DefaultTreeSelectionModel defaultTreeSelectionModel = new DefaultTreeSelectionModel();
128        if (multiSelect) {
129            defaultTreeSelectionModel.setSelectionMode(DefaultTreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
130        } else {
131            defaultTreeSelectionModel.setSelectionMode(DefaultTreeSelectionModel.SINGLE_TREE_SELECTION);
132        }
133        treeSelectionModel = null;
134        tree.setSelectionModel(defaultTreeSelectionModel);
135    }
136    
137    public void setNodes(DefaultMutableTreeNode pnode) {
138        synchronized (treeSynchronizeObject) {
139            Object[] selectedObjects = getSelectedUserObjects();
140        
141            update(topNode, pnode);
142            
143            expandNode(topNode);
144        
145            if (selectedObjects != null) {
146                setSelectedUserObjects(selectedObjects);
147            }
148
149            forceUpdate();
150        }
151    }
152    
153    public void addNodes(DefaultMutableTreeNode pnodes) {
154        if (pnodes == null) {
155            return ;
156        }
157        
158        synchronized (treeSynchronizeObject) {
159            addNodesLocal(topNode, pnodes);
160    
161            forceUpdate();
162        }
163    }
164    
165    public void removeNodes(DefaultMutableTreeNode pnodes) {
166        if (pnodes == null) {
167            return ;
168        }
169        
170        synchronized (treeSynchronizeObject) {
171            removeNodesLocal(topNode, pnodes);
172    
173            forceUpdate();
174        }
175    }
176    
177    private ActionListener updateActionListener;
178    private int count = 0;
179    private long lastUpdate = System.currentTimeMillis();
180    private int numberOfUpdatesInProgress = 0;
181    
182    public void forceUpdate() {
183        long now = System.currentTimeMillis();
184        if (now > lastUpdate + DateUtil.SECOND_IN_MILLISECONDS * 10) {
185            count = 0;
186            lastUpdate = now;
187        }
188        
189        if (count++ > 10 || updateActionListener != null) {
190            if (updateActionListener == null) {
191                lastUpdate = now;
192                updateActionListener = new ActionListener() {
193                    public void actionPerformed(ActionEvent e) {
194                        if (count > 20) {
195                            count = 15;
196                        } else {
197                            count -= 5;
198                            if (count <= 0) {
199                                ActionListener tempActionListener = updateActionListener;
200                                updateActionListener = null;
201                                if (tempActionListener != null) {
202                                    MyTimer.removeActionListener(tempActionListener);
203                                }
204                            }
205                        }
206                        forceUpdateLocal();
207                    }
208                    
209                    public String toString() {
210                        return super.toString() + " " + MyJTree.this.toString() + " " + MyJTree.this.getParent().toString();
211                    }
212                };
213                MyTimer.addActionListener(10, updateActionListener);
214            }
215            return ;
216        }
217        
218        forceUpdateLocal();
219    }
220    
221    private void forceUpdateLocal() {
222        if (numberOfUpdatesInProgress > 0) {
223            return ;
224        }
225        
226        numberOfUpdatesInProgress++;
227        Runnable runnable = new Runnable() {
228            public void run() {
229                tree.updateUI();
230
231                numberOfUpdatesInProgress--;
232            }
233        };
234        SwingUtilities.invokeLater(runnable);
235    }
236    
237    public void dispose() {
238        if (updateActionListener != null) {
239            MyTimer.removeActionListener(updateActionListener);
240            updateActionListener = null;
241        }
242    }
243
244    public DefaultMutableTreeNode getTopNode() {
245        return topNode;
246    }
247    
248    private void update(DefaultMutableTreeNode oldParentNode, DefaultMutableTreeNode newParentNode) {
249/*        int offset = 0;
250        int number = 0;
251        if (newParentNode != null) {
252            Map<Object, Integer> userObjectToIndex = new HashMap<Object, Integer>();
253            for (int i = 0; i < oldParentNode.getChildCount(); i++) {
254                DefaultMutableTreeNode tempNode = (DefaultMutableTreeNode)oldParentNode.getChildAt(i);
255                userObjectToIndex.put(tempNode.getUserObject(), Integer.valueOf(i));
256            }
257            
258            for (int i = 0; i < newParentNode.getChildCount(); i++) {
259                DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)newParentNode.getChildAt(i);
260                if (offset + i < oldParentNode.getChildCount()) {
261                    DefaultMutableTreeNode oldNode = (DefaultMutableTreeNode)oldParentNode.getChildAt(offset + i);
262                    if (newNode.getUserObject() == oldNode.getUserObject()) { // This should NOT be "equals()"
263                        update(oldNode, newNode);
264                        continue;
265                    }
266                    
267                    if (newNode.getUserObject().equals(oldNode.getUserObject())) { // This should be "equals()"
268                        oldNode.setUserObject(newNode.getUserObject()); // Copy the UserObject from the newNode to the oldNode.
269                        update(oldNode, newNode);
270                        continue;
271                    }
272                }
273                
274                Integer index = userObjectToIndex.get(newNode.getUserObject());
275                int oldNodeIndex = (index == null) ? -1 : index.intValue();
276                
277                if (oldNodeIndex != -1) {
278                    if (oldNodeIndex < i) {
279//                        throw new RuntimeException("oldNodeIndex < i " + oldNodeIndex + " < " + i);
280                        System.out.println("oldNodeIndex < i " + oldNodeIndex + " < " + i + "  " + newNode.getUserObject());
281                        return ;
282                    }
283                    
284                    DefaultMutableTreeNode tempNode = (DefaultMutableTreeNode)oldParentNode.getChildAt(oldNodeIndex);
285                    oldParentNode.insert(tempNode, offset + i); // Note: The node is moved inside old.
286                    tempNode.setUserObject(newNode.getUserObject()); // Copy the UserObject from the newNode to the tempNode.
287                    update(tempNode, newNode);
288                    continue;
289                }
290                
291                oldParentNode.insert(newNode, offset + i); // Note: The node is moved from new to old!
292                i--;
293                offset++;
294            }
295            
296            number = newParentNode.getChildCount();
297        }
298        
299        for (int i = number + offset; i < oldParentNode.getChildCount(); i++) {
300            oldParentNode.remove(i);
301            i--;
302        }*/
303        
304        int newNodeCount = 0;
305        if (newParentNode != null) {
306            Map<Object, DefaultMutableTreeNode> userObjectToDefaultMutableTreeNode = new HashMap<Object, DefaultMutableTreeNode>();
307            for (int i = 0; i < oldParentNode.getChildCount(); i++) {
308                DefaultMutableTreeNode tempNode = (DefaultMutableTreeNode)oldParentNode.getChildAt(i);
309                userObjectToDefaultMutableTreeNode.put(tempNode.getUserObject(), tempNode);
310            }
311            
312            DefaultMutableTreeNode[] newNodes = new DefaultMutableTreeNode[newParentNode.getChildCount()];
313            for (int i = 0; i < newParentNode.getChildCount(); i++) {
314                DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)newParentNode.getChildAt(i);
315                
316                DefaultMutableTreeNode oldNode = userObjectToDefaultMutableTreeNode.get(newNode.getUserObject());
317                if (oldNode != null) {
318                    newNodes[i] = oldNode;
319                    // The UserObject in the oldNode and in the newNode are equals, but they are not necessarily "==" => move the UserObject from new to old to make them "==".
320                    oldNode.setUserObject(newNode.getUserObject()); // Copy the UserObject from the newNode to the tempNode.
321                    update(oldNode, newNode);
322                } else {
323                    newNodes[i] = newNode;
324                }
325            }
326            
327            for (int i = 0; i < newNodes.length; i++) {
328                DefaultMutableTreeNode newNode = newNodes[i];
329                oldParentNode.insert(newNode, i); // Note: The node is moved from new to old!
330            }
331                
332            newNodeCount = newNodes.length;
333        }
334
335        for (int i = newNodeCount; i < oldParentNode.getChildCount(); i++) {
336            oldParentNode.remove(i);
337            i--;
338        }
339    }
340    
341    private void addNodesLocal(DefaultMutableTreeNode oldParentNode, DefaultMutableTreeNode newParentNode) {
342        if (newParentNode == null) {
343            return ;
344        }
345
346        for (int i = 0; i < newParentNode.getChildCount(); i++) {
347            DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)newParentNode.getChildAt(i);
348            int oldNodeIndex = findNodeIndex(oldParentNode, newNode);
349            if (oldNodeIndex != -1) {
350                continue;
351            }
352            oldParentNode.add(newNode); // Note the side effect: The node is moved from new to old!
353            i--;
354        }
355    }
356
357    private void removeNodesLocal(DefaultMutableTreeNode oldParentNode, DefaultMutableTreeNode newParentNode) {
358        if (newParentNode.getChildCount() != oldParentNode.getChildCount()) {
359            // Removes nodes based on the assumption that the newParentNode exists in oldParentNode
360            oldParentNode.remove(newParentNode);
361        } else {
362            // Removes nodes based on indices
363            for (int i = 0; i < newParentNode.getChildCount(); i++) {
364                DefaultMutableTreeNode newNode = (DefaultMutableTreeNode)newParentNode.getChildAt(i);
365                int oldNodeIndex = findNodeIndex(newParentNode, newNode);
366                if (oldNodeIndex == -1) {
367                    continue;
368                }
369                oldParentNode.remove(i);
370            }
371        }
372    }
373    
374    private int findNodeIndex(DefaultMutableTreeNode parentNode, DefaultMutableTreeNode node) {
375        if (parentNode == null) {
376            return -1;
377        }
378        for (int i = 0; i < parentNode.getChildCount(); i++) {
379            DefaultMutableTreeNode tempNode = (DefaultMutableTreeNode)parentNode.getChildAt(i);
380            if (tempNode.getUserObject() == node.getUserObject()) { // This should NOT be "equals()"
381                return i;
382            }
383        }
384        return -1;
385    }
386
387    private DefaultMutableTreeNode findObjectInHierarchy(DefaultMutableTreeNode parentNode, Object userObject) {
388        if (parentNode == null) {
389            return null;
390        }
391        for (int i = 0; i < parentNode.getChildCount(); i++) {
392            DefaultMutableTreeNode tempNode = (DefaultMutableTreeNode)parentNode.getChildAt(i);
393            if (tempNode.getUserObject().equals(userObject)) { // This should be "equals()"
394                return tempNode;
395            }
396            
397            DefaultMutableTreeNode foundNode = findObjectInHierarchy(tempNode, userObject);
398            if (foundNode != null) {
399                return foundNode;
400            }
401        }
402        return null;
403    }
404    
405    private TreePath[] selectionPathsOverride;
406
407    public TreePath[] getSelectedTreePath() {
408        if (selectionPathsOverride != null) {
409            return selectionPathsOverride;
410        }
411        TreePath[] selectionPaths;
412        synchronized (treeSynchronizeObject) {
413            selectionPaths = tree.getSelectionPaths();
414        }
415        return selectionPaths;
416    }
417    
418    public DefaultMutableTreeNode[] getSelectedNodes() {
419        TreePath[] selectionPaths = getSelectedTreePath();
420        if (selectionPaths == null) {
421            return null;
422        }
423        if (selectionPaths.length == 0) {
424            return null;
425        }
426        List<DefaultMutableTreeNode> result = new ArrayList<DefaultMutableTreeNode>();
427        for (int i = 0; i < selectionPaths.length; i++) {
428            if (selectionPaths[i] != null) {
429                result.add((DefaultMutableTreeNode)selectionPaths[i].getLastPathComponent());
430            }
431        }
432        return result.toArray(new DefaultMutableTreeNode[result.size()]);
433    }
434    
435    public Object getSelectedUserObject() {
436        Object[] selectedObjects = getSelectedUserObjects();
437        if (selectedObjects == null || selectedObjects.length == 0) {
438            return null;
439        }
440        return selectedObjects[0];
441    }
442    
443    public Object[] getSelectedUserObjects() {
444        DefaultMutableTreeNode[] selectedNodes = getSelectedNodes();
445        if (selectedNodes == null) {
446            return null;
447        }
448        if (selectedNodes.length == 0) {
449            return null;
450        }
451        Object[] result = new Object[selectedNodes.length];
452        for (int i = 0; i < selectedNodes.length; i++) {
453            result[i] = selectedNodes[i].getUserObject();
454        }
455        return result;
456    }
457
458    public void setSelectedUserObject(Object selectedObject) {
459        setSelectedUserObject(selectedObject, false);
460    }
461    
462    public void setSelectedUserObject(Object selectedObject, boolean notify) {
463        if (selectedObject == null) {
464            setSelectedPaths(new TreePath[0], notify);
465            return ;
466        }
467        TreePath[] selectedPath = new TreePath[1];
468        DefaultMutableTreeNode selectedNode = findObjectInHierarchy(topNode, selectedObject);
469        if (selectedNode == null) {
470            setSelectedPaths(new TreePath[0], notify);
471            return ;
472        }
473        selectedPath[0] = new TreePath(selectedNode.getPath());
474        setSelectedPaths(selectedPath, notify);
475    }
476        
477    public void setSelectedUserObjects(Collection<?> selectedObjects) {
478        setSelectedUserObjects(selectedObjects, false);
479    }
480    
481    public void setSelectedUserObjects(Collection<?> selectedObjects, boolean notify) {
482        TreePath[] selectedPath = new TreePath[selectedObjects.size()];
483        Iterator<?> i = selectedObjects.iterator();
484        int index = 0;
485        while (i.hasNext()) {
486            Object selectedObject = i.next();
487            DefaultMutableTreeNode selectedNode = findObjectInHierarchy(topNode, selectedObject);
488            if (selectedNode == null) {
489                continue;
490            }
491            selectedPath[index++] = new TreePath(selectedNode.getPath());
492        }
493        setSelectedPaths(selectedPath, notify);
494    }
495
496    public void setSelectedUserObjects(Object[] selectedObjects) {
497        setSelectedUserObjects(selectedObjects, false);
498    }
499    
500    public void setSelectedUserObjects(Object[] selectedObjects, boolean notify) {
501        TreePath[] selectedPath = new TreePath[selectedObjects.length];
502        for (int i = 0; i < selectedObjects.length; i++) {
503            DefaultMutableTreeNode selectedNode = findObjectInHierarchy(topNode, selectedObjects[i]);
504            if (selectedNode == null) {
505                continue;
506            }
507            selectedPath[i] = new TreePath(selectedNode.getPath());
508        }
509        setSelectedPaths(selectedPath, notify);
510    }
511        
512    public void setSelectedNode(DefaultMutableTreeNode selectedNode) {
513        setSelectedNode(selectedNode, false);
514    }
515    
516    public void setSelectedNode(DefaultMutableTreeNode selectedNode, boolean notify) {
517        if (selectedNode == null) {
518            return ;
519        }
520        DefaultMutableTreeNode[] defaultMutableTreeNode = new DefaultMutableTreeNode[1];
521        defaultMutableTreeNode[0] = selectedNode;
522        setSelectedNodes(defaultMutableTreeNode, notify);
523    }
524    
525    public void setSelectedNodes(DefaultMutableTreeNode[] selectedNode) {
526        setSelectedNodes(selectedNode, false);
527    }
528    
529    public void setSelectedNodes(DefaultMutableTreeNode[] selectedNode, boolean notify) {
530        TreePath[] selectedPath = new TreePath[selectedNode.length];
531        for (int i = 0; i < selectedNode.length; i++) {
532            selectedPath[i] = new TreePath(selectedNode[i].getPath());
533        }
534        setSelectedPaths(selectedPath, notify);
535    }
536    
537    public void setSelectedPaths(TreePath[] selectedPath) {
538        setSelectedPaths(selectedPath, false);
539    }
540    
541    public void setSelectedPaths(TreePath[] selectedPath, boolean notify) {
542        final TreePath[] oldSelectedPathFinal = getSelectedTreePath();
543        selectionPathsOverride = selectedPath; // Must be placed after oldSelectedPathFinal = getSelectedTreePath(), because otherwise oldSelectedPathFinal will be = selectionPathsOverride.
544        final TreePath[] selectedPathFinal = selectedPath;
545        final boolean notifyFinal = notify;
546        Runnable runnable = new Runnable() {
547            public void run() {
548                TreeSelectionListener[] treeSelectionListeners = null;
549                synchronized (treeSynchronizeObject) {
550                    treeSelectionListeners = tree.getTreeSelectionListeners();
551                    for (int i = 0; i < treeSelectionListeners.length; i++) {
552                        tree.removeTreeSelectionListener(treeSelectionListeners[i]);
553                    }
554                    if (treeSelectionModel != null) {
555                        treeSelectionModel.setSelectionPathsHack(selectedPathFinal); // HACK
556                    } else {
557                        tree.setSelectionPaths(selectedPathFinal); // => NullPointerException??
558                    }
559                    
560                    if (selectedPathFinal != null && selectedPathFinal.length > 0 && (oldSelectedPathFinal == null || oldSelectedPathFinal.length == 0 || (selectedPathFinal[0] != null && !selectedPathFinal[0].equals(oldSelectedPathFinal[0])))) {
561                        scrollToVisible(selectedPathFinal[0]);
562                    }
563
564                    if (treeSelectionListeners != null) {
565                        for (int i = 0; i < treeSelectionListeners.length; i++) {
566                            tree.addTreeSelectionListener(treeSelectionListeners[i]);
567                        }
568                    }
569                }
570                
571                if (notifyFinal) { // The call to tree.setSelectionPath(null) will not cause an event, therefore it is made manually.
572                    fireSelectionListener();
573                }
574                
575                if (selectedPathFinal == selectionPathsOverride) {
576                    selectionPathsOverride = null;
577                }
578            }
579        };
580        SwingUtilities.invokeLater(runnable);
581    }
582   
583    public void scrollSelectedToCenterVisibleInvokeLater() {
584        Runnable runnable = new Runnable() {
585            public void run() {
586                scrollSelectedToCenterVisible();
587            }
588        };
589        SwingUtilities.invokeLater(runnable);
590    }
591    
592    public void scrollSelectedToCenterVisible() {
593        DefaultMutableTreeNode[] selectedNodes = getSelectedNodes();
594        if (selectedNodes != null && selectedNodes.length > 0) {
595            TreePath selectedPath = new TreePath(selectedNodes[0].getPath());
596            scrollToCenterVisible(selectedPath);
597        }
598    }
599
600    public void scrollToCenterVisible(TreePath selectedPath) {
601        int height = getHeight();
602        int rowHeight = tree.getRowHeight();
603        if (rowHeight == 0) {
604            rowHeight = 18;
605        }
606        
607        int centerRowOffset = height / 5 / rowHeight;
608        int rowForPath = tree.getRowForPath(selectedPath);
609        tree.scrollRowToVisible(rowForPath - centerRowOffset);
610        tree.scrollRowToVisible(rowForPath + centerRowOffset);
611    }
612    
613    public void scrollSelectedToVisibleInvokeLater() {
614        Runnable runnable = new Runnable() {
615            public void run() {
616                scrollSelectedToVisible();
617            }
618        };
619        SwingUtilities.invokeLater(runnable);
620    }
621    
622    public void scrollSelectedToVisible() {
623        DefaultMutableTreeNode[] selectedNodes = getSelectedNodes();
624        if (selectedNodes != null && selectedNodes.length > 0) {
625            TreePath selectedPath = new TreePath(selectedNodes[0].getPath());
626            scrollToVisible(selectedPath);
627        }
628    }
629    
630    public void scrollToVisible(TreePath selectedPath) {
631        tree.scrollPathToVisible(selectedPath);
632    }
633    
634    public void fireSelectionListener() {
635        fireSelectionListener(null);
636    }
637
638    public void fireSelectionListener(TreeSelectionEvent e) {
639        TreeSelectionListener[] tempTreeSelectionListeners = tree.getTreeSelectionListeners();
640        for (int i = 0; i < tempTreeSelectionListeners.length; i++) {
641            tempTreeSelectionListeners[i].valueChanged(e);
642        }
643    }
644    
645    public void selectFirstElement() {
646        selectFirstElement(false);
647    }
648    
649    public void selectFirstElement(boolean notify) {
650        if (getSelectedUserObject() != null) {
651            return ;
652        }
653        if (topNode.getChildCount() > 0) {
654            DefaultMutableTreeNode node = (DefaultMutableTreeNode)topNode.getChildAt(0);
655            setSelectedUserObject(node.getUserObject(), notify);
656        } else {
657            setSelectedUserObject(null, notify);
658        }
659    }
660    
661    public void selectNextSibling() {
662        TreePath[] selectedTreePath = getSelectedTreePath();
663        if (selectedTreePath == null) {
664            return ;
665        }
666        TreePath treePath = selectedTreePath[0];
667        DefaultMutableTreeNode node = (DefaultMutableTreeNode)treePath.getLastPathComponent();
668        while (node != null) {
669            DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
670            if (parent == null) {
671                return ;
672            }
673            DefaultMutableTreeNode childAfter = (DefaultMutableTreeNode)parent.getChildAfter(node);
674            if (childAfter != null) {
675                setSelectedNode(childAfter);
676                return ;
677            }
678            node = parent;
679        }
680    }
681    
682    public void selectLastElement() {
683        if (getSelectedUserObject() != null) {
684            return ;
685        }
686        if (selectLastActionListener == null) {
687            selectLastActionListener = new ActionListener() {
688                public void actionPerformed(final ActionEvent e) {
689                    final ActionListener actionListener = this;
690                    Runnable runnable = new Runnable() {
691                        public void run() {
692                            eventSelectLastElementActionPerformed(e, actionListener);
693                        }
694                    };
695                    SwingUtilities.invokeLater(runnable);
696                }
697            };
698        }
699        MyTimer.addActionListener(1, selectLastActionListener);
700    }
701
702    private void eventSelectLastElementActionPerformed(ActionEvent e, ActionListener actionListener) {
703        if (!selectLastElementLocal()) {
704            return ;
705        }
706        MyTimer.removeActionListener(actionListener);
707        if (actionListener == selectLastActionListener) {
708            selectLastActionListener = null;
709        }
710    }
711
712    private boolean selectLastElementLocal() {
713        try {
714            DefaultMutableTreeNode node = topNode.getLastLeaf();
715            setSelectedUserObject(node.getUserObject(), true);
716            return scrollToTheBottom();
717        } catch (NoSuchElementException e) {
718            setSelectedUserObject(null, true);
719        }
720        return true;
721    }
722    
723    public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
724        synchronized (treeSynchronizeObject) {
725            tree.addTreeSelectionListener(treeSelectionListener);
726        }
727    }
728
729    public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
730        synchronized (treeSynchronizeObject) {
731            tree.removeTreeSelectionListener(treeSelectionListener);
732        }
733    }
734    
735    public TreeSelectionListener[] getTreeSelectionListeners() {
736        synchronized (treeSynchronizeObject) {
737            return tree.getTreeSelectionListeners();
738        }
739    }
740    
741    public void expandSelected() {
742        DefaultMutableTreeNode[] selectedNodes = getSelectedNodes();
743        if (selectedNodes == null) {
744            return ;
745        }
746        for (int i = 0; i < selectedNodes.length; i++) {
747            expandTree(selectedNodes[i]);
748        }
749    }
750    
751    public void expandAll() {
752        GUIUtil.expandTree(tree, topNode);
753    }
754    
755    public void expandTree(DefaultMutableTreeNode parentDefaultMutableTreeNode) {
756        GUIUtil.expandTree(tree, parentDefaultMutableTreeNode);
757    }
758
759    public void expandNode(DefaultMutableTreeNode defaultMutableTreeNode) {
760        GUIUtil.expandTreeNode(tree, defaultMutableTreeNode);
761    }
762    
763    public void collapseAll() {
764        GUIUtil.collapseTree(tree);
765    }
766    
767    private static boolean isFirstLetterNavigationKeyListener(KeyListener keyListener) { // HACK
768        return keyListener.getClass().getName().startsWith("javax.swing.plaf.basic.BasicTreeUI$Handler");
769    }
770    
771    public void removeFirstLetterNavigation() { // HACK
772        removeFirstLetterNavigation = true;
773        KeyListener[] keyListeners = tree.getKeyListeners();
774        for (int i = 0; i < keyListeners.length; i++) {
775            if (isFirstLetterNavigationKeyListener(keyListeners[i])) {
776                tree.removeKeyListener(keyListeners[i]);
777            }
778        }
779    }
780    
781    @Override
782    protected JComponent getDelegatingJComponent() {
783        return tree;
784    }
785    
786    private static final long serialVersionUID = -1212159756993599368L;
787}