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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2000-2014 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.codeInsight.generation.*;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.ui.*;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.HashMap;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;

public class MemberChooser<T extends ClassMember> extends DialogWrapper implements TypeSafeDataProvider {
    protected Tree myTree;
    private DefaultTreeModel myTreeModel;
    protected JComponent[] myOptionControls;
    private JCheckBox myCopyJavadocCheckbox;
    private JCheckBox myInsertOverrideAnnotationCheckbox;
    private final ArrayList<MemberNode> mySelectedNodes = new ArrayList<MemberNode>();

    private final SortEmAction mySortAction;

    private boolean myAlphabeticallySorted = false;
    private boolean myShowClasses = true;
    protected boolean myAllowEmptySelection = false;
    private final boolean myAllowMultiSelection;
    private final Project myProject;
    private final boolean myIsInsertOverrideVisible;
    private final JComponent myHeaderPanel;

    protected T[] myElements;
    protected Comparator<ElementNode> myComparator = new OrderComparator();

    protected final HashMap<MemberNode, ParentNode> myNodeToParentMap = new HashMap<MemberNode, ParentNode>();
    protected final HashMap<ClassMember, MemberNode> myElementToNodeMap = new HashMap<ClassMember, MemberNode>();
    protected final ArrayList<ContainerNode> myContainerNodes = new ArrayList<ContainerNode>();

    protected LinkedHashSet<T> mySelectedElements;

    @NonNls
    private static final String PROP_SORTED = "MemberChooser.sorted";
    @NonNls
    private static final String PROP_SHOWCLASSES = "MemberChooser.showClasses";
    @NonNls
    private static final String PROP_COPYJAVADOC = "MemberChooser.copyJavadoc";

    public MemberChooser(T[] elements, boolean allowEmptySelection, boolean allowMultiSelection,
            @NotNull Project project, @Nullable JComponent headerPanel, JComponent[] optionControls) {
        this(allowEmptySelection, allowMultiSelection, project, false, headerPanel, optionControls);
        resetElements(elements);
        init();
    }

    public MemberChooser(T[] elements, boolean allowEmptySelection, boolean allowMultiSelection,
            @NotNull Project project) {
        this(elements, allowEmptySelection, allowMultiSelection, project, false);
    }

    public MemberChooser(T[] elements, boolean allowEmptySelection, boolean allowMultiSelection,
            @NotNull Project project, boolean isInsertOverrideVisible) {
        this(elements, allowEmptySelection, allowMultiSelection, project, isInsertOverrideVisible, null);
    }

    public MemberChooser(T[] elements, boolean allowEmptySelection, boolean allowMultiSelection,
            @NotNull Project project, boolean isInsertOverrideVisible, @Nullable JComponent headerPanel) {
        this(allowEmptySelection, allowMultiSelection, project, isInsertOverrideVisible, headerPanel, null);
        resetElements(elements);
        init();
    }

