| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.ide.util; |
| |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.ide.commander.CommanderPanel; |
| import com.intellij.ide.commander.ProjectListBuilder; |
| import com.intellij.ide.structureView.StructureViewBuilder; |
| import com.intellij.ide.structureView.StructureViewModel; |
| import com.intellij.ide.structureView.StructureViewTreeElement; |
| import com.intellij.ide.structureView.TreeBasedStructureViewBuilder; |
| import com.intellij.ide.structureView.newStructureView.TreeModelWrapper; |
| import com.intellij.ide.util.treeView.AbstractTreeNode; |
| import com.intellij.ide.util.treeView.smartTree.*; |
| import com.intellij.lang.LanguageStructureViewBuilder; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; |
| import com.intellij.openapi.keymap.KeymapUtil; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.util.PsiUtilBase; |
| import com.intellij.ui.*; |
| import com.intellij.ui.docking.DockManager; |
| import com.intellij.ui.speedSearch.SpeedSearchSupply; |
| import com.intellij.util.ArrayUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.Border; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class FileStructureDialog extends DialogWrapper { |
| private final Editor myEditor; |
| private final Navigatable myNavigatable; |
| private final Project myProject; |
| private MyCommanderPanel myCommanderPanel; |
| private final StructureViewModel myTreeModel; |
| private final StructureViewModel myBaseTreeModel; |
| private SmartTreeStructure myTreeStructure; |
| private final TreeStructureActionsOwner myTreeActionsOwner; |
| |
| @NonNls private static final String ourPropertyKey = "FileStructure.narrowDown"; |
| private boolean myShouldNarrowDown = false; |
| |
| public FileStructureDialog(StructureViewModel structureViewModel, |
| @Nullable Editor editor, |
| Project project, |
| Navigatable navigatable, |
| @NotNull final Disposable auxDisposable, |
| final boolean applySortAndFilter) { |
| super(project, true); |
| myProject = project; |
| myEditor = editor; |
| myNavigatable = navigatable; |
| myBaseTreeModel = structureViewModel; |
| if (applySortAndFilter) { |
| myTreeActionsOwner = new TreeStructureActionsOwner(myBaseTreeModel); |
| myTreeModel = new TreeModelWrapper(structureViewModel, myTreeActionsOwner); |
| } |
| else { |
| myTreeActionsOwner = null; |
| myTreeModel = structureViewModel; |
| } |
| |
| PsiFile psiFile = getPsiFile(project); |
| |
| final PsiElement psiElement = getCurrentElement(psiFile); |
| |
| //myDialog.setUndecorated(true); |
| init(); |
| |
| if (psiElement != null) { |
| if (structureViewModel.shouldEnterElement(psiElement)) { |
| myCommanderPanel.getBuilder().enterElement(psiElement, PsiUtilBase.getVirtualFile(psiElement)); |
| } |
| else { |
| myCommanderPanel.getBuilder().selectElement(psiElement, PsiUtilBase.getVirtualFile(psiElement)); |
| } |
| } |
| |
| Disposer.register(myDisposable, auxDisposable); |
| } |
| |
| protected PsiFile getPsiFile(final Project project) { |
| return PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument()); |
| } |
| |
| @Override |
| @Nullable |
| protected Border createContentPaneBorder() { |
| return null; |
| } |
| |
| @Override |
| public void dispose() { |
| myCommanderPanel.dispose(); |
| super.dispose(); |
| } |
| |
| @Override |
| protected String getDimensionServiceKey() { |
| return DockManager.getInstance(myProject).getDimensionKeyForFocus("#com.intellij.ide.util.FileStructureDialog"); |
| } |
| |
| @Override |
| public JComponent getPreferredFocusedComponent() { |
| return IdeFocusTraversalPolicy.getPreferredFocusedComponent(myCommanderPanel); |
| } |
| |
| @Nullable |
| protected PsiElement getCurrentElement(@Nullable final PsiFile psiFile) { |
| if (psiFile == null) return null; |
| |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| |
| Object elementAtCursor = myTreeModel.getCurrentEditorElement(); |
| if (elementAtCursor instanceof PsiElement) { |
| return (PsiElement)elementAtCursor; |
| } |
| |
| return null; |
| } |
| |
| @Override |
| protected JComponent createCenterPanel() { |
| myCommanderPanel = new MyCommanderPanel(myProject); |
| myTreeStructure = new MyStructureTreeStructure(); |
| |
| List<FileStructureFilter> fileStructureFilters = new ArrayList<FileStructureFilter>(); |
| List<FileStructureNodeProvider> fileStructureNodeProviders = new ArrayList<FileStructureNodeProvider>(); |
| if (myTreeActionsOwner != null) { |
| for(Filter filter: myBaseTreeModel.getFilters()) { |
| if (filter instanceof FileStructureFilter) { |
| final FileStructureFilter fsFilter = (FileStructureFilter)filter; |
| myTreeActionsOwner.setActionIncluded(fsFilter, true); |
| fileStructureFilters.add(fsFilter); |
| } |
| } |
| |
| if (myBaseTreeModel instanceof ProvidingTreeModel) { |
| for (NodeProvider provider : ((ProvidingTreeModel)myBaseTreeModel).getNodeProviders()) { |
| if (provider instanceof FileStructureNodeProvider) { |
| fileStructureNodeProviders.add((FileStructureNodeProvider)provider); |
| } |
| } |
| } |
| } |
| |
| PsiFile psiFile = getPsiFile(myProject); |
| boolean showRoot = isShowRoot(psiFile); |
| ProjectListBuilder projectListBuilder = new ProjectListBuilder(myProject, myCommanderPanel, myTreeStructure, null, showRoot) { |
| @Override |
| protected boolean shouldEnterSingleTopLevelElement(Object rootChild) { |
| return myBaseTreeModel.shouldEnterElement(((StructureViewTreeElement)((AbstractTreeNode)rootChild).getValue()).getValue()); |
| } |
| |
| @Override |
| protected boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element) { |
| return Comparing.equal(((StructureViewTreeElement)node.getValue()).getValue(), element); |
| } |
| |
| @Override |
| protected void refreshSelection() { |
| myCommanderPanel.scrollSelectionInView(); |
| if (myShouldNarrowDown) { |
| myCommanderPanel.updateSpeedSearch(); |
| } |
| } |
| |
| @Override |
| protected List<AbstractTreeNode> getAllAcceptableNodes(final Object[] childElements, VirtualFile file) { |
| ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>(); |
| for (Object childElement : childElements) { |
| result.add((AbstractTreeNode)childElement); |
| } |
| return result; |
| } |
| }; |
| myCommanderPanel.setBuilder(projectListBuilder); |
| myCommanderPanel.setTitlePanelVisible(false); |
| |
| new AnAction() { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final boolean succeeded = myCommanderPanel.navigateSelectedElement(); |
| if (succeeded) { |
| unregisterCustomShortcutSet(myCommanderPanel); |
| } |
| } |
| }.registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE).getShortcutSet(), myCommanderPanel); |
| |
| myCommanderPanel.setPreferredSize(new Dimension(400, 500)); |
| |
| JPanel panel = new JPanel(new BorderLayout()); |
| JPanel comboPanel = new JPanel(new GridLayout(0, 2, 0, 0)); |
| |
| addNarrowDownCheckbox(comboPanel); |
| |
| for(FileStructureFilter filter: fileStructureFilters) { |
| addCheckbox(comboPanel, filter); |
| } |
| |
| for (FileStructureNodeProvider provider : fileStructureNodeProviders) { |
| addCheckbox(comboPanel, provider); |
| } |
| |
| myCommanderPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); |
| panel.add(comboPanel, BorderLayout.NORTH); |
| panel.add(myCommanderPanel, BorderLayout.CENTER); |
| //new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); |
| |
| return panel; |
| } |
| |
| protected boolean isShowRoot(final PsiFile psiFile) { |
| StructureViewBuilder viewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(psiFile); |
| return viewBuilder instanceof TreeBasedStructureViewBuilder && ((TreeBasedStructureViewBuilder)viewBuilder).isRootNodeShown(); |
| } |
| |
| private void addNarrowDownCheckbox(final JPanel panel) { |
| final JCheckBox checkBox = new JCheckBox(IdeBundle.message("checkbox.narrow.down.the.list.on.typing")); |
| checkBox.setSelected(PropertiesComponent.getInstance().isTrueValue(ourPropertyKey)); |
| checkBox.addChangeListener(new ChangeListener() { |
| @Override |
| public void stateChanged(ChangeEvent e) { |
| myShouldNarrowDown = checkBox.isSelected(); |
| PropertiesComponent.getInstance().setValue(ourPropertyKey, Boolean.toString(myShouldNarrowDown)); |
| |
| ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder(); |
| if (builder == null) { |
| return; |
| } |
| builder.addUpdateRequest(); |
| } |
| }); |
| |
| checkBox.setFocusable(false); |
| panel.add(checkBox); |
| //,new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); |
| } |
| |
| private void addCheckbox(final JPanel panel, final TreeAction action) { |
| String text = action instanceof FileStructureFilter ? ((FileStructureFilter)action).getCheckBoxText() : |
| action instanceof FileStructureNodeProvider ? ((FileStructureNodeProvider)action).getCheckBoxText() : null; |
| |
| if (text == null) return; |
| |
| Shortcut[] shortcuts = action instanceof FileStructureFilter ? |
| ((FileStructureFilter)action).getShortcut() : ((FileStructureNodeProvider)action).getShortcut(); |
| |
| |
| final JCheckBox chkFilter = new JCheckBox(); |
| chkFilter.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(final ActionEvent e) { |
| ProjectListBuilder builder = (ProjectListBuilder)myCommanderPanel.getBuilder(); |
| PsiElement currentParent = null; |
| if (builder != null) { |
| final AbstractTreeNode parentNode = builder.getParentNode(); |
| final Object value = parentNode.getValue(); |
| if (value instanceof StructureViewTreeElement) { |
| final Object elementValue = ((StructureViewTreeElement)value).getValue(); |
| if (elementValue instanceof PsiElement) { |
| currentParent = (PsiElement) elementValue; |
| } |
| } |
| } |
| final boolean state = chkFilter.isSelected(); |
| myTreeActionsOwner.setActionIncluded(action, action instanceof FileStructureFilter ? !state : state); |
| myTreeStructure.rebuildTree(); |
| if (builder != null) { |
| if (currentParent != null) { |
| boolean oldNarrowDown = myShouldNarrowDown; |
| myShouldNarrowDown = false; |
| try { |
| builder.enterElement(currentParent, PsiUtilBase.getVirtualFile(currentParent)); |
| } |
| finally { |
| myShouldNarrowDown = oldNarrowDown; |
| } |
| } |
| builder.updateList(true); |
| } |
| |
| if (SpeedSearchBase.hasActiveSpeedSearch(myCommanderPanel.getList())) { |
| final SpeedSearchSupply supply = SpeedSearchBase.getSupply(myCommanderPanel.getList()); |
| if (supply != null && supply.isPopupActive()) supply.refreshSelection(); |
| } |
| } |
| }); |
| chkFilter.setFocusable(false); |
| |
| if (shortcuts.length > 0) { |
| text += " (" + KeymapUtil.getShortcutText(shortcuts [0]) + ")"; |
| new AnAction() { |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| chkFilter.doClick(); |
| } |
| }.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), myCommanderPanel); |
| } |
| chkFilter.setText(text); |
| panel.add(chkFilter); |
| //,new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); |
| } |
| |
| @Override |
| @Nullable |
| protected JComponent createSouthPanel() { |
| return null; |
| } |
| |
| public CommanderPanel getPanel() { |
| return myCommanderPanel; |
| } |
| |
| private class MyCommanderPanel extends CommanderPanel implements DataProvider { |
| @Override |
| protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) { |
| return false; |
| } |
| |
| public MyCommanderPanel(Project _project) { |
| super(_project, false, true); |
| myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
| myListSpeedSearch.addChangeListener(new PropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| ProjectListBuilder builder = (ProjectListBuilder)getBuilder(); |
| if (builder == null) { |
| return; |
| } |
| builder.addUpdateRequest(hasPrefixShortened(evt)); |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| int index = myList.getSelectedIndex(); |
| if (index != -1 && index < myList.getModel().getSize()) { |
| myList.clearSelection(); |
| ListScrollingUtil.selectItem(myList, index); |
| } |
| else { |
| ListScrollingUtil.ensureSelectionExists(myList); |
| } |
| } |
| }); |
| |
| myList.repaint(); // to update match highlighting |
| } |
| }); |
| myListSpeedSearch.setComparator(createSpeedSearchComparator()); |
| } |
| |
| private boolean hasPrefixShortened(final PropertyChangeEvent evt) { |
| return evt.getNewValue() != null && evt.getOldValue() != null && |
| ((String)evt.getNewValue()).length() < ((String)evt.getOldValue()).length(); |
| } |
| |
| @Override |
| public boolean navigateSelectedElement() { |
| final Ref<Boolean> succeeded = new Ref<Boolean>(); |
| final CommandProcessor commandProcessor = CommandProcessor.getInstance(); |
| commandProcessor.executeCommand(myProject, new Runnable() { |
| @Override |
| public void run() { |
| succeeded.set(MyCommanderPanel.super.navigateSelectedElement()); |
| IdeDocumentHistory.getInstance(myProject).includeCurrentCommandAsNavigation(); |
| } |
| }, "Navigate", null); |
| if (succeeded.get()) { |
| close(CANCEL_EXIT_CODE); |
| } |
| return succeeded.get(); |
| } |
| |
| @Override |
| public Object getData(String dataId) { |
| Object selectedElement = myCommanderPanel.getSelectedValue(); |
| |
| if (selectedElement instanceof TreeElement) selectedElement = ((StructureViewTreeElement)selectedElement).getValue(); |
| |
| if (CommonDataKeys.NAVIGATABLE.is(dataId)) { |
| return selectedElement instanceof Navigatable ? selectedElement : myNavigatable; |
| } |
| |
| if (OpenFileDescriptor.NAVIGATE_IN_EDITOR.is(dataId)) return myEditor; |
| |
| return getDataImpl(dataId); |
| } |
| |
| public String getEnteredPrefix() { |
| return myListSpeedSearch.getEnteredPrefix(); |
| } |
| |
| public void updateSpeedSearch() { |
| myListSpeedSearch.refreshSelection(); |
| } |
| |
| public void scrollSelectionInView() { |
| int selectedIndex = myList.getSelectedIndex(); |
| if (selectedIndex >= 0) { |
| ListScrollingUtil.ensureIndexIsVisible(myList, selectedIndex, 0); |
| } |
| } |
| } |
| |
| private class MyStructureTreeStructure extends SmartTreeStructure { |
| public MyStructureTreeStructure() { |
| super(FileStructureDialog.this.myProject, myTreeModel); |
| } |
| |
| @Override |
| public Object[] getChildElements(Object element) { |
| Object[] childElements = super.getChildElements(element); |
| |
| if (!myShouldNarrowDown) { |
| return childElements; |
| } |
| |
| String enteredPrefix = myCommanderPanel.getEnteredPrefix(); |
| if (enteredPrefix == null) { |
| return childElements; |
| } |
| |
| ArrayList<Object> filteredElements = new ArrayList<Object>(childElements.length); |
| SpeedSearchComparator speedSearchComparator = createSpeedSearchComparator(); |
| |
| for (Object child : childElements) { |
| if (child instanceof AbstractTreeNode) { |
| Object value = ((AbstractTreeNode)child).getValue(); |
| if (value instanceof TreeElement) { |
| String name = ((TreeElement)value).getPresentation().getPresentableText(); |
| if (name == null) { |
| continue; |
| } |
| if (speedSearchComparator.matchingFragments(enteredPrefix, name) == null) { |
| continue; |
| } |
| } |
| } |
| filteredElements.add(child); |
| } |
| return ArrayUtil.toObjectArray(filteredElements); |
| } |
| |
| @Override |
| public void rebuildTree() { |
| getChildElements(getRootElement()); // for some reason necessary to rebuild tree correctly |
| super.rebuildTree(); |
| } |
| } |
| |
| private static SpeedSearchComparator createSpeedSearchComparator() { |
| return new SpeedSearchComparator(false); |
| } |
| } |