blob: 2c6196b609026599d90553c3da38f111ce636b73 [file] [log] [blame]
/*
* 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);
}
}