2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.ide.util;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.commander.CommanderPanel;
21 import com.intellij.ide.commander.ProjectListBuilder;
22 import com.intellij.ide.structureView.StructureViewBuilder;
23 import com.intellij.ide.structureView.StructureViewModel;
24 import com.intellij.ide.structureView.StructureViewTreeElement;
25 import com.intellij.ide.structureView.TreeBasedStructureViewBuilder;
26 import com.intellij.ide.structureView.newStructureView.TreeActionsOwner;
27 import com.intellij.ide.structureView.newStructureView.TreeModelWrapper;
28 import com.intellij.ide.util.treeView.AbstractTreeNode;
29 import com.intellij.ide.util.treeView.smartTree.Filter;
30 import com.intellij.ide.util.treeView.smartTree.SmartTreeStructure;
31 import com.intellij.ide.util.treeView.smartTree.Sorter;
32 import com.intellij.ide.util.treeView.smartTree.TreeElement;
33 import com.intellij.lang.LanguageStructureViewBuilder;
34 import com.intellij.openapi.Disposable;
35 import com.intellij.openapi.actionSystem.*;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.command.CommandProcessor;
38 import com.intellij.openapi.editor.Editor;
39 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
40 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
41 import com.intellij.openapi.keymap.KeymapUtil;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.ui.DialogWrapper;
44 import com.intellij.openapi.util.Comparing;
45 import com.intellij.openapi.util.Disposer;
46 import com.intellij.openapi.util.Ref;
47 import com.intellij.openapi.vfs.VirtualFile;
48 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
49 import com.intellij.pom.Navigatable;
50 import com.intellij.psi.PsiDocumentManager;
51 import com.intellij.psi.PsiElement;
52 import com.intellij.psi.PsiFile;
53 import com.intellij.psi.util.PsiUtilBase;
54 import com.intellij.ui.IdeBorderFactory;
55 import com.intellij.ui.ListScrollingUtil;
56 import com.intellij.ui.SideBorder;
57 import com.intellij.ui.SpeedSearchBase;
58 import com.intellij.util.ArrayUtil;
59 import com.intellij.util.containers.HashSet;
60 import org.jetbrains.annotations.NonNls;
61 import org.jetbrains.annotations.NotNull;
62 import org.jetbrains.annotations.Nullable;
65 import javax.swing.border.Border;
66 import javax.swing.event.ChangeEvent;
67 import javax.swing.event.ChangeListener;
69 import java.awt.event.ActionEvent;
70 import java.awt.event.ActionListener;
71 import java.beans.PropertyChangeEvent;
72 import java.beans.PropertyChangeListener;
73 import java.util.ArrayList;
74 import java.util.List;
77 public class FileStructureDialog extends DialogWrapper {
78 private final Editor myEditor;
79 private final Navigatable myNavigatable;
80 private final Project myProject;
81 private MyCommanderPanel myCommanderPanel;
82 private final StructureViewModel myTreeModel;
83 private final StructureViewModel myBaseTreeModel;
84 private SmartTreeStructure myTreeStructure;
85 private final MyTreeActionsOwner myTreeActionsOwner;
87 @NonNls private static final String ourPropertyKey = "FileStructure.narrowDown";
88 private boolean myShouldNarrowDown = false;
90 public FileStructureDialog(StructureViewModel structureViewModel,
91 @Nullable Editor editor,
93 Navigatable navigatable,
94 @NotNull final Disposable auxDisposable,
95 final boolean applySortAndFilter) {
99 myNavigatable = navigatable;
100 myBaseTreeModel = structureViewModel;
101 if (applySortAndFilter) {
102 myTreeActionsOwner = new MyTreeActionsOwner();
103 myTreeModel = new TreeModelWrapper(structureViewModel, myTreeActionsOwner);
106 myTreeActionsOwner = null;
107 myTreeModel = structureViewModel;
110 PsiFile psiFile = getPsiFile(project);
112 final PsiElement psiElement = getCurrentElement(psiFile);
114 //myDialog.setUndecorated(true);
117 if (psiElement != null) {
118 if (structureViewModel.shouldEnterElement(psiElement)) {
119 myCommanderPanel.getBuilder().enterElement(psiElement, PsiUtilBase.getVirtualFile(psiElement));
122 myCommanderPanel.getBuilder().selectElement(psiElement, PsiUtilBase.getVirtualFile(psiElement));
126 Disposer.register(myDisposable, auxDisposable);
129 protected PsiFile getPsiFile(final Project project) {
130 return PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument());
134 protected Border createContentPaneBorder() {
138 public void dispose() {
139 myCommanderPanel.dispose();
143 protected String getDimensionServiceKey() {
144 return "#com.intellij.ide.util.FileStructureDialog";
147 public JComponent getPreferredFocusedComponent() {
148 return IdeFocusTraversalPolicy.getPreferredFocusedComponent(myCommanderPanel);
152 protected PsiElement getCurrentElement(@Nullable final PsiFile psiFile) {
153 if (psiFile == null) return null;
155 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
157 Object elementAtCursor = myTreeModel.getCurrentEditorElement();
158 if (elementAtCursor instanceof PsiElement) {
159 return (PsiElement)elementAtCursor;
165 protected JComponent createCenterPanel() {
166 myCommanderPanel = new MyCommanderPanel(myProject);
167 myTreeStructure = new MyStructureTreeStructure();
169 List<FileStructureFilter> fileStructureFilters = new ArrayList<FileStructureFilter>();
170 if (myTreeActionsOwner != null) {
171 for(Filter filter: myBaseTreeModel.getFilters()) {
172 if (filter instanceof FileStructureFilter) {
173 final FileStructureFilter fsFilter = (FileStructureFilter)filter;
174 myTreeActionsOwner.setFilterIncluded(fsFilter, true);
175 fileStructureFilters.add(fsFilter);
180 PsiFile psiFile = getPsiFile(myProject);
181 boolean showRoot = isShowRoot(psiFile);
182 ProjectListBuilder projectListBuilder = new ProjectListBuilder(myProject, myCommanderPanel, myTreeStructure, null, showRoot) {
183 protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) {
184 return Comparing.equal(((StructureViewTreeElement)node.getValue()).getValue(), element);
187 protected void refreshSelection() {
188 myCommanderPanel.scrollSelectionInView();
189 if (myShouldNarrowDown) {
190 myCommanderPanel.updateSpeedSearch();
194 protected List<AbstractTreeNode> getAllAcceptableNodes(final Object[] childElements, VirtualFile file) {
195 ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
196 for (Object childElement : childElements) {
197 result.add((AbstractTreeNode)childElement);
202 myCommanderPanel.setBuilder(projectListBuilder);
203 myCommanderPanel.setTitlePanelVisible(false);
206 public void actionPerformed(AnActionEvent e) {
207 final boolean succeeded = myCommanderPanel.navigateSelectedElement();
209 unregisterCustomShortcutSet(myCommanderPanel);
212 }.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(), myCommanderPanel);
214 myCommanderPanel.setPreferredSize(new Dimension(400, 500));
216 JPanel panel = new JPanel(new GridBagLayout());
218 addNarrowDownCheckbox(panel);
220 for(FileStructureFilter filter: fileStructureFilters) {
221 addFilterCheckbox(panel, filter);
224 myCommanderPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
225 panel.add(myCommanderPanel,
226 new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
231 protected boolean isShowRoot(final PsiFile psiFile) {
232 StructureViewBuilder viewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile);
233 return viewBuilder instanceof TreeBasedStructureViewBuilder && ((TreeBasedStructureViewBuilder)viewBuilder).isRootNodeShown();
236 private void addNarrowDownCheckbox(final JPanel panel) {
237 final JCheckBox checkBox = new JCheckBox(IdeBundle.message("checkbox.narrow.down.the.list.on.typing"));
238 checkBox.setSelected(PropertiesComponent.getInstance().isTrueValue(ourPropertyKey));
239 checkBox.addChangeListener(new ChangeListener() {
240 public void stateChanged(ChangeEvent e) {
241 myShouldNarrowDown = checkBox.isSelected();
242 PropertiesComponent.getInstance().setValue(ourPropertyKey, Boolean.toString(myShouldNarrowDown));
244 ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder();
245 if (builder == null) {
248 builder.addUpdateRequest();
252 checkBox.setFocusable(false);
254 new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0));
257 private void addFilterCheckbox(final JPanel panel, final FileStructureFilter fileStructureFilter) {
258 final JCheckBox chkFilter = new JCheckBox();
259 chkFilter.addActionListener(new ActionListener() {
260 public void actionPerformed(final ActionEvent e) {
261 myTreeActionsOwner.setFilterIncluded(fileStructureFilter, !chkFilter.isSelected());
262 myTreeStructure.rebuildTree();
263 ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder();
264 if (builder != null) {
265 builder.updateList(true);
269 chkFilter.setFocusable(false);
270 String text = fileStructureFilter.getCheckBoxText();
271 final Shortcut[] shortcuts = fileStructureFilter.getShortcut();
272 if (shortcuts.length > 0) {
273 text += " (" + KeymapUtil.getShortcutText(shortcuts [0]) + ")";
275 public void actionPerformed(final AnActionEvent e) {
278 }.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), panel);
280 chkFilter.setText(text);
282 new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0));
286 protected JComponent createSouthPanel() {
290 public CommanderPanel getPanel() {
291 return myCommanderPanel;
294 private class MyCommanderPanel extends CommanderPanel implements DataProvider {
296 protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) {
300 public MyCommanderPanel(Project _project) {
301 super(_project, false);
302 myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
303 myListSpeedSearch.addChangeListener(new PropertyChangeListener() {
304 public void propertyChange(PropertyChangeEvent evt) {
305 ProjectListBuilder builder = (ProjectListBuilder)getBuilder();
306 if (builder == null) {
309 builder.addUpdateRequest(hasPrefixShortened(evt));
310 ApplicationManager.getApplication().invokeLater(new Runnable() {
312 int index = myList.getSelectedIndex();
313 if (index != -1 && index < myList.getModel().getSize()) {
314 myList.clearSelection();
315 ListScrollingUtil.selectItem(myList, index);
318 ListScrollingUtil.ensureSelectionExists(myList);
324 myListSpeedSearch.setComparator(createSpeedSearchComparator());
327 private boolean hasPrefixShortened(final PropertyChangeEvent evt) {
328 return evt.getNewValue() != null && evt.getOldValue() != null &&
329 ((String)evt.getNewValue()).length() < ((String)evt.getOldValue()).length();
332 public boolean navigateSelectedElement() {
333 final Ref<Boolean> succeeded = new Ref<Boolean>();
334 final CommandProcessor commandProcessor = CommandProcessor.getInstance();
335 commandProcessor.executeCommand(myProject, new Runnable() {
337 succeeded.set(MyCommanderPanel.super.navigateSelectedElement());
338 IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation();
340 }, "Navigate", null);
341 if (succeeded.get()) {
342 close(CANCEL_EXIT_CODE);
344 return succeeded.get();
347 public Object getData(String dataId) {
348 Object selectedElement = myCommanderPanel.getSelectedValue();
350 if (selectedElement instanceof TreeElement) selectedElement = ((StructureViewTreeElement)selectedElement).getValue();
352 if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
353 return selectedElement instanceof Navigatable ? selectedElement : myNavigatable;
356 if (OpenFileDescriptor.NAVIGATE_IN_EDITOR.is(dataId)) return myEditor;
358 return getDataImpl(dataId);
361 public String getEnteredPrefix() {
362 return myListSpeedSearch.getEnteredPrefix();
365 public void updateSpeedSearch() {
366 myListSpeedSearch.refreshSelection();
369 public void scrollSelectionInView() {
370 int selectedIndex = myList.getSelectedIndex();
371 if (selectedIndex >= 0) {
372 ListScrollingUtil.ensureIndexIsVisible(myList, selectedIndex, 0);
377 private class MyStructureTreeStructure extends SmartTreeStructure {
378 public MyStructureTreeStructure() {
379 super(FileStructureDialog.this.myProject, myTreeModel);
382 public Object[] getChildElements(Object element) {
383 Object[] childElements = super.getChildElements(element);
385 if (!myShouldNarrowDown) {
386 return childElements;
389 String enteredPrefix = myCommanderPanel.getEnteredPrefix();
390 if (enteredPrefix == null) {
391 return childElements;
394 ArrayList<Object> filteredElements = new ArrayList<Object>(childElements.length);
395 SpeedSearchBase.SpeedSearchComparator speedSearchComparator = createSpeedSearchComparator();
397 for (Object child : childElements) {
398 if (child instanceof AbstractTreeNode) {
399 Object value = ((AbstractTreeNode)child).getValue();
400 if (value instanceof TreeElement) {
401 String name = ((TreeElement)value).getPresentation().getPresentableText();
405 if (!speedSearchComparator.doCompare(enteredPrefix, name)) {
410 filteredElements.add(child);
412 return ArrayUtil.toObjectArray(filteredElements);
415 public void rebuildTree() {
416 getChildElements(getRootElement()); // for some reason necessary to rebuild tree correctly
421 private static SpeedSearchBase.SpeedSearchComparator createSpeedSearchComparator() {
422 return new SpeedSearchBase.SpeedSearchComparator() {
423 public void translateCharacter(final StringBuilder buf, final char ch) {
425 buf.append(".*"); // overrides '*' handling to skip (,) in parameter lists
429 buf.append(".*"); // get:int should match any getter returning int
431 super.translateCharacter(buf, ch);
437 private class MyTreeActionsOwner implements TreeActionsOwner {
438 private final Set<Filter> myFilters = new HashSet<Filter>();
440 public void setActionActive(String name, boolean state) {
443 public boolean isActionActive(String name) {
444 for (final Sorter sorter : myBaseTreeModel.getSorters()) {
445 if (sorter.getName().equals(name)) {
446 if (!sorter.isVisible()) return true;
449 for(Filter filter: myFilters) {
450 if (filter.getName().equals(name)) return true;
452 return Sorter.ALPHA_SORTER_ID.equals(name);
455 public void setFilterIncluded(final FileStructureFilter filter, final boolean selected) {
457 myFilters.add(filter);
460 myFilters.remove(filter);