blob: b13daeb3108657967eab70ffe2858aba7964ae07 [file] [log] [blame]
/*
* Copyright 2000-2010 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.projectView.BaseProjectTreeBuilder;
import com.intellij.ide.projectView.impl.AbstractProjectTreeStructure;
import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase;
import com.intellij.ide.projectView.impl.ProjectTreeBuilder;
import com.intellij.ide.util.gotoByName.*;
import com.intellij.ide.util.treeView.AlphaComparator;
import com.intellij.ide.util.treeView.NodeRenderer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.presentation.java.SymbolPresentationUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.TabbedPaneWrapper;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.FindSymbolParameters;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
public abstract class AbstractTreeClassChooserDialog<T extends PsiNamedElement> extends DialogWrapper implements TreeChooser<T> {
private Tree myTree;
private T mySelectedClass = null;
@NotNull private final Project myProject;
private BaseProjectTreeBuilder myBuilder;
private TabbedPaneWrapper myTabbedPane;
private ChooseByNamePanel myGotoByNamePanel;
private final GlobalSearchScope myScope;
@NotNull private final Filter<T> myClassFilter;
private final Class<T> myElementClass;
@Nullable private final T myBaseClass;
private T myInitialClass;
private final boolean myIsShowMembers;
private final boolean myIsShowLibraryContents;
public AbstractTreeClassChooserDialog(String title, Project project, final Class<T> elementClass) {
this(title, project, elementClass, null);
}
public AbstractTreeClassChooserDialog(String title, Project project, final Class<T> elementClass, @Nullable T initialClass) {
this(title, project, GlobalSearchScope.projectScope(project), elementClass, null, initialClass);
}
public AbstractTreeClassChooserDialog(String title,
@NotNull Project project,
GlobalSearchScope scope,
@NotNull Class<T> elementClass,
@Nullable Filter<T> classFilter,
@Nullable T initialClass) {
this(title, project, scope, elementClass, classFilter, null, initialClass, false, true);
}
public AbstractTreeClassChooserDialog(String title,
@NotNull Project project,
GlobalSearchScope scope,
@NotNull Class<T> elementClass,
@Nullable Filter<T> classFilter,
@Nullable T baseClass,
@Nullable T initialClass,
boolean isShowMembers,
boolean isShowLibraryContents) {
super(project, true);
myScope = scope;
myElementClass = elementClass;
myClassFilter = classFilter == null ? allFilter() : classFilter;
myBaseClass = baseClass;
myInitialClass = initialClass;
myIsShowMembers = isShowMembers;
myIsShowLibraryContents = isShowLibraryContents;
setTitle(title);
myProject = project;
init();
if (initialClass != null) {
select(initialClass);
}
handleSelectionChanged();
}
private Filter<T> allFilter() {
return new Filter<T>() {
@Override
public boolean isAccepted(T element) {
return true;
}
};
}
@Override
protected JComponent createCenterPanel() {
final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode());
myTree = new Tree(model);
ProjectAbstractTreeStructureBase treeStructure = new AbstractProjectTreeStructure(myProject) {
@Override
public boolean isFlattenPackages() {
return false;
}
@Override
public boolean isShowMembers() {
return myIsShowMembers;
}
@Override
public boolean isHideEmptyMiddlePackages() {
return true;
}
@Override
public boolean isAbbreviatePackageNames() {
return false;
}
@Override
public boolean isShowLibraryContents() {
return myIsShowLibraryContents;
}
@Override
public boolean isShowModules() {
return false;
}
};
myBuilder = new ProjectTreeBuilder(myProject, myTree, model, AlphaComparator.INSTANCE, treeStructure);
myTree.setRootVisible(false);
myTree.setShowsRootHandles(true);
myTree.expandRow(0);
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
myTree.setCellRenderer(new NodeRenderer());
UIUtil.setLineStyleAngled(myTree);
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
scrollPane.setPreferredSize(new Dimension(500, 300));
myTree.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (KeyEvent.VK_ENTER == e.getKeyCode()) {
doOKAction();
}
}
});
new DoubleClickListener() {
@Override
protected boolean onDoubleClick(MouseEvent event) {
TreePath path = myTree.getPathForLocation(event.getX(), event.getY());
if (path != null && myTree.isPathSelected(path)) {
doOKAction();
return true;
}
return false;
}
}.installOn(myTree);
myTree.addTreeSelectionListener(
new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
handleSelectionChanged();
}
}
);
new TreeSpeedSearch(myTree);
myTabbedPane = new TabbedPaneWrapper(getDisposable());
final JPanel dummyPanel = new JPanel(new BorderLayout());
String name = null;
/*
if (myInitialClass != null) {
name = myInitialClass.getName();
}
*/
myGotoByNamePanel = new ChooseByNamePanel(myProject, createChooseByNameModel(), name, myScope.isSearchInLibraries(), getContext()) {
@Override
protected void showTextFieldPanel() {
}
@Override
protected void close(boolean isOk) {
super.close(isOk);
if (isOk) {
doOKAction();
}
else {
doCancelAction();
}
}
@NotNull
@Override
protected Set<Object> filter(@NotNull Set<Object> elements) {
return doFilter(elements);
}
@Override
protected void initUI(ChooseByNamePopupComponent.Callback callback, ModalityState modalityState, boolean allowMultipleSelection) {
super.initUI(callback, modalityState, allowMultipleSelection);
dummyPanel.add(myGotoByNamePanel.getPanel(), BorderLayout.CENTER);
IdeFocusTraversalPolicy.getPreferredFocusedComponent(myGotoByNamePanel.getPanel()).requestFocus();
}
@Override
protected void showList() {
super.showList();
if (myInitialClass != null && myList.getModel().getSize() > 0) {
myList.setSelectedValue(myInitialClass, true);
myInitialClass = null;
}
}
@Override
protected void chosenElementMightChange() {
handleSelectionChanged();
}
};
Disposer.register(myDisposable, myGotoByNamePanel);
myTabbedPane.addTab(IdeBundle.message("tab.chooser.search.by.name"), dummyPanel);
myTabbedPane.addTab(IdeBundle.message("tab.chooser.project"), scrollPane);
myGotoByNamePanel.invoke(new MyCallback(), getModalityState(), false);
myTabbedPane.addChangeListener(
new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
handleSelectionChanged();
}
}
);
return myTabbedPane.getComponent();
}
private Set<Object> doFilter(Set<Object> elements) {
Set<Object> result = new LinkedHashSet<Object>();
for (Object o : elements) {
if (myElementClass.isInstance(o) && getFilter().isAccepted((T)o)) {
result.add(o);
}
}
return result;
}
protected ChooseByNameModel createChooseByNameModel() {
if (myBaseClass == null) {
return new MyGotoClassModel<T>(myProject, this);
}
else {
BaseClassInheritorsProvider<T> inheritorsProvider = getInheritorsProvider(myBaseClass);
if (inheritorsProvider != null) {
return new SubclassGotoClassModel<T>(myProject, this, inheritorsProvider);
}
else {
throw new IllegalStateException("inheritors provider is null");
}
}
}
/**
* Makes sense only in case of not null base class.
*
* @param baseClass
* @return
*/
@Nullable
protected BaseClassInheritorsProvider<T> getInheritorsProvider(@NotNull T baseClass) {
return null;
}
private void handleSelectionChanged() {
T selection = calcSelectedClass();
setOKActionEnabled(selection != null);
}
@Override
protected void doOKAction() {
mySelectedClass = calcSelectedClass();
if (mySelectedClass == null) return;
if (!myClassFilter.isAccepted(mySelectedClass)) {
Messages.showErrorDialog(myTabbedPane.getComponent(),
SymbolPresentationUtil.getSymbolPresentableText(mySelectedClass) + " is not acceptable");
return;
}
super.doOKAction();
}
@Override
public T getSelected() {
return mySelectedClass;
}
@Override
public void select(@NotNull final T aClass) {
selectElementInTree(aClass);
}
@Override
public void selectDirectory(@NotNull final PsiDirectory directory) {
selectElementInTree(directory);
}
@Override
public void showDialog() {
show();
}
@Override
public void showPopup() {
//todo leak via not shown dialog?
ChooseByNamePopup popup = ChooseByNamePopup.createPopup(myProject, createChooseByNameModel(), getContext());
popup.invoke(new ChooseByNamePopupComponent.Callback() {
@Override
public void elementChosen(Object element) {
mySelectedClass = (T)element;
((Navigatable)element).navigate(true);
}
}, getModalityState(), true);
}
private T getContext() {
return myBaseClass != null ? myBaseClass : myInitialClass != null ? myInitialClass : null;
}
private void selectElementInTree(@NotNull final PsiElement element) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (myBuilder == null) return;
final VirtualFile vFile = PsiUtilBase.getVirtualFile(element);
myBuilder.select(element, vFile, false);
}
}, getModalityState());
}
private ModalityState getModalityState() {
return ModalityState.stateForComponent(getRootPane());
}
@Nullable
protected T calcSelectedClass() {
if (getTabbedPane().getSelectedIndex() == 0) {
return (T)getGotoByNamePanel().getChosenElement();
}
else {
TreePath path = getTree().getSelectionPath();
if (path == null) return null;
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
return getSelectedFromTreeUserObject(node);
}
}
protected abstract T getSelectedFromTreeUserObject(DefaultMutableTreeNode node);
@Override
public void dispose() {
if (myBuilder != null) {
Disposer.dispose(myBuilder);
myBuilder = null;
}
super.dispose();
}
@Override
protected String getDimensionServiceKey() {
return "#com.intellij.ide.util.TreeClassChooserDialog";
}
@Override
public JComponent getPreferredFocusedComponent() {
return myGotoByNamePanel.getPreferredFocusedComponent();
}
@NotNull
protected Project getProject() {
return myProject;
}
GlobalSearchScope getScope() {
return myScope;
}
@NotNull
protected Filter<T> getFilter() {
return myClassFilter;
}
T getBaseClass() {
return myBaseClass;
}
T getInitialClass() {
return myInitialClass;
}
protected TabbedPaneWrapper getTabbedPane() {
return myTabbedPane;
}
protected Tree getTree() {
return myTree;
}
protected ChooseByNamePanel getGotoByNamePanel() {
return myGotoByNamePanel;
}
protected static class MyGotoClassModel<T extends PsiNamedElement> extends GotoClassModel2 {
private final AbstractTreeClassChooserDialog<T> myTreeClassChooserDialog;
AbstractTreeClassChooserDialog<T> getTreeClassChooserDialog() {
return myTreeClassChooserDialog;
}
public MyGotoClassModel(@NotNull Project project,
AbstractTreeClassChooserDialog<T> treeClassChooserDialog) {
super(project);
myTreeClassChooserDialog = treeClassChooserDialog;
}
@NotNull
@Override
public Object[] getElementsByName(String name, FindSymbolParameters parameters, @NotNull ProgressIndicator canceled) {
String patternName = parameters.getLocalPatternName();
List<T> classes = myTreeClassChooserDialog.getClassesByName(
name, parameters.isSearchInLibraries(), patternName, myTreeClassChooserDialog.getScope()
);
if (classes.size() == 0) return ArrayUtil.EMPTY_OBJECT_ARRAY;
if (classes.size() == 1) {
return isAccepted(classes.get(0)) ? ArrayUtil.toObjectArray(classes) : ArrayUtil.EMPTY_OBJECT_ARRAY;
}
Set<String> qNames = ContainerUtil.newHashSet();
List<T> list = new ArrayList<T>(classes.size());
for (T aClass : classes) {
if (qNames.add(getFullName(aClass)) && isAccepted(aClass)) {
list.add(aClass);
}
}
return ArrayUtil.toObjectArray(list);
}
@Override
@Nullable
public String getPromptText() {
return null;
}
protected boolean isAccepted(T aClass) {
return myTreeClassChooserDialog.getFilter().isAccepted(aClass);
}
}
@NotNull
protected abstract List<T> getClassesByName(final String name,
final boolean checkBoxState,
final String pattern,
final GlobalSearchScope searchScope);
public abstract static class BaseClassInheritorsProvider<T> {
private final T myBaseClass;
private final GlobalSearchScope myScope;
public T getBaseClass() {
return myBaseClass;
}
public GlobalSearchScope getScope() {
return myScope;
}
public BaseClassInheritorsProvider(T baseClass, GlobalSearchScope scope) {
myBaseClass = baseClass;
myScope = scope;
}
@NotNull
protected abstract Query<T> searchForInheritors(T baseClass, GlobalSearchScope searchScope, boolean checkDeep);
protected abstract boolean isInheritor(T clazz, T baseClass, boolean checkDeep);
protected abstract String[] getNames();
protected Query<T> searchForInheritorsOfBaseClass() {
return searchForInheritors(myBaseClass, myScope, true);
}
protected boolean isInheritorOfBaseClass(T aClass) {
return isInheritor(aClass, myBaseClass, true);
}
}
private static class SubclassGotoClassModel<T extends PsiNamedElement> extends MyGotoClassModel<T> {
private final BaseClassInheritorsProvider<T> myInheritorsProvider;
private boolean myFastMode = true;
public SubclassGotoClassModel(@NotNull final Project project,
@NotNull final AbstractTreeClassChooserDialog<T> treeClassChooserDialog,
@NotNull BaseClassInheritorsProvider<T> inheritorsProvider) {
super(project, treeClassChooserDialog);
myInheritorsProvider = inheritorsProvider;
assert myInheritorsProvider.getBaseClass() != null;
}
@NotNull
@Override
public String[] getNames(boolean checkBoxState) {
if (!myFastMode) {
return myInheritorsProvider.getNames();
}
final List<String> names = new ArrayList<String>();
myFastMode = myInheritorsProvider.searchForInheritorsOfBaseClass().forEach(new Processor<T>() {
private long start = System.currentTimeMillis();
@Override
public boolean process(T aClass) {
if (System.currentTimeMillis() - start > 500) {
return false;
}
if ((getTreeClassChooserDialog().getFilter().isAccepted(aClass)) && aClass.getName() != null) {
names.add(aClass.getName());
}
return true;
}
});
if (!myFastMode) {
return myInheritorsProvider.getNames();
}
if ((getTreeClassChooserDialog().getFilter().isAccepted(myInheritorsProvider.getBaseClass())) &&
myInheritorsProvider.getBaseClass().getName() != null) {
names.add(myInheritorsProvider.getBaseClass().getName());
}
return ArrayUtil.toStringArray(names);
}
@Override
protected boolean isAccepted(T aClass) {
if (myFastMode) {
return getTreeClassChooserDialog().getFilter().isAccepted(aClass);
}
else {
return (aClass == getTreeClassChooserDialog().getBaseClass() ||
myInheritorsProvider.isInheritorOfBaseClass(aClass)) &&
getTreeClassChooserDialog().getFilter().isAccepted(
aClass);
}
}
}
private class MyCallback extends ChooseByNamePopupComponent.Callback {
@Override
public void elementChosen(Object element) {
mySelectedClass = (T)element;
close(OK_EXIT_CODE);
}
}
}