reduce memory
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / options / newEditor / OptionsTree.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.options.newEditor;
17
18 import com.intellij.ide.util.treeView.NodeDescriptor;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.options.Configurable;
21 import com.intellij.openapi.options.ConfigurableGroup;
22 import com.intellij.openapi.options.SearchableConfigurable;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.ActionCallback;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.openapi.util.SystemInfo;
27 import com.intellij.ui.*;
28 import com.intellij.ui.components.panels.NonOpaquePanel;
29 import com.intellij.ui.treeStructure.*;
30 import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
31 import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure;
32 import com.intellij.util.ui.UIUtil;
33 import com.intellij.util.ui.update.MergingUpdateQueue;
34 import com.intellij.util.ui.update.Update;
35 import org.jetbrains.annotations.Nullable;
36
37 import javax.swing.*;
38 import javax.swing.border.EmptyBorder;
39 import javax.swing.event.TreeExpansionEvent;
40 import javax.swing.event.TreeExpansionListener;
41 import javax.swing.event.TreeSelectionEvent;
42 import javax.swing.event.TreeSelectionListener;
43 import javax.swing.plaf.TreeUI;
44 import javax.swing.plaf.basic.BasicTreeUI;
45 import javax.swing.tree.DefaultMutableTreeNode;
46 import javax.swing.tree.TreeCellRenderer;
47 import javax.swing.tree.TreePath;
48 import javax.swing.tree.TreeSelectionModel;
49 import java.awt.*;
50 import java.awt.event.*;
51 import java.util.*;
52 import java.util.List;
53
54 public class OptionsTree extends JPanel implements Disposable, OptionsEditorColleague {
55   Project myProject;
56   final SimpleTree myTree;
57   List<ConfigurableGroup> myGroups;
58   FilteringTreeBuilder myBuilder;
59   Root myRoot;
60   OptionsEditorContext myContext;
61
62   Map<Configurable, EditorNode> myConfigurable2Node = new HashMap<Configurable, EditorNode>();
63
64   MergingUpdateQueue mySelection;
65   private final OptionsTree.Renderer myRendrer;
66
67   public OptionsTree(Project project, ConfigurableGroup[] groups, OptionsEditorContext context) {
68     myProject = project;
69     myGroups = Arrays.asList(groups);
70     myContext = context;
71
72
73     myRoot = new Root();
74     final SimpleTreeStructure structure = new SimpleTreeStructure() {
75       public Object getRootElement() {
76         return myRoot;
77       }
78     };
79
80     myTree = new MyTree();
81     myTree.setBorder(new EmptyBorder(0, 1, 0, 0));
82
83     myTree.setRowHeight(-1);
84     myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
85     myRendrer = new Renderer();
86     myTree.setCellRenderer(myRendrer);
87     myTree.setRootVisible(false);
88     myTree.setShowsRootHandles(false);
89     myBuilder = new MyBuilder(structure);
90     myBuilder.setFilteringMerge(300, null);
91     Disposer.register(this, myBuilder);
92
93     myBuilder.updateFromRoot();
94
95     setLayout(new BorderLayout());
96
97     myTree.addComponentListener(new ComponentAdapter() {
98       @Override
99       public void componentResized(final ComponentEvent e) {
100         revalidateTree();
101       }
102
103       @Override
104       public void componentMoved(final ComponentEvent e) {
105         revalidateTree();
106       }
107
108       @Override
109       public void componentShown(final ComponentEvent e) {
110         revalidateTree();
111       }
112     });
113
114     final JScrollPane scrolls = ScrollPaneFactory.createScrollPane(myTree);
115     scrolls.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
116
117     add(scrolls, BorderLayout.CENTER);
118
119     mySelection = new MergingUpdateQueue("OptionsTree", 150, false, this, this, this).setRestartTimerOnAdd(true);
120     myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
121       public void valueChanged(final TreeSelectionEvent e) {
122         final TreePath path = e.getNewLeadSelectionPath();
123         if (path == null) {
124           queueSelection(null);
125         }
126         else {
127           final Base base = extractNode(path.getLastPathComponent());
128           queueSelection(base != null ? base.getConfigurable() : null);
129         }
130       }
131     });
132     myTree.addKeyListener(new KeyListener() {
133       public void keyTyped(final KeyEvent e) {
134         _onTreeKeyEvent(e);
135       }
136
137       public void keyPressed(final KeyEvent e) {
138         _onTreeKeyEvent(e);
139       }
140
141       public void keyReleased(final KeyEvent e) {
142         _onTreeKeyEvent(e);
143       }
144     });
145   }
146
147   protected void _onTreeKeyEvent(KeyEvent e) {
148     final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
149
150     final Object action = myTree.getInputMap().get(stroke);
151     if (action == null) {
152       onTreeKeyEvent(e);
153     }
154   }
155
156   protected void onTreeKeyEvent(KeyEvent e) {
157
158   }
159
160
161   ActionCallback select(@Nullable Configurable configurable) {
162     return queueSelection(configurable);
163   }
164
165   public void selectFirst() {
166     for (ConfigurableGroup eachGroup : myGroups) {
167       final Configurable[] kids = eachGroup.getConfigurables();
168       if (kids.length > 0) {
169         queueSelection(kids[0]);
170         return;
171       }
172     }
173   }
174
175   ActionCallback queueSelection(final Configurable configurable) {
176     final ActionCallback callback = new ActionCallback();
177     final Update update = new Update(this) {
178       public void run() {
179         if (configurable == null) {
180           myTree.getSelectionModel().clearSelection();
181           myContext.fireSelected(null, OptionsTree.this);
182         }
183         else {
184           final EditorNode editorNode = myConfigurable2Node.get(configurable);
185           FilteringTreeStructure.Node editorUiNode = myBuilder.getVisibleNodeFor(editorNode);
186           if (!myBuilder.getSelectedElements().contains(editorUiNode)) {
187             myBuilder.select(editorUiNode, new Runnable() {
188               public void run() {
189                 fireSelected(configurable, callback);
190               }
191             });
192           } else {
193             myBuilder.scrollSelectionToVisible(new Runnable() {
194               public void run() {
195                 fireSelected(configurable, callback);
196               }
197             }, false);
198           }
199         }
200       }
201
202       @Override
203       public void setRejected() {
204         super.setRejected();
205         callback.setRejected();
206       }
207     };
208     mySelection.queue(update);
209     return callback;
210   }
211
212   private void fireSelected(Configurable configurable, final ActionCallback callback) {
213     myContext.fireSelected(configurable, this).doWhenProcessed(new Runnable() {
214       public void run() {
215         callback.setDone();
216       }
217     });
218   }
219
220   void revalidateTree() {
221     myTree.invalidate();
222     myTree.setRowHeight(myTree.getRowHeight() == -1 ? -2 : -1);
223     myTree.revalidate();
224     myTree.repaint();
225   }
226
227   public JTree getTree() {
228     return myTree;
229   }
230
231   public List<Configurable> getPathToRoot(final Configurable configurable) {
232     final ArrayList<Configurable> path = new ArrayList<Configurable>();
233
234     EditorNode eachNode = myConfigurable2Node.get(configurable);
235     if (eachNode == null) return path;
236
237     while (eachNode != null) {
238       path.add(eachNode.getConfigurable());
239       final SimpleNode parent = eachNode.getParent();
240       if (parent instanceof EditorNode) {
241         eachNode = (EditorNode)parent;
242       }
243       else {
244         break;
245       }
246     }
247
248     return path;
249   }
250
251   @Nullable
252   public Configurable getParentFor(final Configurable configurable) {
253     final List<Configurable> path = getPathToRoot(configurable);
254     if (path.size() > 1) {
255       return path.get(1);
256     }
257     else {
258       return null;
259     }
260   }
261
262   public SimpleNode findNodeFor(final Configurable toSelect) {
263     return myConfigurable2Node.get(toSelect);
264   }
265
266   class Renderer extends GroupedElementsRenderer.Tree implements TreeCellRenderer {
267
268
269     private JLabel myHandle;
270
271     @Override
272     protected void layout() {
273       myRendererComponent.setOpaqueActive(false);
274
275       myRendererComponent.add(mySeparatorComponent, BorderLayout.NORTH);
276
277       final NonOpaquePanel content = new NonOpaquePanel(new BorderLayout());
278       myHandle = new JLabel("", JLabel.CENTER);
279       if (!SystemInfo.isMac) {
280         myHandle.setBorder(new EmptyBorder(0, 2, 0, 2));
281       }
282       myHandle.setOpaque(false);
283       content.add(myHandle, BorderLayout.WEST);
284       content.add(myComponent, BorderLayout.CENTER);
285       myRendererComponent.add(content, BorderLayout.CENTER);
286     }
287
288     public Component getTreeCellRendererComponent(final JTree tree,
289                                                   final Object value,
290                                                   final boolean selected,
291                                                   final boolean expanded,
292                                                   final boolean leaf,
293                                                   final int row,
294                                                   final boolean hasFocus) {
295
296
297       JComponent result;
298       Color fg = UIUtil.getTreeTextForeground();
299
300       final Base base = extractNode(value);
301       if (base instanceof EditorNode) {
302         DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
303
304         final EditorNode editor = (EditorNode)base;
305         ConfigurableGroup group = null;
306         if (editor.getParent() == myRoot) {
307           final DefaultMutableTreeNode prevValue = ((DefaultMutableTreeNode)value).getPreviousSibling();
308           if (prevValue == null || prevValue instanceof LoadingNode) {
309             group = editor.getGroup();
310           }
311           else {
312             final Base prevBase = extractNode(prevValue);
313             if (prevBase instanceof EditorNode) {
314               final EditorNode prevEditor = (EditorNode)prevBase;
315               if (prevEditor.getGroup() != editor.getGroup()) {
316                 group = editor.getGroup();
317               }
318             }
319           }
320         }
321
322         int forcedWidth = 2000;
323         TreePath path = tree.getPathForRow(row);
324         if (path == null) {
325           if (value instanceof DefaultMutableTreeNode) {
326             path = new TreePath(((DefaultMutableTreeNode)value).getPath());
327           }
328         }
329
330         final boolean toStretch = tree.isVisible() && path != null;
331
332         if (toStretch) {
333           final Rectangle visibleRect = tree.getVisibleRect();
334
335           int nestingLevel = tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2;
336
337           final int left = UIManager.getInt("Tree.leftChildIndent");
338           final int right = UIManager.getInt("Tree.rightChildIndent");
339
340           final Insets treeInsets = tree.getInsets();
341
342           int indent = (left + right) * nestingLevel + (treeInsets != null ? treeInsets.left + treeInsets.right : 0);
343
344           forcedWidth = visibleRect.width > 0 ? visibleRect.width - indent : forcedWidth;
345         }
346
347         result = configureComponent(base.getText(), base.getText(), null, null, row == -1 ? true : selected, group != null,
348                                     group != null ? group.getDisplayName() : null, forcedWidth - 4);
349
350
351         if (base.isError()) {
352           fg = Color.red;
353         }
354         else if (base.isModified()) {
355           fg = Color.blue;
356         }
357
358       }
359       else {
360         result = configureComponent(value.toString(), null, null, null, selected, false, null, -1);
361       }
362
363       if (value instanceof DefaultMutableTreeNode) {
364         DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
365         TreePath nodePath = new TreePath(node.getPath());
366         myHandle.setIcon(((SimpleTree)tree).getHandleIcon(node, nodePath));
367       } else {
368         myHandle.setIcon(null);
369       }
370
371
372       myTextLabel.setForeground(selected ? UIUtil.getTreeSelectionForeground() : fg);
373
374       myTextLabel.setOpaque(selected);
375
376       return result;
377     }
378
379     protected JComponent createItemComponent() {
380       myTextLabel = new ErrorLabel();
381       return myTextLabel;
382     }
383
384     public boolean isUnderHandle(final Point point) {
385       final Point handlePoint = SwingUtilities.convertPoint(myRendererComponent, point, myHandle);
386       final Rectangle bounds = myHandle.getBounds();
387       return bounds.x < handlePoint.x && bounds.getMaxX() >= handlePoint.x;
388     }
389   }
390
391   @Nullable
392   private Base extractNode(Object object) {
393     if (object instanceof DefaultMutableTreeNode) {
394       final DefaultMutableTreeNode uiNode = (DefaultMutableTreeNode)object;
395       final Object o = uiNode.getUserObject();
396       if (o instanceof FilteringTreeStructure.Node) {
397         return (Base)((FilteringTreeStructure.Node)o).getDelegate();
398       }
399     }
400
401     return null;
402   }
403
404   abstract class Base extends CachingSimpleNode {
405
406     protected Base(final SimpleNode aParent) {
407       super(aParent);
408     }
409
410     String getText() {
411       return null;
412     }
413
414     boolean isModified() {
415       return false;
416     }
417
418     boolean isError() {
419       return false;
420     }
421
422     Configurable getConfigurable() {
423       return null;
424     }
425   }
426
427   class Root extends Base {
428
429     Root() {
430       super(null);
431     }
432
433     protected SimpleNode[] buildChildren() {
434       List<SimpleNode> result = new ArrayList<SimpleNode>();
435       for (ConfigurableGroup eachGroup : myGroups) {
436         result.addAll(buildGroup(eachGroup));
437       }
438
439       return result.isEmpty() ? NO_CHILDREN : result.toArray(new SimpleNode[result.size()]);
440     }
441
442     private List<EditorNode> buildGroup(final ConfigurableGroup eachGroup) {
443       List<EditorNode> result = new ArrayList<EditorNode>();
444       final Configurable[] kids = eachGroup.getConfigurables();
445       if (kids.length > 0) {
446         for (Configurable eachKid : kids) {
447           if (isInvisibleNode(eachKid)) {
448             result.addAll(OptionsTree.this.buildChildren(eachKid, this, eachGroup));
449           }
450           else {
451             result.add(new EditorNode(this, eachKid, eachGroup));
452           }
453         }
454
455       }
456       return sort(result);
457     }
458   }
459
460   private static boolean isInvisibleNode(final Configurable child) {
461     return child instanceof SearchableConfigurable.Parent && !((SearchableConfigurable.Parent)child).isVisible();
462   }
463
464   private static List<EditorNode> sort(List<EditorNode> c) {
465     List<EditorNode> cc = new ArrayList<EditorNode>(c);
466     Collections.sort(cc, new Comparator<EditorNode>() {
467       public int compare(final EditorNode o1, final EditorNode o2) {
468         return getConfigurableDisplayName(o1.getConfigurable()).compareToIgnoreCase(getConfigurableDisplayName(o2.getConfigurable()));
469       }
470     });
471     return cc;
472   }
473
474   private static String getConfigurableDisplayName(final Configurable c) {
475     final String name = c.getDisplayName();
476     return name != null ? name : "{ Unnamed Page:" + c.getClass().getSimpleName() + " }";
477   }
478
479   private List<EditorNode> buildChildren(final Configurable configurable, SimpleNode parent, final ConfigurableGroup group) {
480     if (configurable instanceof Configurable.Composite) {
481       final Configurable[] kids = ((Configurable.Composite)configurable).getConfigurables();
482       final List<EditorNode> result = new ArrayList<EditorNode>(kids.length);
483       for (Configurable child : kids) {
484         if (isInvisibleNode(child)) {
485           result.addAll(buildChildren(child, parent, group));
486         }
487         result.add(new EditorNode(parent, child, group));
488         myContext.registerKid(configurable, child);
489       }
490       return result; // TODO: DECIDE IF INNERS SHOULD BE SORTED: sort(result);
491     }
492     else {
493       return Collections.EMPTY_LIST;
494     }
495   }
496
497   private static final EditorNode[] EMPTY_EN_ARRAY = new EditorNode[0];
498   class EditorNode extends Base {
499     Configurable myConfigurable;
500     ConfigurableGroup myGroup;
501
502     EditorNode(SimpleNode parent, Configurable configurable, @Nullable ConfigurableGroup group) {
503       super(parent);
504       myConfigurable = configurable;
505       myGroup = group;
506       myConfigurable2Node.put(configurable, this);
507       addPlainText(getConfigurableDisplayName(configurable));
508     }
509
510     protected EditorNode[] buildChildren() {
511       List<EditorNode> list = OptionsTree.this.buildChildren(myConfigurable, this, null);
512       return list.isEmpty() ? EMPTY_EN_ARRAY : list.toArray(new EditorNode[list.size()]);
513     }
514
515     @Override
516     public boolean isContentHighlighted() {
517       return getParent() == myRoot;
518     }
519
520     @Override
521     Configurable getConfigurable() {
522       return myConfigurable;
523     }
524
525     @Override
526     public int getWeight() {
527       if (getParent() == myRoot) {
528         return Integer.MAX_VALUE - myGroups.indexOf(myGroup);
529       }
530       else {
531         return WeightBasedComparator.UNDEFINED_WEIGHT;
532       }
533     }
534
535     public ConfigurableGroup getGroup() {
536       return myGroup;
537     }
538
539     @Override
540     String getText() {
541       return getConfigurableDisplayName(myConfigurable).replace("\n", " ");
542     }
543
544     @Override
545     boolean isModified() {
546       return myContext.getModified().contains(myConfigurable);
547     }
548
549     @Override
550     boolean isError() {
551       return myContext.getErrors().containsKey(myConfigurable);
552     }
553   }
554
555   public void dispose() {
556   }
557
558   public ActionCallback onSelected(final Configurable configurable, final Configurable oldConfigurable) {
559     return queueSelection(configurable);
560   }
561
562   public ActionCallback onModifiedAdded(final Configurable colleague) {
563     myTree.repaint();
564     return new ActionCallback.Done();
565   }
566
567   public ActionCallback onModifiedRemoved(final Configurable configurable) {
568     myTree.repaint();
569     return new ActionCallback.Done();
570   }
571
572   public ActionCallback onErrorsChanged() {
573     return new ActionCallback.Done();
574   }
575
576   public void processTextEvent(KeyEvent e) {
577     myTree.processKeyEvent(e);
578   }
579
580   private class MyTree extends SimpleTree {
581
582     private MyTree() {
583       getInputMap().clear();
584     }
585
586     @Override
587     protected boolean highlightSingleNode() {
588       return false;
589     }
590
591     @Override
592     public void setUI(final TreeUI ui) {
593       TreeUI actualUI = ui;
594       if (!(ui instanceof MyTreeUi)) {
595         actualUI = new MyTreeUi();
596       }
597       super.setUI(actualUI);
598     }
599
600     @Override
601     protected boolean isCustomUI() {
602       return true;
603     }
604
605     @Override
606     protected void configureUiHelper(final TreeUIHelper helper) {
607     }
608
609     @Override
610     public boolean getScrollableTracksViewportWidth() {
611       return true;
612     }
613
614
615     @Override
616     public void processKeyEvent(final KeyEvent e) {
617       TreePath path = myTree.getSelectionPath();
618       if (path != null) {
619         if (e.getKeyCode() == KeyEvent.VK_LEFT) {
620           if (isExpanded(path)) {
621             collapsePath(path);
622             return;
623           }
624         } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
625           if (isCollapsed(path)) {
626             expandPath(path);
627             return;
628           }
629         }
630       }
631
632       super.processKeyEvent(e);
633     }
634
635     @Override
636     protected void processMouseEvent(final MouseEvent e) {
637       final MyTreeUi ui = (MyTreeUi)myTree.getUI();
638       if (e.getID() == MouseEvent.MOUSE_RELEASED && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !ui.isToggleEvent(e)) {
639         final TreePath path = getPathForLocation(e.getX(), e.getY());
640         if (path != null) {
641           final Rectangle bounds = getPathBounds(path);
642           if (bounds != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
643             DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
644             final boolean selected = isPathSelected(path);
645             final boolean expanded = isExpanded(path);
646             final Component comp =
647               myRendrer.getTreeCellRendererComponent(this, node, selected, expanded, node.isLeaf(), getRowForPath(path), isFocusOwner());
648
649             comp.setBounds(bounds);
650             comp.validate();
651
652             Point point = new Point(e.getX() - bounds.x, e.getY() - bounds.y);
653             if (myRendrer.isUnderHandle(point)) {
654               ui.toggleExpandState(path);
655               e.consume();
656               return;
657             }
658           }
659         }
660       }
661
662       super.processMouseEvent(e);
663     }
664
665     private class MyTreeUi extends BasicTreeUI {
666
667       @Override
668       public void toggleExpandState(final TreePath path) {
669         super.toggleExpandState(path);
670       }
671
672       @Override
673       public boolean isToggleEvent(final MouseEvent event) {
674         return super.isToggleEvent(event);
675       }
676
677       @Override
678       protected boolean shouldPaintExpandControl(final TreePath path,
679                                                  final int row,
680                                                  final boolean isExpanded,
681                                                  final boolean hasBeenExpanded,
682                                                  final boolean isLeaf) {
683         return false;
684       }
685
686       @Override
687       protected void paintHorizontalPartOfLeg(final Graphics g,
688                                               final Rectangle clipBounds,
689                                               final Insets insets,
690                                               final Rectangle bounds,
691                                               final TreePath path,
692                                               final int row,
693                                               final boolean isExpanded,
694                                               final boolean hasBeenExpanded,
695                                               final boolean isLeaf) {
696
697       }
698
699       @Override
700       protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) {
701       }
702     }
703   }
704
705   private class MyBuilder extends FilteringTreeBuilder {
706
707     List<Object> myToExpandOnResetFilter;
708     boolean myRefilteringNow;
709     boolean myWasHoldingFilter;
710
711     public MyBuilder(SimpleTreeStructure structure) {
712       super(OptionsTree.this.myProject, OptionsTree.this.myTree, OptionsTree.this.myContext.getFilter(), structure, new WeightBasedComparator(false));
713       myTree.addTreeExpansionListener(new TreeExpansionListener() {
714         public void treeExpanded(TreeExpansionEvent event) {
715           invalidateExpansions();
716         }
717
718         public void treeCollapsed(TreeExpansionEvent event) {
719           invalidateExpansions();
720         }
721       });
722     }
723
724     private void invalidateExpansions() {
725       if (!myRefilteringNow) {
726         myToExpandOnResetFilter = null;
727       }
728     }
729
730     @Override
731     protected boolean isSelectable(final Object nodeObject) {
732       return nodeObject instanceof EditorNode;
733     }
734
735     @Override
736     public boolean isAutoExpandNode(final NodeDescriptor nodeDescriptor) {
737       return myContext.isHoldingFilter();
738     }
739
740     @Override
741     protected ActionCallback refilterNow(Object preferredSelection, boolean adjustSelection) {
742       final List<Object> toRestore = new ArrayList<Object>();
743       if (myContext.isHoldingFilter() && !myWasHoldingFilter && myToExpandOnResetFilter == null) {
744         myToExpandOnResetFilter = myBuilder.getUi().getExpandedElements();
745       } else if (!myContext.isHoldingFilter() && myWasHoldingFilter && myToExpandOnResetFilter != null) {
746         toRestore.addAll(myToExpandOnResetFilter);
747         myToExpandOnResetFilter = null;
748       }
749
750       myWasHoldingFilter = myContext.isHoldingFilter();
751
752       ActionCallback result = super.refilterNow(preferredSelection, adjustSelection);
753       myRefilteringNow = true;
754       return result.doWhenDone(new Runnable() {
755         public void run() {
756           myRefilteringNow = false;
757           if (!myContext.isHoldingFilter()) {
758             restoreExpandedState(toRestore);
759           }
760         }
761       });
762     }
763
764     private void restoreExpandedState(List<Object> toRestore) {
765       TreePath[] selected = myTree.getSelectionPaths();
766       if (selected == null) {
767         selected = new TreePath[0];
768       }
769
770       List<TreePath> toCollapse = new ArrayList<TreePath>();
771
772       for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) {
773         if (!myTree.isExpanded(eachRow)) continue;
774
775         TreePath eachVisiblePath = myTree.getPathForRow(eachRow);
776         if (eachVisiblePath == null) continue;
777
778         Object eachElement = myBuilder.getElementFor(eachVisiblePath.getLastPathComponent());
779         if (toRestore.contains(eachElement)) continue;
780
781
782         for (TreePath eachSelected : selected) {
783           if (!eachVisiblePath.isDescendant(eachSelected)) {
784             toCollapse.add(eachVisiblePath);
785           }
786         }
787       }
788
789       for (TreePath each : toCollapse) {
790         myTree.collapsePath(each);
791       }
792
793     }
794   }
795 }