selection tests
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / util / FileStructurePopup.java
1 /*
2  * Copyright 2000-2011 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.ide.util;
17
18 import com.intellij.ide.DataManager;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.structureView.StructureViewModel;
21 import com.intellij.ide.structureView.StructureViewTreeElement;
22 import com.intellij.ide.structureView.newStructureView.StructureViewComponent;
23 import com.intellij.ide.structureView.newStructureView.TreeModelWrapper;
24 import com.intellij.ide.util.treeView.AbstractTreeNode;
25 import com.intellij.ide.util.treeView.NodeRenderer;
26 import com.intellij.ide.util.treeView.smartTree.*;
27 import com.intellij.navigation.ItemPresentation;
28 import com.intellij.openapi.Disposable;
29 import com.intellij.openapi.MnemonicHelper;
30 import com.intellij.openapi.actionSystem.*;
31 import com.intellij.openapi.application.AccessToken;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.command.CommandProcessor;
34 import com.intellij.openapi.editor.Editor;
35 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
36 import com.intellij.openapi.keymap.KeymapUtil;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.ui.popup.JBPopup;
39 import com.intellij.openapi.ui.popup.JBPopupFactory;
40 import com.intellij.openapi.util.*;
41 import com.intellij.openapi.util.text.StringUtil;
42 import com.intellij.openapi.wm.IdeFocusManager;
43 import com.intellij.psi.PsiDocumentManager;
44 import com.intellij.psi.PsiElement;
45 import com.intellij.psi.PsiFile;
46 import com.intellij.ui.*;
47 import com.intellij.ui.popup.AbstractPopup;
48 import com.intellij.ui.popup.PopupUpdateProcessor;
49 import com.intellij.ui.speedSearch.ElementFilter;
50 import com.intellij.ui.speedSearch.SpeedSearchUtil;
51 import com.intellij.ui.treeStructure.Tree;
52 import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
53 import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure;
54 import com.intellij.util.Alarm;
55 import com.intellij.util.ArrayUtil;
56 import com.intellij.util.containers.ContainerUtil;
57 import com.intellij.util.containers.HashSet;
58 import com.intellij.util.ui.UIUtil;
59 import com.intellij.util.ui.tree.TreeUtil;
60 import org.jetbrains.annotations.Nls;
61 import org.jetbrains.annotations.NonNls;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64
65 import javax.swing.*;
66 import javax.swing.event.ChangeEvent;
67 import javax.swing.event.ChangeListener;
68 import javax.swing.event.TreeSelectionEvent;
69 import javax.swing.event.TreeSelectionListener;
70 import javax.swing.tree.DefaultMutableTreeNode;
71 import javax.swing.tree.TreePath;
72 import java.awt.*;
73 import java.awt.event.ActionEvent;
74 import java.awt.event.ActionListener;
75 import java.awt.event.MouseAdapter;
76 import java.awt.event.MouseEvent;
77 import java.util.*;
78 import java.util.List;
79
80 /**
81  * @author Konstantin Bulenkov
82  */
83 public class FileStructurePopup implements Disposable {
84   private static final Comparator<TextRange> TEXT_RANGE_COMPARATOR = new Comparator<TextRange>() {
85     @Override
86     public int compare(TextRange o1, TextRange o2) {
87       if (o1.getStartOffset() == o2.getStartOffset()) {
88         return o2.getEndOffset() - o1.getEndOffset(); //longer is better
89       }
90       return o1.getStartOffset() - o2.getStartOffset();
91     }
92   };
93   private final Editor myEditor;
94   private final Project myProject;
95   private final StructureViewModel myTreeModel;
96   private final StructureViewModel myBaseTreeModel;
97   private final TreeStructureActionsOwner myTreeActionsOwner;
98   private JBPopup myPopup;
99
100   @NonNls private static final String narrowDownPropertyKey = "FileStructurePopup.narrowDown";
101   private boolean myShouldNarrowDown = false;
102   private Tree myTree;
103   private FilteringTreeBuilder myAbstractTreeBuilder;
104   private String myTitle;
105   private TreeSpeedSearch mySpeedSearch;
106   private SmartTreeStructure myTreeStructure;
107   private int myPreferredWidth;
108   private final FilteringTreeStructure myFilteringStructure;
109   private PsiElement myInitialPsiElement;
110
111   public FileStructurePopup(StructureViewModel structureViewModel,
112                             @Nullable Editor editor,
113                             Project project,
114                             @NotNull final Disposable auxDisposable,
115                             final boolean applySortAndFilter) {
116     myProject = project;
117     myEditor = editor;
118     myBaseTreeModel = structureViewModel;
119     Disposer.register(this, auxDisposable);
120     if (applySortAndFilter) {
121       myTreeActionsOwner = new TreeStructureActionsOwner(myBaseTreeModel);
122       myTreeModel = new TreeModelWrapper(structureViewModel, myTreeActionsOwner);
123     }
124     else {
125       myTreeActionsOwner = null;
126       myTreeModel = structureViewModel;
127     }    
128
129     myTreeStructure = new SmartTreeStructure(project, myTreeModel){
130       public void rebuildTree() {
131         if (!myPopup.isDisposed()) {
132           super.rebuildTree();
133         }
134       }
135
136       public boolean isToBuildChildrenInBackground(final Object element) {
137         return getRootElement() == element;
138       }
139
140       protected TreeElementWrapper createTree() {
141         return new StructureViewComponent.StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel);
142       }
143
144       @Override
145       public String toString() {
146         return "structure view tree structure(model=" + myTreeModel + ")";
147       }
148     };
149     myTree = new JBTreeWithHintProvider(new DefaultMutableTreeNode(myTreeStructure.getRootElement())) {
150       @Override
151       protected PsiElement getPsiElementForHint(Object selectedValue) {
152         //noinspection ConstantConditions
153         return getPsi((FilteringTreeStructure.FilteringNode)((DefaultMutableTreeNode)selectedValue).getUserObject());
154       }
155     };
156     myTree.setCellRenderer(new NodeRenderer() {
157       @Override
158       protected void doAppend(@NotNull @Nls String fragment,
159                               @NotNull SimpleTextAttributes attributes,
160                               boolean isMainText,
161                               boolean selected) {
162         SpeedSearchUtil.appendFragmentsForSpeedSearch(myTree, fragment, attributes, selected, this);
163       }
164
165       @Override
166       public void doAppend(@NotNull String fragment, @NotNull SimpleTextAttributes attributes, boolean selected) {
167         SpeedSearchUtil.appendFragmentsForSpeedSearch(myTree, fragment, attributes, selected, this);
168       }
169
170       @Override
171       public void doAppend(String fragment, boolean selected) {
172         SpeedSearchUtil.appendFragmentsForSpeedSearch(myTree, fragment, SimpleTextAttributes.REGULAR_ATTRIBUTES, selected, this);
173       }
174     });
175     myTree.setRootVisible(false);
176     myTree.setShowsRootHandles(true);
177
178     mySpeedSearch = new TreeSpeedSearch(myTree, TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING, true) {      
179       @Override
180       protected Point getComponentLocationOnScreen() {
181         return myPopup.getContent().getLocationOnScreen();
182       }
183
184       @Override
185       protected Rectangle getComponentVisibleRect() {
186         return myPopup.getContent().getVisibleRect();
187       }
188
189       @Override
190       protected Object findElement(String s) {
191         List<ObjectWithWeight> elements = new ArrayList<ObjectWithWeight>();
192         s = s.trim();
193         final ListIterator<Object> it = getElementIterator(0);
194         while (it.hasNext()) {
195           final ObjectWithWeight o = new ObjectWithWeight(it.next(), s, getComparator());
196           if (!o.weights.isEmpty()) {
197             elements.add(o);
198           }
199         }
200         ObjectWithWeight cur = null;
201         ArrayList<ObjectWithWeight> current = new ArrayList<ObjectWithWeight>();
202         for (ObjectWithWeight element : elements) {
203           if (cur == null) {
204             cur = element;
205             current.add(cur);
206             continue;
207           }
208
209           final int i = element.compareWith(cur);
210           if (i == 0) {
211             current.add(element);
212           } else if (i < 0) {
213             cur = element;
214             current.clear();
215             current.add(cur);
216           }
217         }
218
219         return current.isEmpty() ? null : findClosestTo(myInitialPsiElement, current);
220       }
221
222       @Nullable
223       private Object findClosestTo(PsiElement path, ArrayList<ObjectWithWeight> paths) {
224         if (path == null || myInitialPsiElement == null) {
225           return paths.get(0).node;
226         }
227         final Set<PsiElement> parents = getAllParents(myInitialPsiElement);
228         ArrayList<TreePath> cur = new ArrayList<TreePath>();
229         int max = -1;
230         for (ObjectWithWeight p : paths) {
231           final Object last = ((TreePath)p.node).getLastPathComponent();
232           final List<PsiElement> elements = new ArrayList<PsiElement>();
233           final Object object = ((DefaultMutableTreeNode)last).getUserObject();
234           if (object instanceof FilteringTreeStructure.FilteringNode) {
235             FilteringTreeStructure.FilteringNode node = (FilteringTreeStructure.FilteringNode)object;
236             while (node != null) {
237               elements.add(getPsi(node));
238               node = node.getParentNode();
239             }
240             final int size = ContainerUtil.intersection(parents, elements).size();
241             if (size > max) {
242               max = size;
243               cur.clear();
244               cur.add((TreePath)p.node);
245             } else if (size == max) {
246               cur.add((TreePath)p.node);
247             }
248           }
249         }
250
251         Collections.sort(cur, new Comparator<TreePath>() {
252           @Override
253           public int compare(TreePath o1, TreePath o2) {
254             return o2.getPathCount() - o1.getPathCount();
255           }
256         });
257         return cur.isEmpty() ? null : cur.get(0);
258       }
259
260       class ObjectWithWeight {
261         final Object node;
262         final List<TextRange> weights = new ArrayList<TextRange>();
263
264         ObjectWithWeight(Object element, String pattern, SpeedSearchComparator comparator) {
265           this.node = element;
266           final String text = getElementText(element);
267           if (text != null) {
268             final Iterable<TextRange> ranges = comparator.matchingFragments(pattern, text);
269             if (ranges != null) {
270               for (TextRange range : ranges) {
271                 weights.add(range);
272               }
273             }
274           }
275           Collections.sort(weights, TEXT_RANGE_COMPARATOR);
276         }
277         
278         int compareWith(ObjectWithWeight obj) {
279           final List<TextRange> w = obj.weights;
280           for (int i = 0; i < weights.size(); i++) {
281             if (i >= w.size()) return 1;
282             final int result = TEXT_RANGE_COMPARATOR.compare(weights.get(i), w.get(i));
283             if (result != 0) {
284               return result;
285             }
286           }
287           
288           return 0;
289         }
290         
291       }
292       
293     };
294     mySpeedSearch.setComparator(new SpeedSearchComparator(false, true));
295
296     final FileStructurePopupFilter filter = new FileStructurePopupFilter();
297     myFilteringStructure = new FilteringTreeStructure(filter, myTreeStructure, ApplicationManager.getApplication().isUnitTestMode());
298     myAbstractTreeBuilder = new FilteringTreeBuilder(myTree, filter, myFilteringStructure, null) {
299       @Override
300       protected boolean validateNode(Object child) {
301         return StructureViewComponent.isValid(child);
302       }
303
304       @Override
305       public void revalidateTree() {
306         //myTree.revalidate();
307         //myTree.repaint();
308       }
309
310       @Override
311       public boolean isToEnsureSelectionOnFocusGained() {
312         return false;
313       }
314     };
315
316     myAbstractTreeBuilder.getUi().getUpdater().setDelay(1);
317
318     //myAbstractTreeBuilder.setCanYieldUpdate(true);
319     Disposer.register(this, myAbstractTreeBuilder);
320   }
321
322   public void show() {
323     //final long time = System.currentTimeMillis();
324     final ActionCallback treeHasBuilt = new ActionCallback();
325     IdeFocusManager.getInstance(myProject).typeAheadUntil(treeHasBuilt);
326     JComponent panel = createCenterPanel();
327     new MnemonicHelper().register(panel);
328     boolean shouldSetWidth = DimensionService.getInstance().getSize(getDimensionServiceKey(), myProject) == null;
329     myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, null)
330       .setTitle(myTitle)
331       .setResizable(true)
332       .setModalContext(false)
333       .setFocusable(true)
334       .setMovable(true)
335       .setBelongsToGlobalPopupStack(true)
336       //.setCancelOnClickOutside(false) //for debug and snapshots
337       .setCancelKeyEnabled(false)
338       .setDimensionServiceKey(null, getDimensionServiceKey(), false)
339       .setCancelCallback(new Computable<Boolean>() {
340         @Override
341         public Boolean compute() {
342           DimensionService.getInstance().setLocation(getDimensionServiceKey(), myPopup.getLocationOnScreen(), myProject);
343           return true;
344         }
345       })
346       .createPopup();
347     
348     myTree.addTreeSelectionListener(new TreeSelectionListener() {
349       @Override
350       public void valueChanged(TreeSelectionEvent e) {
351         if (myPopup.isVisible()) {
352           final PopupUpdateProcessor updateProcessor = myPopup.getUserData(PopupUpdateProcessor.class);
353           if (updateProcessor != null) {
354             final AbstractTreeNode node = getSelectedNode();
355             updateProcessor.updatePopup(node);
356           }
357         }
358       }
359     });
360     Disposer.register(myPopup, this);
361     Disposer.register(myPopup, new Disposable() {
362       @Override
363       public void dispose() {
364         if (!treeHasBuilt.isDone()) {
365           treeHasBuilt.setRejected();
366         }
367       }
368     });
369     final Point location = DimensionService.getInstance().getLocation(getDimensionServiceKey(), myProject);
370     if (location != null) {
371       myPopup.showInScreenCoordinates(myEditor.getContentComponent(), location);
372     } else {
373       myPopup.showCenteredInCurrentWindow(myProject);
374     }
375
376     ((AbstractPopup)myPopup).setShowHints(true);
377     if (shouldSetWidth) {
378       myPopup.setSize(new Dimension(myPreferredWidth + 10, myPopup.getSize().height));
379     }
380
381     myAbstractTreeBuilder.expandAll(new Runnable() {
382       @Override
383       public void run() {
384         IdeFocusManager.getInstance(myProject).requestFocus(myTree, true);
385         myAbstractTreeBuilder.queueUpdate().doWhenDone(new Runnable() {
386           @Override
387           public void run() {
388             myInitialPsiElement = getCurrentElement(getPsiFile(myProject));
389             selectPsiElement(myInitialPsiElement);
390             treeHasBuilt.setDone();
391             //long t = System.currentTimeMillis() - time;
392             //System.out.println("Shown in " + t + "ms");
393           }
394         });
395       }
396     });
397     if (!ApplicationManager.getApplication().isUnitTestMode()) {
398       final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD, myPopup);
399       alarm.addRequest(new Runnable() {
400         String filter = "";
401
402         @Override
403         public void run() {
404           alarm.cancelAllRequests();
405           String prefix = mySpeedSearch.getEnteredPrefix();
406           myTree.getEmptyText().setText(StringUtil.isEmpty(prefix) ? "Nothing to show" : "Can't find '" + prefix + "'");
407           if (prefix == null) prefix = "";
408
409           if (!filter.equals(prefix)) {
410             filter = prefix;
411             myAbstractTreeBuilder.refilter(null, false, false).doWhenProcessed(new Runnable() {
412               @Override
413               public void run() {
414                 myTree.repaint();
415                 //if (mySpeedSearch.isPopupActive()) {
416                 //  mySpeedSearch.refreshSelection();
417                 //}
418               }
419             });
420           }
421           alarm.addRequest(this, 300);
422         }
423       }, 300);
424     }
425   }
426
427   @Nullable
428   public FilteringTreeStructure.FilteringNode selectPsiElement(PsiElement element) {
429     Set<PsiElement> parents = getAllParents(element);
430
431     FilteringTreeStructure.FilteringNode node = (FilteringTreeStructure.FilteringNode)myAbstractTreeBuilder.getRootElement();
432     while (node != null) {
433       boolean changed = false;
434       for (FilteringTreeStructure.FilteringNode n : node.children()) {
435         final PsiElement psiElement = getPsi(n);
436         if (psiElement != null && parents.contains(psiElement)) {
437           node = n;
438           changed = true;
439           break;
440         }
441       }
442       if (!changed) {
443         myAbstractTreeBuilder.getUi().select(node, null);
444         if (myAbstractTreeBuilder.getSelectedElements().isEmpty()) {
445           TreeUtil.selectFirstNode(myTree);
446         }
447         return node;
448       }
449     }
450     TreeUtil.selectFirstNode(myTree);
451     return null;
452   }
453
454   private static Set<PsiElement> getAllParents(PsiElement element) {
455     Set<PsiElement> parents = new java.util.HashSet<PsiElement>();
456
457     while (element != null) {
458       parents.add(element);
459       if (element instanceof PsiFile) break;
460       element = element.getParent();
461     }
462     return parents;
463   }
464
465   @Nullable
466   private PsiElement getPsi(FilteringTreeStructure.FilteringNode n) {
467     final Object delegate = n.getDelegate();
468     if (delegate instanceof StructureViewComponent.StructureViewTreeElementWrapper) {
469       final TreeElement value = ((StructureViewComponent.StructureViewTreeElementWrapper)delegate).getValue();
470       if (value instanceof StructureViewTreeElement) {
471         final Object element = ((StructureViewTreeElement)value).getValue();
472         if (element instanceof PsiElement) {
473           return (PsiElement)element;
474         }
475       }
476     }
477     return null;
478   }
479
480   @Nullable
481   protected PsiFile getPsiFile(final Project project) {
482     return PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument());
483   }
484
485   public void dispose() {
486   }
487
488   protected static String getDimensionServiceKey() {
489     return "StructurePopup";
490   }
491
492   @Nullable
493   public PsiElement getCurrentElement(@Nullable final PsiFile psiFile) {
494     if (psiFile == null) return null;
495
496     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
497
498     Object elementAtCursor = myTreeModel.getCurrentEditorElement();
499     if (elementAtCursor instanceof PsiElement) {
500       return (PsiElement)elementAtCursor;
501     }
502
503     return null;
504   }
505
506   protected JComponent createCenterPanel() {
507     List<FileStructureFilter> fileStructureFilters = new ArrayList<FileStructureFilter>();
508     List<FileStructureNodeProvider> fileStructureNodeProviders = new ArrayList<FileStructureNodeProvider>();
509     if (myTreeActionsOwner != null) {
510       for(Filter filter: myBaseTreeModel.getFilters()) {
511         if (filter instanceof FileStructureFilter) {
512           final FileStructureFilter fsFilter = (FileStructureFilter)filter;
513           myTreeActionsOwner.setActionIncluded(fsFilter, true);
514           fileStructureFilters.add(fsFilter);
515         }
516       }
517
518       if (myBaseTreeModel instanceof ProvidingTreeModel) {
519         for (NodeProvider provider : ((ProvidingTreeModel)myBaseTreeModel).getNodeProviders()) {
520           if (provider instanceof FileStructureNodeProvider) {
521             fileStructureNodeProviders.add((FileStructureNodeProvider)provider);
522           }
523         }
524       }
525     }
526     final JPanel panel = new JPanel(new BorderLayout());
527     JPanel comboPanel = new JPanel(new GridLayout(0, 2, 0, 0));
528
529     final Shortcut[] F4 = ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet().getShortcuts();
530     final Shortcut[] ENTER = CustomShortcutSet.fromString("ENTER").getShortcuts();
531     final CustomShortcutSet shortcutSet = new CustomShortcutSet(ArrayUtil.mergeArrays(F4, ENTER));
532     new AnAction() {
533       public void actionPerformed(AnActionEvent e) {
534         final boolean succeeded = navigateSelectedElement();
535         if (succeeded) {
536           unregisterCustomShortcutSet(panel);
537         }
538       }
539     }.registerCustomShortcutSet(shortcutSet, panel);
540
541     new AnAction() {
542       public void actionPerformed(AnActionEvent e) {
543         if (mySpeedSearch != null && mySpeedSearch.isPopupActive()) {
544           mySpeedSearch.hidePopup();
545         } else {
546           myPopup.cancel();
547         }
548       }
549     }.registerCustomShortcutSet(CustomShortcutSet.fromString("ESCAPE"), myTree);
550     
551     myTree.addMouseListener(new MouseAdapter() {
552       @Override
553       public void mouseClicked(MouseEvent e) {
554         if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1) {          
555           navigateSelectedElement();
556         }
557       }
558     });
559
560     for(FileStructureFilter filter: fileStructureFilters) {
561       addCheckbox(comboPanel, filter);
562     }
563
564     for (FileStructureNodeProvider provider : fileStructureNodeProviders) {
565       addCheckbox(comboPanel, provider);
566     }
567     myPreferredWidth = Math.max(comboPanel.getPreferredSize().width, 350);
568     panel.add(comboPanel, BorderLayout.NORTH);
569     JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myAbstractTreeBuilder.getTree());
570     scrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.BOTTOM));
571     panel.add(scrollPane, BorderLayout.CENTER);
572     panel.add(createSouthPanel(), BorderLayout.SOUTH);
573     DataManager.registerDataProvider(panel, new DataProvider() {
574       @Override
575       public Object getData(@NonNls String dataId) {
576         if (PlatformDataKeys.PROJECT.is(dataId)) {
577           return myProject;
578         }
579         if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
580           final Object node = ContainerUtil.getFirstItem(myAbstractTreeBuilder.getSelectedElements());
581           if (!(node instanceof FilteringTreeStructure.FilteringNode)) return null;
582           return getPsi((FilteringTreeStructure.FilteringNode)node);
583         }
584         if (LangDataKeys.POSITION_ADJUSTER_POPUP.is(dataId)) {
585           return myPopup;
586         }
587         return null;
588       }
589     });
590     myFilteringStructure.rebuild();
591     return panel;
592   }
593
594   @Nullable
595   private AbstractTreeNode getSelectedNode() {
596     Object component = myTree.getSelectionPath().getLastPathComponent();
597     if (component instanceof DefaultMutableTreeNode) {
598       component = ((DefaultMutableTreeNode)component).getUserObject();
599       if (component instanceof FilteringTreeStructure.FilteringNode) {
600         component = ((FilteringTreeStructure.FilteringNode)component).getDelegate();
601         if (component instanceof AbstractTreeNode) {
602           return (AbstractTreeNode)component;
603         }
604       }
605     }
606     return null;
607   }
608
609   public boolean navigateSelectedElement() {
610     final Ref<Boolean> succeeded = new Ref<Boolean>();
611     final CommandProcessor commandProcessor = CommandProcessor.getInstance();
612     commandProcessor.executeCommand(myProject, new Runnable() {
613       public void run() {
614         final AbstractTreeNode selectedNode = getSelectedNode();
615         if (selectedNode != null) {
616           if (selectedNode.canNavigateToSource()) {
617             selectedNode.navigate(true);
618             succeeded.set(true);
619           } else {
620             succeeded.set(false);
621           }
622         } else {
623           succeeded.set(false);
624         }
625
626
627         IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
628       }
629     }, "Navigate", null);
630     if (succeeded.get()) {
631       myPopup.cancel();
632     }
633     return succeeded.get();
634   }
635
636   private JComponent createSouthPanel() {
637     final JCheckBox checkBox = new JCheckBox(IdeBundle.message("checkbox.narrow.down.on.typing"));
638     checkBox.setSelected(PropertiesComponent.getInstance().getBoolean(narrowDownPropertyKey, true));
639     checkBox.addChangeListener(new ChangeListener() {
640       public void stateChanged(ChangeEvent e) {
641         myShouldNarrowDown = checkBox.isSelected();
642         PropertiesComponent.getInstance().setValue(narrowDownPropertyKey, Boolean.toString(myShouldNarrowDown));
643
644         myAbstractTreeBuilder.queueUpdate();
645       }
646     });
647
648     checkBox.setFocusable(false);
649     UIUtil.applyStyle(UIUtil.ComponentStyle.MINI, checkBox);
650     final JPanel panel = new JPanel(new BorderLayout());
651     panel.add(checkBox, BorderLayout.WEST);
652     return panel;
653   }
654
655   private void addCheckbox(final JPanel panel, final TreeAction action) {
656     String text = action instanceof FileStructureFilter ? ((FileStructureFilter)action).getCheckBoxText() :
657                   action instanceof FileStructureNodeProvider ? ((FileStructureNodeProvider)action).getCheckBoxText() : null;    
658
659     if (text == null) return;
660
661     Shortcut[] shortcuts = action instanceof FileStructureFilter ?
662                           ((FileStructureFilter)action).getShortcut() : ((FileStructureNodeProvider)action).getShortcut();
663
664
665     
666     final JCheckBox chkFilter = new JCheckBox();
667     final boolean selected = getDefaultValue(action);
668     chkFilter.setSelected(selected);
669     myTreeActionsOwner.setActionIncluded(action, action instanceof FileStructureFilter ? !selected : selected);
670     chkFilter.addActionListener(new ActionListener() {
671       public void actionPerformed(final ActionEvent e) {
672         final boolean state = chkFilter.isSelected();
673         saveState(action, state);
674         myTreeActionsOwner.setActionIncluded(action, action instanceof FileStructureFilter ? !state : state);
675         //final String filter = mySpeedSearch.isPopupActive() ? mySpeedSearch.getEnteredPrefix() : null;
676         //mySpeedSearch.hidePopup();
677         Object selection = ContainerUtil.getFirstItem(myAbstractTreeBuilder.getSelectedElements());
678         if (selection instanceof FilteringTreeStructure.FilteringNode) {
679           selection = ((FilteringTreeStructure.FilteringNode)selection).getDelegate();
680         }
681         myTreeStructure.rebuildTree();
682         myFilteringStructure.rebuild();
683         
684         final Object sel = selection;
685         myAbstractTreeBuilder.refilter(sel, true, false).doWhenProcessed(new Runnable() {
686           @Override
687           public void run() {
688             if (mySpeedSearch.isPopupActive()) {
689               mySpeedSearch.refreshSelection();
690             }
691           }
692         });
693       }
694     });
695     chkFilter.setFocusable(false);
696
697     if (shortcuts.length > 0) {
698       text += " (" + KeymapUtil.getShortcutText(shortcuts[0]) + ")";
699       new AnAction() {
700         public void actionPerformed(final AnActionEvent e) {
701           chkFilter.doClick();
702         }
703       }.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), myTree);
704     }
705     chkFilter.setText(text);
706     panel.add(chkFilter);
707   }
708
709   private static boolean getDefaultValue(TreeAction action) {
710     if (action instanceof PropertyOwner) {
711       final String propertyName = ((PropertyOwner)action).getPropertyName();
712       return PropertiesComponent.getInstance().getBoolean(getPropertyName(propertyName), false);
713     }
714
715     return false;
716   }
717
718   private static void saveState(TreeAction action, boolean state) {
719     if (action instanceof PropertyOwner) {
720       final String propertyName = ((PropertyOwner)action).getPropertyName();
721       PropertiesComponent.getInstance().setValue(getPropertyName(propertyName), Boolean.toString(state));
722     }
723   }
724
725   public static String getPropertyName(String propertyName) {
726     return propertyName + ".file.structure.state";
727   }
728
729   public void setTitle(String title) {
730     myTitle = title;
731   }
732
733   public Tree getTree() {
734     return myTree;
735   }
736
737   public TreeSpeedSearch getSpeedSearch() {
738     return mySpeedSearch;
739   }
740
741   public FilteringTreeBuilder getTreeBuilder() {
742     return myAbstractTreeBuilder;
743   }
744
745   private class FileStructurePopupFilter implements ElementFilter {
746     private String myLastFilter = null;
747     private HashSet<Object> myVisibleParents = new HashSet<Object>();
748     @Override
749     public boolean shouldBeShowing(Object value) {
750       if (!myShouldNarrowDown) return true;
751
752       String filter = mySpeedSearch != null && !StringUtil.isEmpty(mySpeedSearch.getEnteredPrefix()) 
753                       ? mySpeedSearch.getEnteredPrefix() : null;
754       if (!StringUtil.equals(myLastFilter, filter)) {
755         myVisibleParents.clear();
756         myLastFilter = filter;
757       }
758       if (filter != null) {
759         if (myVisibleParents.contains(value)) {
760           return true;
761         }
762
763         final String text = getText(value);
764         if (text == null) return false;
765         boolean matches = mySpeedSearch.matchingFragments(text) != null;
766
767         if (matches) {
768           Object o = value;
769           while (o instanceof FilteringTreeStructure.FilteringNode && (o = ((FilteringTreeStructure.FilteringNode)o).getParent()) != null) {
770             myVisibleParents.add(o);
771           }
772           return true;
773         } else {
774           return false;
775         }
776         
777       } 
778       return true;
779     }
780
781     @Nullable
782     private String getText(Object node) {
783       final String text = String.valueOf(node);
784       if (text != null) {
785         return text;
786       }
787
788       if (node instanceof StructureViewComponent.StructureViewTreeElementWrapper) {
789         final AccessToken token = ApplicationManager.getApplication().acquireReadActionLock();
790         try {
791           final ItemPresentation presentation = ((StructureViewComponent.StructureViewTreeElementWrapper)node).getValue().getPresentation();
792           return presentation.getPresentableText();
793         }
794         finally {
795           token.finish();
796         }
797       }
798
799       return null;
800     }
801   }
802 }