    protected MemberChooser(boolean allowEmptySelection, boolean allowMultiSelection, @NotNull Project project,
            boolean isInsertOverrideVisible, @Nullable JComponent headerPanel,
            @Nullable JComponent[] optionControls) {
        super(project, true);
        myAllowEmptySelection = allowEmptySelection;
        myAllowMultiSelection = allowMultiSelection;
        myProject = project;
        myIsInsertOverrideVisible = isInsertOverrideVisible;
        myHeaderPanel = headerPanel;
        myTree = createTree();
        myOptionControls = optionControls;
        mySortAction = new SortEmAction();
        mySortAction.registerCustomShortcutSet(
                new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.ALT_MASK)), myTree);
    }

    protected void resetElementsWithDefaultComparator(T[] elements, final boolean restoreSelectedElements) {
        myComparator = myAlphabeticallySorted ? new AlphaComparator() : new OrderComparator();
        resetElements(elements, null, restoreSelectedElements);
    }

    public void resetElements(T[] elements) {
        resetElements(elements, null, false);
    }

    @SuppressWarnings("unchecked")
    public void resetElements(T[] elements, final @Nullable Comparator<T> sortComparator,
            final boolean restoreSelectedElements) {
        final List<T> selectedElements = restoreSelectedElements && mySelectedElements != null
                ? new ArrayList<T>(mySelectedElements)
                : null;
        myElements = elements;
        if (sortComparator != null) {
            myComparator = new ElementNodeComparatorWrapper(sortComparator);
        }
        mySelectedNodes.clear();
        myNodeToParentMap.clear();
        myElementToNodeMap.clear();
        myContainerNodes.clear();

        ApplicationManager.getApplication().runReadAction(new Runnable() {
            @Override
            public void run() {
                myTreeModel = buildModel();
            }
        });

        myTree.setModel(myTreeModel);
        myTree.setRootVisible(false);

        doSort();

        defaultExpandTree();

        if (myOptionControls == null) {
            myCopyJavadocCheckbox = new NonFocusableCheckBox(IdeBundle.message("checkbox.copy.javadoc"));
            if (myIsInsertOverrideVisible) {
                myInsertOverrideAnnotationCheckbox = new NonFocusableCheckBox(
                        IdeBundle.message("checkbox.insert.at.override"));
                myOptionControls = new JCheckBox[] { myCopyJavadocCheckbox, myInsertOverrideAnnotationCheckbox };
            } else {
                myOptionControls = new JCheckBox[] { myCopyJavadocCheckbox };
            }
        }

        myTree.doLayout();
        setOKActionEnabled(myAllowEmptySelection || myElements != null && myElements.length > 0);

        if (selectedElements != null) {
            selectElements(selectedElements.toArray(new ClassMember[selectedElements.size()]));
        }
        if (mySelectedElements == null || mySelectedElements.isEmpty()) {
            expandFirst();
        }
    }

    /**
     * should be invoked in read action
     */
    private DefaultTreeModel buildModel() {
        final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
        final Ref<Integer> count = new Ref<Integer>(0);
        final FactoryMap<MemberChooserObject, ParentNode> map = new FactoryMap<MemberChooserObject, ParentNode>() {
            @Override
            protected ParentNode create(final MemberChooserObject key) {
                ParentNode node = null;
                DefaultMutableTreeNode parentNode = rootNode;

                if (supportsNestedContainers() && key instanceof ClassMember) {
                    MemberChooserObject parentNodeDelegate = ((ClassMember) key).getParentNodeDelegate();

                    if (parentNodeDelegate != null) {
                        parentNode = get(parentNodeDelegate);
                    }
                }
                if (isContainerNode(key)) {
                    final ContainerNode containerNode = new ContainerNode(parentNode, key, count);
                    node = containerNode;
                    myContainerNodes.add(containerNode);
                }
                if (node == null) {
                    node = new ParentNode(parentNode, key, count);
                }
                return node;
            }
        };

        for (T object : myElements) {
            final ParentNode parentNode = map.get(object.getParentNodeDelegate());
            final MemberNode elementNode = createMemberNode(count, object, parentNode);
            myNodeToParentMap.put(elementNode, parentNode);
            myElementToNodeMap.put(object, elementNode);
        }
        return new DefaultTreeModel(rootNode);
    }

    protected MemberNode createMemberNode(Ref<Integer> count, T object, ParentNode parentNode) {
        return new MemberNodeImpl(parentNode, object, count);
    }

    protected boolean supportsNestedContainers() {
        return false;
    }

    protected void defaultExpandTree() {
        TreeUtil.expandAll(myTree);
    }

    protected boolean isContainerNode(MemberChooserObject key) {
        return key instanceof PsiElementMemberChooserObject;
    }

    public void selectElements(ClassMember[] elements) {
        ArrayList<TreePath> selectionPaths = new ArrayList<TreePath>();
        for (ClassMember element : elements) {
            MemberNode treeNode = myElementToNodeMap.get(element);
            if (treeNode != null) {
                selectionPaths.add(new TreePath(((DefaultMutableTreeNode) treeNode).getPath()));
            }
        }
        myTree.setSelectionPaths(selectionPaths.toArray(new TreePath[selectionPaths.size()]));
    }

    @Override
    @NotNull
    protected Action[] createActions() {
        final List<Action> actions = new ArrayList<Action>();
        actions.add(getOKAction());
        if (myAllowEmptySelection) {
            actions.add(new SelectNoneAction());
        }
        actions.add(getCancelAction());
        if (getHelpId() != null) {
            actions.add(getHelpAction());
        }
        return actions.toArray(new Action[actions.size()]);
    }

    @Override
    protected void doHelpAction() {
        if (getHelpId() == null)
            return;
        super.doHelpAction();
    }

    protected void customizeOptionsPanel() {
        if (myInsertOverrideAnnotationCheckbox != null && myIsInsertOverrideVisible) {
            CodeStyleSettings styleSettings = CodeStyleSettingsManager.getInstance(myProject).getCurrentSettings();
            myInsertOverrideAnnotationCheckbox.setSelected(styleSettings.INSERT_OVERRIDE_ANNOTATION);
        }
        if (myCopyJavadocCheckbox != null) {
            myCopyJavadocCheckbox.setSelected(PropertiesComponent.getInstance().isTrueValue(PROP_COPYJAVADOC));
        }
    }

    @Override
    protected JComponent createSouthPanel() {
        JPanel panel = new JPanel(new GridBagLayout());

        customizeOptionsPanel();
        JPanel optionsPanel = new JPanel(new VerticalFlowLayout());
        for (final JComponent component : myOptionControls) {
            optionsPanel.add(component);
        }

        panel.add(optionsPanel, new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST,
                GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0));

        if (!myAllowEmptySelection && (myElements == null || myElements.length == 0)) {
            setOKActionEnabled(false);
        }
        panel.add(super.createSouthPanel(), new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.SOUTH,
                GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
        return panel;
    }

    @Override
    protected JComponent createNorthPanel() {
        return myHeaderPanel;
    }

    @Override
    protected JComponent createCenterPanel() {
        JPanel panel = new JPanel(new BorderLayout());

        // Toolbar

        DefaultActionGroup group = new DefaultActionGroup();

        fillToolbarActions(group);

        group.addSeparator();

        ExpandAllAction expandAllAction = new ExpandAllAction();
        expandAllAction.registerCustomShortcutSet(
                new CustomShortcutSet(
                        KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_EXPAND_ALL)),
                myTree);
        group.add(expandAllAction);

        CollapseAllAction collapseAllAction = new CollapseAllAction();
        collapseAllAction.registerCustomShortcutSet(
                new CustomShortcutSet(
                        KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_COLLAPSE_ALL)),
                myTree);
        group.add(collapseAllAction);

        panel.add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(),
                BorderLayout.NORTH);

        // Tree
        expandFirst();
        defaultExpandTree();
        installSpeedSearch();

        JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
        scrollPane.setPreferredSize(new Dimension(350, 450));
        panel.add(scrollPane, BorderLayout.CENTER);

        return panel;
    }

    private void expandFirst() {
        if (getRootNode().getChildCount() > 0) {
            myTree.expandRow(0);
            myTree.setSelectionRow(1);
        }
    }

    protected Tree createTree() {
        final Tree tree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode()));

        tree.setCellRenderer(getTreeCellRenderer());
        UIUtil.setLineStyleAngled(tree);
        tree.setRootVisible(false);
        tree.setShowsRootHandles(true);
        tree.addKeyListener(new TreeKeyListener());
        tree.addTreeSelectionListener(new MyTreeSelectionListener());

        if (!myAllowMultiSelection) {
            tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        }

        new DoubleClickListener() {
            @Override
            protected boolean onDoubleClick(MouseEvent e) {
                if (tree.getPathForLocation(e.getX(), e.getY()) != null) {
                    doOKAction();
                    return true;
                }
                return false;
            }
        }.installOn(tree);

        TreeUtil.installActions(tree);
        return tree;
    }

    protected TreeCellRenderer getTreeCellRenderer() {
        return new ColoredTreeCellRenderer() {
            @Override
            public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded,
                    boolean leaf, int row, boolean hasFocus) {
                if (value instanceof ElementNode) {
                    ((ElementNode) value).getDelegate().renderTreeNode(this, tree);
                }
            }
        };
    }

    @NotNull
    protected String convertElementText(@NotNull String originalElementText) {
        String res = originalElementText;

        int i = res.indexOf(':');
        if (i >= 0) {
            res = res.substring(0, i);
        }
        i = res.indexOf('(');
        if (i >= 0) {
            res = res.substring(0, i);
        }

        return res;
    }

    protected void installSpeedSearch() {
        final TreeSpeedSearch treeSpeedSearch = new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
            @Override
            @Nullable
            public String convert(TreePath path) {
                final ElementNode lastPathComponent = (ElementNode) path.getLastPathComponent();
                if (lastPathComponent == null)
                    return null;
                String text = lastPathComponent.getDelegate().getText();
                if (text != null) {
                    text = convertElementText(text);
                }
                return text;
            }
        });
        treeSpeedSearch.setComparator(getSpeedSearchComparator());
    }

    protected SpeedSearchComparator getSpeedSearchComparator() {
        return new SpeedSearchComparator(false);
    }

    protected void disableAlphabeticalSorting(final AnActionEvent event) {
        mySortAction.setSelected(event, false);
    }

    protected void onAlphabeticalSortingEnabled(final AnActionEvent event) {
        //do nothing by default
    }

    protected void fillToolbarActions(DefaultActionGroup group) {
        final boolean alphabeticallySorted = PropertiesComponent.getInstance().isTrueValue(PROP_SORTED);
        if (alphabeticallySorted) {
            setSortComparator(new AlphaComparator());
        }
        myAlphabeticallySorted = alphabeticallySorted;
        group.add(mySortAction);

        if (!supportsNestedContainers()) {
            ShowContainersAction showContainersAction = getShowContainersAction();
            showContainersAction.registerCustomShortcutSet(
                    new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.ALT_MASK)), myTree);
            setShowClasses(PropertiesComponent.getInstance().getBoolean(PROP_SHOWCLASSES, true));
            group.add(showContainersAction);
        }
    }

    @Override
    protected String getDimensionServiceKey() {
        return "#com.intellij.ide.util.MemberChooser";
    }

    @Override
    public JComponent getPreferredFocusedComponent() {
        return myTree;
    }

    public JComponent[] getOptionControls() {
        return myOptionControls;
    }

    @Nullable
    private LinkedHashSet<T> getSelectedElementsList() {
        return getExitCode() == OK_EXIT_CODE ? mySelectedElements : null;
    }

    @Nullable
    public List<T> getSelectedElements() {
        final LinkedHashSet<T> list = getSelectedElementsList();
        return list == null ? null : new ArrayList<T>(list);
    }

    @Nullable
    public T[] getSelectedElements(T[] a) {
        LinkedHashSet<T> list = getSelectedElementsList();
        if (list == null)
            return null;
        return list.toArray(a);
    }

    protected final boolean areElementsSelected() {
        return mySelectedElements != null && !mySelectedElements.isEmpty();
    }

    public void setCopyJavadocVisible(boolean state) {
        myCopyJavadocCheckbox.setVisible(state);
    }

    public boolean isCopyJavadoc() {
        return myCopyJavadocCheckbox.isSelected();
    }

    public boolean isInsertOverrideAnnotation() {
        return myIsInsertOverrideVisible && myInsertOverrideAnnotationCheckbox.isSelected();
    }

    private boolean isAlphabeticallySorted() {
        return myAlphabeticallySorted;
    }

    @SuppressWarnings("unchecked")
    protected void changeSortComparator(final Comparator<T> comparator) {
        setSortComparator(new ElementNodeComparatorWrapper(comparator));
    }

    private void setSortComparator(Comparator<ElementNode> sortComparator) {
        if (myComparator.equals(sortComparator))
            return;
        myComparator = sortComparator;
        doSort();
    }

    protected void doSort() {
        Pair<ElementNode, List<ElementNode>> pair = storeSelection();

        Enumeration<TreeNode> children = getRootNodeChildren();
        while (children.hasMoreElements()) {
            ParentNode classNode = (ParentNode) children.nextElement();
            sortNode(classNode, myComparator);
            myTreeModel.nodeStructureChanged(classNode);
        }

        restoreSelection(pair);
    }

    private static void sortNode(ParentNode node, final Comparator<ElementNode> sortComparator) {
        ArrayList<ElementNode> arrayList = new ArrayList<ElementNode>();
        Enumeration<TreeNode> children = node.children();
        while (children.hasMoreElements()) {
            arrayList.add((ElementNode) children.nextElement());
        }

        Collections.sort(arrayList, sortComparator);

        replaceChildren(node, arrayList);
    }

    private static void replaceChildren(final DefaultMutableTreeNode node,
            final Collection<? extends ElementNode> arrayList) {
        node.removeAllChildren();
        for (ElementNode child : arrayList) {
            node.add(child);
        }
    }

    protected void restoreTree() {
        Pair<ElementNode, List<ElementNode>> selection = storeSelection();

        DefaultMutableTreeNode root = getRootNode();
        if (!myShowClasses || myContainerNodes.isEmpty()) {
            List<ParentNode> otherObjects = new ArrayList<ParentNode>();
            Enumeration<TreeNode> children = getRootNodeChildren();
            ParentNode newRoot = new ParentNode(null, new MemberChooserObjectBase(getAllContainersNodeName()),
                    new Ref<Integer>(0));
            while (children.hasMoreElements()) {
                final ParentNode nextElement = (ParentNode) children.nextElement();
                if (nextElement instanceof ContainerNode) {
                    final ContainerNode containerNode = (ContainerNode) nextElement;
                    Enumeration<TreeNode> memberNodes = containerNode.children();
                    List<MemberNode> memberNodesList = new ArrayList<MemberNode>();
                    while (memberNodes.hasMoreElements()) {
                        memberNodesList.add((MemberNode) memberNodes.nextElement());
                    }
                    for (MemberNode memberNode : memberNodesList) {
                        newRoot.add(memberNode);
                    }
                } else {
                    otherObjects.add(nextElement);
                }
            }
            replaceChildren(root, otherObjects);
            sortNode(newRoot, myComparator);
            if (newRoot.children().hasMoreElements())
                root.add(newRoot);
        } else {
            Enumeration<TreeNode> children = getRootNodeChildren();
            while (children.hasMoreElements()) {
                ParentNode allClassesNode = (ParentNode) children.nextElement();
                Enumeration<TreeNode> memberNodes = allClassesNode.children();
                ArrayList<MemberNode> arrayList = new ArrayList<MemberNode>();
                while (memberNodes.hasMoreElements()) {
                    arrayList.add((MemberNode) memberNodes.nextElement());
                }
                Collections.sort(arrayList, myComparator);
                for (MemberNode memberNode : arrayList) {
                    myNodeToParentMap.get(memberNode).add(memberNode);
                }
            }
            replaceChildren(root, myContainerNodes);
        }
        myTreeModel.nodeStructureChanged(root);

        defaultExpandTree();

        restoreSelection(selection);
    }

    private void setShowClasses(boolean showClasses) {
        myShowClasses = showClasses;
        restoreTree();
    }

    protected String getAllContainersNodeName() {
        return IdeBundle.message("node.memberchooser.all.classes");
    }

    private Enumeration<TreeNode> getRootNodeChildren() {
        return getRootNode().children();
    }

    protected DefaultMutableTreeNode getRootNode() {
        return (DefaultMutableTreeNode) myTreeModel.getRoot();
    }

    private Pair<ElementNode, List<ElementNode>> storeSelection() {
        List<ElementNode> selectedNodes = new ArrayList<ElementNode>();
        TreePath[] paths = myTree.getSelectionPaths();
        if (paths != null) {
            for (TreePath path : paths) {
                selectedNodes.add((ElementNode) path.getLastPathComponent());
            }
        }
        TreePath leadSelectionPath = myTree.getLeadSelectionPath();
        return Pair.create(
                leadSelectionPath != null ? (ElementNode) leadSelectionPath.getLastPathComponent() : null,
                selectedNodes);
    }

    private void restoreSelection(Pair<ElementNode, List<ElementNode>> pair) {
        List<ElementNode> selectedNodes = pair.second;

        DefaultMutableTreeNode root = getRootNode();

        ArrayList<TreePath> toSelect = new ArrayList<TreePath>();
        for (ElementNode node : selectedNodes) {
            DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
            if (root.isNodeDescendant(treeNode)) {
                toSelect.add(new TreePath(treeNode.getPath()));
            }
        }

        if (!toSelect.isEmpty()) {
            myTree.setSelectionPaths(toSelect.toArray(new TreePath[toSelect.size()]));
        }

        ElementNode leadNode = pair.first;
        if (leadNode != null) {
            myTree.setLeadSelectionPath(new TreePath(((DefaultMutableTreeNode) leadNode).getPath()));
        }
    }

    @Override
    public void dispose() {
        PropertiesComponent instance = PropertiesComponent.getInstance();
        instance.setValue(PROP_SORTED, Boolean.toString(isAlphabeticallySorted()));
        instance.setValue(PROP_SHOWCLASSES, Boolean.toString(myShowClasses));

        if (myCopyJavadocCheckbox != null) {
            instance.setValue(PROP_COPYJAVADOC, Boolean.toString(myCopyJavadocCheckbox.isSelected()));
        }

        final Container contentPane = getContentPane();
        if (contentPane != null) {
            contentPane.removeAll();
        }
        mySelectedNodes.clear();
        myElements = null;
        super.dispose();
    }

    @Override
    public void calcData(final DataKey key, final DataSink sink) {
        if (key.equals(CommonDataKeys.PSI_ELEMENT)) {
            if (mySelectedElements != null && !mySelectedElements.isEmpty()) {
                T selectedElement = mySelectedElements.iterator().next();
                if (selectedElement instanceof ClassMemberWithElement) {
                    sink.put(CommonDataKeys.PSI_ELEMENT, ((ClassMemberWithElement) selectedElement).getElement());
                }
            }
        }
    }

    private class MyTreeSelectionListener implements TreeSelectionListener {
        @Override
        public void valueChanged(TreeSelectionEvent e) {
            TreePath[] paths = e.getPaths();
            if (paths == null)
                return;
            for (int i = 0; i < paths.length; i++) {
                Object node = paths[i].getLastPathComponent();
                if (node instanceof MemberNode) {
                    final MemberNode memberNode = (MemberNode) node;
                    if (e.isAddedPath(i)) {
                        if (!mySelectedNodes.contains(memberNode)) {
                            mySelectedNodes.add(memberNode);
                        }
                    } else {
                        mySelectedNodes.remove(memberNode);
                    }
                }
            }
            mySelectedElements = new LinkedHashSet<T>();
            for (MemberNode selectedNode : mySelectedNodes) {
                mySelectedElements.add((T) selectedNode.getDelegate());
            }
        }
    }

    protected interface ElementNode extends MutableTreeNode {
        MemberChooserObject getDelegate();

        int getOrder();
    }

    protected interface MemberNode extends ElementNode {
    }

    protected abstract static class ElementNodeImpl extends DefaultMutableTreeNode implements ElementNode {
        private final int myOrder;
        private final MemberChooserObject myDelegate;

        public ElementNodeImpl(@Nullable DefaultMutableTreeNode parent, MemberChooserObject delegate,
                Ref<Integer> order) {
            myOrder = order.get();
            order.set(myOrder + 1);
            myDelegate = delegate;
            if (parent != null) {
                parent.add(this);
            }
        }

        @Override
        public MemberChooserObject getDelegate() {
            return myDelegate;
        }

        @Override
        public int getOrder() {
            return myOrder;
        }
    }

    protected static class MemberNodeImpl extends ElementNodeImpl implements MemberNode {
        public MemberNodeImpl(ParentNode parent, ClassMember delegate, Ref<Integer> order) {
            super(parent, delegate, order);
        }
    }

    protected static class ParentNode extends ElementNodeImpl {
        public ParentNode(@Nullable DefaultMutableTreeNode parent, MemberChooserObject delegate,
                Ref<Integer> order) {
            super(parent, delegate, order);
        }
    }

    protected static class ContainerNode extends ParentNode {
        public ContainerNode(DefaultMutableTreeNode parent, MemberChooserObject delegate, Ref<Integer> order) {
            super(parent, delegate, order);
        }
    }

    private class SelectNoneAction extends AbstractAction {
        public SelectNoneAction() {
            super(IdeBundle.message("action.select.none"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            myTree.clearSelection();
            doOKAction();
        }
    }

    private class TreeKeyListener extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {
            TreePath path = myTree.getLeadSelectionPath();
            if (path == null)
                return;
            final Object lastComponent = path.getLastPathComponent();
            if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                if (lastComponent instanceof ParentNode)
                    return;
                doOKAction();
                e.consume();
            } else if (e.getKeyCode() == KeyEvent.VK_INSERT) {
                if (lastComponent instanceof ElementNode) {
                    final DefaultMutableTreeNode node = (DefaultMutableTreeNode) lastComponent;
                    if (!mySelectedNodes.contains(node)) {
                        if (node.getNextNode() != null) {
                            myTree.setSelectionPath(new TreePath(node.getNextNode().getPath()));
                        }
                    } else {
                        if (node.getNextNode() != null) {
                            myTree.removeSelectionPath(new TreePath(node.getPath()));
                            myTree.setSelectionPath(new TreePath(node.getNextNode().getPath()));
                            myTree.repaint();
                        }
                    }
                    e.consume();
                }
            }
        }
    }

    private class SortEmAction extends ToggleAction {
        public SortEmAction() {
            super(IdeBundle.message("action.sort.alphabetically"), IdeBundle.message("action.sort.alphabetically"),
                    AllIcons.ObjectBrowser.Sorted);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return isAlphabeticallySorted();
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            myAlphabeticallySorted = flag;
            setSortComparator(flag ? new AlphaComparator() : new OrderComparator());
            if (flag) {
                MemberChooser.this.onAlphabeticalSortingEnabled(event);
            }
        }
    }

    protected ShowContainersAction getShowContainersAction() {
        return new ShowContainersAction(IdeBundle.message("action.show.classes"), AllIcons.Nodes.Class);
    }

    protected class ShowContainersAction extends ToggleAction {
        public ShowContainersAction(final String text, final Icon icon) {
            super(text, text, icon);
        }

        @Override
        public boolean isSelected(AnActionEvent event) {
            return myShowClasses;
        }

        @Override
        public void setSelected(AnActionEvent event, boolean flag) {
            setShowClasses(flag);
        }

        @Override
        public void update(AnActionEvent e) {
            super.update(e);
            Presentation presentation = e.getPresentation();
            presentation.setEnabled(myContainerNodes.size() > 1);
        }
    }

    private class ExpandAllAction extends AnAction {
        public ExpandAllAction() {
            super(IdeBundle.message("action.expand.all"), IdeBundle.message("action.expand.all"),
                    AllIcons.Actions.Expandall);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            TreeUtil.expandAll(myTree);
        }
    }

    private class CollapseAllAction extends AnAction {
        public CollapseAllAction() {
            super(IdeBundle.message("action.collapse.all"), IdeBundle.message("action.collapse.all"),
                    AllIcons.Actions.Collapseall);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            TreeUtil.collapseAll(myTree, 1);
        }
    }

    private static class AlphaComparator implements Comparator<ElementNode> {
        @Override
        public int compare(ElementNode n1, ElementNode n2) {
            return n1.getDelegate().getText().compareToIgnoreCase(n2.getDelegate().getText());
        }
    }

    protected static class OrderComparator implements Comparator<ElementNode> {
        public OrderComparator() {
        } // To make this class instanceable from the subclasses

        @Override
        public int compare(ElementNode n1, ElementNode n2) {
            if (n1.getDelegate() instanceof ClassMemberWithElement
                    && n2.getDelegate() instanceof ClassMemberWithElement) {
                PsiElement element1 = ((ClassMemberWithElement) n1.getDelegate()).getElement();
                PsiElement element2 = ((ClassMemberWithElement) n2.getDelegate()).getElement();
                if (!(element1 instanceof PsiCompiledElement) && !(element2 instanceof PsiCompiledElement)) {
                    return element1.getTextOffset() - element2.getTextOffset();
                }
            }
            return n1.getOrder() - n2.getOrder();
        }
    }

    private static class ElementNodeComparatorWrapper<T> implements Comparator<ElementNode> {
        private final Comparator<T> myDelegate;

        public ElementNodeComparatorWrapper(final Comparator<T> delegate) {
            myDelegate = delegate;
        }

        @SuppressWarnings("unchecked")
        @Override
        public int compare(final ElementNode o1, final ElementNode o2) {
            return myDelegate.compare((T) o1.getDelegate(), (T) o2.getDelegate());
        }
    }
}