com.intellij.ide.util.AbstractTreeClassChooserDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.util.AbstractTreeClassChooserDialog.java

Source

/*
 * 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.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

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;
    }

    protected 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 int count;

                @Override
                public boolean process(T aClass) {
                    if (count++ > 1000) {
                        return false;
                    }
                    if ((getTreeClassChooserDialog().getFilter().isAccepted(aClass)) && aClass.getName() != null) {
                        names.add(aClass.getName());
                    }
                    return true;
                }
            });
            if (!myFastMode) {
                return getNames(checkBoxState);
            }
            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);
        }
    }
}