Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / platform-api / src / com / intellij / ui / treeStructure / filtered / FilteringTreeBuilder.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.ui.treeStructure.filtered;
17
18 import com.intellij.ide.util.treeView.AbstractTreeBuilder;
19 import com.intellij.ide.util.treeView.AbstractTreeStructure;
20 import com.intellij.ide.util.treeView.NodeDescriptor;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.util.ActionCallback;
23 import com.intellij.openapi.util.Disposer;
24 import com.intellij.ui.speedSearch.ElementFilter;
25 import com.intellij.ui.treeStructure.PatchedDefaultMutableTreeNode;
26 import com.intellij.ui.treeStructure.SimpleTree;
27 import com.intellij.ui.treeStructure.Tree;
28 import com.intellij.util.ui.tree.TreeUtil;
29 import com.intellij.util.ui.update.MergingUpdateQueue;
30 import com.intellij.util.ui.update.Update;
31 import org.jetbrains.annotations.Nullable;
32
33 import javax.swing.*;
34 import javax.swing.event.TreeSelectionEvent;
35 import javax.swing.event.TreeSelectionListener;
36 import javax.swing.tree.DefaultMutableTreeNode;
37 import javax.swing.tree.DefaultTreeModel;
38 import javax.swing.tree.TreePath;
39 import java.util.Comparator;
40
41 public class FilteringTreeBuilder extends AbstractTreeBuilder {
42
43   private Object myLastSuccessfulSelect;
44   private final Tree myTree;
45
46   private MergingUpdateQueue myRefilterQueue;
47
48   public FilteringTreeBuilder(Tree tree,
49                               ElementFilter filter,
50                               AbstractTreeStructure structure,
51                               Comparator<NodeDescriptor> comparator) {
52     super(tree,
53           (DefaultTreeModel)tree.getModel(),
54           structure instanceof FilteringTreeStructure ? structure
55                                                       : new FilteringTreeStructure(filter, structure),
56           comparator);
57
58     myTree = tree;
59     initRootNode();
60
61     if (filter instanceof ElementFilter.Active) {
62       ((ElementFilter.Active)filter).addListener(new ElementFilter.Listener() {
63         public ActionCallback update(final Object preferredSelection, final boolean adjustSelection, final boolean now) {
64           return refilter(preferredSelection, adjustSelection, now);
65         }
66       }, this);
67     }
68
69     myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
70       public void valueChanged(TreeSelectionEvent e) {
71         TreePath newPath = e.getNewLeadSelectionPath();
72         if (newPath != null) {
73           Object element = getElementFor(newPath.getLastPathComponent());
74           if (element != null) {
75             myLastSuccessfulSelect = element;
76           }
77         }
78       }
79     });
80   }
81
82   public boolean isAlwaysShowPlus(NodeDescriptor nodeDescriptor) {
83     return false;
84   }
85
86   public boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) {
87     return true;
88   }
89
90   protected final DefaultMutableTreeNode createChildNode(final NodeDescriptor childDescr) {
91     return new PatchedDefaultMutableTreeNode(childDescr);
92   }
93
94   public void setFilteringMerge(int gap, @Nullable JComponent modalityStateComponent) {
95     if (myRefilterQueue != null) {
96       Disposer.dispose(myRefilterQueue);
97       myRefilterQueue = null;
98     }
99
100     if (gap >= 0) {
101       JComponent stateComponent = modalityStateComponent;
102       if (stateComponent == null) {
103         stateComponent = myTree;
104       }
105
106       myRefilterQueue = new MergingUpdateQueue("FilteringTreeBuilder", gap, false, stateComponent, this, myTree);
107       myRefilterQueue.setRestartTimerOnAdd(true);
108     }
109   }
110
111   protected boolean isSelectable(Object nodeObject) {
112     return true;
113   }
114
115   public ActionCallback refilter() {
116     return refilter(null, true, false);
117   }
118
119   public ActionCallback refilter(@Nullable final Object preferredSelection, final boolean adjustSelection, final boolean now) {
120     if (myRefilterQueue != null) {
121       myRefilterQueue.cancelAllUpdates();
122     }
123     final ActionCallback callback = new ActionCallback();
124     final Runnable afterCancelUpdate = new Runnable() {
125       @Override
126       public void run() {
127         if (myRefilterQueue == null || now) {
128           refilterNow(preferredSelection, adjustSelection).doWhenDone(new Runnable() {
129             @Override
130             public void run() {
131               callback.setDone();
132             }
133           });
134         }
135         else {
136           myRefilterQueue.queue(new Update(this) {
137             public void run() {
138               refilterNow(preferredSelection, adjustSelection).notifyWhenDone(callback);
139             }
140
141             @Override
142             public void setRejected() {
143               super.setRejected();
144               callback.setDone();
145             }
146           });
147         }
148       }
149     };
150     if (!ApplicationManager.getApplication().isUnitTestMode()) {
151       getUi().cancelUpdate().doWhenProcessed(afterCancelUpdate);
152     } else {
153       afterCancelUpdate.run();
154     }
155
156     return callback;
157   }
158
159
160   protected ActionCallback refilterNow(final Object preferredSelection, final boolean adjustSelection) {
161     final ActionCallback selectionDone = new ActionCallback();
162
163     getFilteredStructure().refilter();
164     final Runnable selectionRunnable = new Runnable() {
165       public void run() {
166         revalidateTree();
167
168         Object toSelect = preferredSelection != null ? preferredSelection : myLastSuccessfulSelect;
169
170         if (adjustSelection && toSelect != null) {
171           final FilteringTreeStructure.FilteringNode nodeToSelect = getFilteredStructure().getVisibleNodeFor(toSelect);
172
173           if (nodeToSelect != null) {
174             select(nodeToSelect, new Runnable() {
175               public void run() {
176                 if (getSelectedElements().contains(nodeToSelect)) {
177                   myLastSuccessfulSelect = getOriginalNode(nodeToSelect);
178                 }
179                 selectionDone.setDone();
180               }
181             });
182           }
183           else {
184             TreeUtil.ensureSelection(myTree);
185             selectionDone.setDone();
186           }
187         }
188         else {
189           selectionDone.setDone();
190         }
191       }
192     };
193     if (!ApplicationManager.getApplication().isUnitTestMode()) {
194       queueUpdate().doWhenProcessed(selectionRunnable);
195     } else {
196       selectionRunnable.run();
197     }
198
199     final ActionCallback result = new ActionCallback();
200
201     selectionDone.doWhenDone(new Runnable() {
202       public void run() {
203         if (!ApplicationManager.getApplication().isUnitTestMode()) {
204           scrollSelectionToVisible(new Runnable() {
205             public void run() {
206               getReady(this).notify(result);
207             }
208           }, false);
209         } else {
210           result.setDone();
211         }
212       }
213     }).doWhenRejected(new Runnable() {
214       @Override
215       public void run() {
216         result.setRejected();
217       }
218     });
219
220     return result;
221   }
222
223   public void revalidateTree() {
224     revalidateTree(myTree);
225   }
226
227   public static void revalidateTree(Tree tree) {
228     tree.invalidate();
229     tree.setRowHeight(tree.getRowHeight() == -1 ? -2 : -1);
230     tree.revalidate();
231     tree.repaint();
232   }
233
234
235   private FilteringTreeStructure getFilteredStructure() {
236     return ((FilteringTreeStructure)getTreeStructure());
237   }
238
239   //todo kirillk
240   private boolean isSimpleTree() {
241     return myTree instanceof SimpleTree;
242   }
243
244   @Nullable
245   private Object getSelected() {
246     if (isSimpleTree()) {
247       FilteringTreeStructure.FilteringNode selected = (FilteringTreeStructure.FilteringNode)((SimpleTree)myTree).getSelectedNode();
248       return selected != null ? selected.getDelegate() : null;
249     } else {
250       final Object[] nodes = myTree.getSelectedNodes(Object.class, null);
251       return nodes.length > 0 ? nodes[0] : null;
252     }
253   }
254
255   public FilteringTreeStructure.FilteringNode getVisibleNodeFor(Object nodeObject) {
256     FilteringTreeStructure structure = getFilteredStructure();
257     return structure != null ? structure.getVisibleNodeFor(nodeObject) : null;
258   }
259
260   public Object getOriginalNode(Object node) {
261     return ((FilteringTreeStructure.FilteringNode)node).getDelegate();
262   }
263
264   @Override
265   protected Object transformElement(Object object) {
266     return getOriginalNode(object);
267   }
268
269   @Nullable
270   public Object getElementFor(Object node) {
271     return getUi().getElementFor(node);
272   }
273 }