com.intellij.ide.commander.CommanderPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.commander.CommanderPanel.java

Source

/*
 * Copyright 2000-2013 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.commander;

import com.intellij.history.LocalHistory;
import com.intellij.history.LocalHistoryAction;
import com.intellij.ide.CopyPasteDelegator;
import com.intellij.ide.DeleteProvider;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.IdeView;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.impl.ModuleGroup;
import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase;
import com.intellij.ide.projectView.impl.nodes.LibraryGroupElement;
import com.intellij.ide.projectView.impl.nodes.NamedLibraryElement;
import com.intellij.ide.structureView.StructureViewTreeElement;
import com.intellij.ide.ui.customization.CustomActionsSchema;
import com.intellij.ide.util.DeleteHandler;
import com.intellij.ide.util.DirectoryChooserUtil;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.ide.util.EditorHelper;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.*;
import com.intellij.ui.components.JBList;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Eugene Belyaev
 */
public class CommanderPanel extends JPanel {
    private static final Logger LOG = Logger.getInstance("#com.intellij.ide.commander.CommanderPanel");

    private static final Color DARK_BLUE = new Color(55, 85, 134);
    private static final Color DARK_BLUE_BRIGHTER = new Color(58, 92, 149);
    private static final Color DARK_BLUE_DARKER = new Color(38, 64, 106);

    private Project myProject;
    private AbstractListBuilder myBuilder;
    private JPanel myTitlePanel;
    private JLabel myParentTitle;
    protected final JBList myList;
    private final MyModel myModel;

    private CopyPasteDelegator myCopyPasteDelegator;
    protected final ListSpeedSearch myListSpeedSearch;
    private final IdeView myIdeView = new MyIdeView();
    private final MyDeleteElementProvider myDeleteElementProvider = new MyDeleteElementProvider();
    @NonNls
    private static final String ACTION_DRILL_DOWN = "DrillDown";
    @NonNls
    private static final String ACTION_GO_UP = "GoUp";
    private ProjectAbstractTreeStructureBase myProjectTreeStructure;
    private boolean myActive = true;
    private final List<CommanderHistoryListener> myHistoryListeners = ContainerUtil.createLockFreeCopyOnWriteList();
    private boolean myMoveFocus = false;
    private final boolean myEnableSearchHighlighting;

    public CommanderPanel(final Project project, final boolean enablePopupMenu,
            final boolean enableSearchHighlighting) {
        super(new BorderLayout());
        myProject = project;
        myEnableSearchHighlighting = enableSearchHighlighting;
        myModel = new MyModel();
        myList = new JBList(myModel);
        myList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

        if (enablePopupMenu) {
            myCopyPasteDelegator = new CopyPasteDelegator(myProject, myList) {
                @Override
                @NotNull
                protected PsiElement[] getSelectedElements() {
                    return CommanderPanel.this.getSelectedElements();
                }
            };
        }

        myListSpeedSearch = new ListSpeedSearch(myList);
        myListSpeedSearch.setClearSearchOnNavigateNoMatch(true);

        ListScrollingUtil.installActions(myList);

        myList.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                if (myBuilder == null)
                    return;
                myBuilder.buildRoot();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH,
                SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);

        myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ACTION_DRILL_DOWN);
        myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN,
                SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK), ACTION_DRILL_DOWN);
        myList.getActionMap().put(ACTION_DRILL_DOWN, new AbstractAction() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                drillDown();
            }
        });
        new DoubleClickListener() {
            @Override
            protected boolean onDoubleClick(MouseEvent e) {
                drillDown();
                return true;
            }
        }.installOn(myList);

        myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP,
                SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK), ACTION_GO_UP);
        myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), ACTION_GO_UP);
        myList.getActionMap().put(ACTION_GO_UP, new AbstractAction() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                goUp();
            }
        });

        myList.getActionMap().put("selectAll", new AbstractAction() {
            @Override
            public void actionPerformed(final ActionEvent e) {
            }
        });

        if (enablePopupMenu) {
            myList.addMouseListener(new PopupHandler() {
                @Override
                public void invokePopup(final Component comp, final int x, final int y) {
                    CommanderPanel.this.invokePopup(comp, x, y);
                }
            });
        }

        myList.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(final FocusEvent e) {
                setActive(true);
            }

            @Override
            public void focusLost(final FocusEvent e) {
                setActive(false);
            }
        });
    }

    public boolean isEnableSearchHighlighting() {
        return myEnableSearchHighlighting;
    }

    public void addHistoryListener(@NotNull CommanderHistoryListener listener) {
        myHistoryListeners.add(listener);
    }

    private void removeHistoryListener(CommanderHistoryListener listener) {
        myHistoryListeners.remove(listener);
    }

    private void updateHistory(boolean elementExpanded) {
        for (CommanderHistoryListener listener : myHistoryListeners) {
            listener.historyChanged(getSelectedElement(), elementExpanded);
        }
    }

    public final JList getList() {
        return myList;
    }

    public final AbstractListBuilder.Model getModel() {
        return myModel;
    }

    public void setMoveFocus(final boolean moveFocus) {
        myMoveFocus = moveFocus;
    }

    public void goUp() {
        if (myBuilder == null) {
            return;
        }
        updateHistory(true);
        myBuilder.goUp();
        updateHistory(false);
    }

    public void drillDown() {
        if (topElementIsSelected()) {
            goUp();
            return;
        }

        if (getSelectedValue() == null) {
            return;
        }

        final AbstractTreeNode element = getSelectedNode();
        if (element.getChildren().isEmpty()) {
            if (!shouldDrillDownOnEmptyElement(element)) {
                navigateSelectedElement();
                return;
            }
        }

        if (myBuilder == null) {
            return;
        }
        updateHistory(false);
        myBuilder.drillDown();
        updateHistory(true);
    }

    public boolean navigateSelectedElement() {
        final AbstractTreeNode selectedNode = getSelectedNode();
        if (selectedNode != null) {
            if (selectedNode.canNavigateToSource()) {
                selectedNode.navigate(true);
                return true;
            }
        }
        return false;
    }

    protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) {
        return node instanceof ProjectViewNode && ((ProjectViewNode) node).shouldDrillDownOnEmptyElement();
    }

    private boolean topElementIsSelected() {
        int[] selectedIndices = myList.getSelectedIndices();
        return selectedIndices.length == 1 && selectedIndices[0] == 0
                && myModel.getElementAt(selectedIndices[0]) instanceof TopLevelNode;
    }

    public final void setBuilder(final AbstractListBuilder builder) {
        myBuilder = builder;
        removeAll();

        myTitlePanel = new JPanel(new BorderLayout());
        myTitlePanel.setBackground(UIUtil.getControlColor());
        myTitlePanel.setOpaque(true);

        myParentTitle = new MyTitleLabel(myTitlePanel);
        myParentTitle.setText(" ");
        myParentTitle.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
        myParentTitle.setForeground(JBColor.foreground());
        myParentTitle.setUI(new RightAlignedLabelUI());
        final JPanel panel1 = new JPanel(new BorderLayout());
        panel1.setOpaque(false);
        panel1.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
        panel1.add(myParentTitle, BorderLayout.CENTER);
        myTitlePanel.add(panel1, BorderLayout.CENTER);

        add(myTitlePanel, BorderLayout.NORTH);
        final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList);
        scrollPane.setBorder(null);
        scrollPane.getVerticalScrollBar().setFocusable(false); // otherwise the scrollbar steals focus and panel switching with tab is broken 
        scrollPane.getHorizontalScrollBar().setFocusable(false);
        add(scrollPane, BorderLayout.CENTER);

        myBuilder.setParentTitle(myParentTitle);

        // TODO[vova,anton] it seems that the code below performs double focus request. Is it OK?
        myTitlePanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(final MouseEvent e) {
                myList.requestFocus();
            }

            @Override
            public void mousePressed(final MouseEvent e) {
                myList.requestFocus();
            }
        });
    }

    public final AbstractListBuilder getBuilder() {
        return myBuilder;
    }

    public final PsiElement getSelectedElement() {
        Object value = getValueAtIndex(getSelectedNode());
        return (PsiElement) (value instanceof PsiElement ? value : null);
    }

    public final PsiElement getSelectedElement(int index) {
        Object elementAtIndex = myModel.getElementAt(index);
        Object value = getValueAtIndex(
                elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode) elementAtIndex : null);
        return (PsiElement) (value instanceof PsiElement ? value : null);
    }

    public AbstractTreeNode getSelectedNode() {
        if (myBuilder == null)
            return null;
        final int[] indices = myList.getSelectedIndices();
        if (indices.length != 1)
            return null;
        int index = indices[0];
        if (index >= myModel.getSize())
            return null;
        Object elementAtIndex = myModel.getElementAt(index);
        return elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode) elementAtIndex : null;
    }

    @NotNull
    private List<AbstractTreeNode> getSelectedNodes() {
        if (myBuilder == null)
            return Collections.emptyList();
        final int[] indices = myList.getSelectedIndices();
        ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
        for (int index : indices) {
            if (index >= myModel.getSize())
                continue;
            Object elementAtIndex = myModel.getElementAt(index);
            AbstractTreeNode node = elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode) elementAtIndex
                    : null;
            if (node != null) {
                result.add(node);
            }
        }
        return result;
    }

    public Object getSelectedValue() {
        return getValueAtIndex(getSelectedNode());
    }

    private PsiElement[] getSelectedElements() {
        if (myBuilder == null)
            return PsiElement.EMPTY_ARRAY;
        final int[] indices = myList.getSelectedIndices();

        final ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
        for (int index : indices) {
            final PsiElement element = getSelectedElement(index);
            if (element != null) {
                elements.add(element);
            }
        }

        return PsiUtilCore.toPsiElementArray(elements);
    }

    private static Object getValueAtIndex(AbstractTreeNode node) {
        if (node == null)
            return null;
        Object value = node.getValue();
        if (value instanceof StructureViewTreeElement) {
            return ((StructureViewTreeElement) value).getValue();
        }
        return value;
    }

    public final void setActive(final boolean active) {
        myActive = active;
        if (active) {
            myTitlePanel.setBackground(DARK_BLUE);
            myTitlePanel.setBorder(
                    BorderFactory.createBevelBorder(BevelBorder.RAISED, DARK_BLUE_BRIGHTER, DARK_BLUE_DARKER));
            myParentTitle.setForeground(Color.white);
        } else {
            final Color color = UIUtil.getPanelBackground();
            LOG.assertTrue(color != null);
            myTitlePanel.setBackground(color);
            myTitlePanel.setBorder(
                    BorderFactory.createBevelBorder(BevelBorder.LOWERED, color.brighter(), color.darker()));
            myParentTitle.setForeground(JBColor.foreground());
        }
        final int[] selectedIndices = myList.getSelectedIndices();
        if (selectedIndices.length == 0 && myList.getModel().getSize() > 0) {
            myList.setSelectedIndex(0);
            if (!myList.hasFocus()) {
                myList.requestFocus();
            }
        } else if (myList.getModel().getSize() > 0) {
            // need this to generate SelectionChanged events so that listeners, added by Commander, will be notified
            myList.setSelectedIndices(selectedIndices);
        }
    }

    public boolean isActive() {
        return myActive;
    }

    private void invokePopup(final Component c, final int x, final int y) {
        if (myBuilder == null)
            return;

        if (myList.getSelectedIndices().length <= 1) {
            final int popupIndex = myList.locationToIndex(new Point(x, y));
            if (popupIndex >= 0) {
                myList.setSelectedIndex(popupIndex);
                myList.requestFocus();
            }
        }

        final ActionGroup group = (ActionGroup) CustomActionsSchema.getInstance()
                .getCorrectedAction(IdeActions.GROUP_COMMANDER_POPUP);
        final ActionPopupMenu popupMenu = ActionManager.getInstance()
                .createActionPopupMenu(ActionPlaces.COMMANDER_POPUP, group);
        popupMenu.getComponent().show(c, x, y);
    }

    public final void dispose() {
        if (myBuilder != null) {
            myBuilder.dispose();
            myBuilder = null;
        }
        myProject = null;
    }

    public final void setTitlePanelVisible(final boolean flag) {
        myTitlePanel.setVisible(flag);
    }

    public final Object getDataImpl(final String dataId) {
        if (myBuilder == null)
            return null;
        final Object selectedValue = getSelectedValue();
        if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
            final PsiElement selectedElement = getSelectedElement();
            return selectedElement != null && selectedElement.isValid() ? selectedElement : null;
        }
        if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
            return filterInvalidElements(getSelectedElements());
        }
        if (LangDataKeys.PASTE_TARGET_PSI_ELEMENT.is(dataId)) {
            final AbstractTreeNode parentNode = myBuilder.getParentNode();
            final Object element = parentNode != null ? parentNode.getValue() : null;
            return element instanceof PsiElement && ((PsiElement) element).isValid() ? element : null;
        }
        if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
            return getNavigatables();
        }
        if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
            return myCopyPasteDelegator != null ? myCopyPasteDelegator.getCopyProvider() : null;
        }
        if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
            return myCopyPasteDelegator != null ? myCopyPasteDelegator.getCutProvider() : null;
        }
        if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
            return myCopyPasteDelegator != null ? myCopyPasteDelegator.getPasteProvider() : null;
        }
        if (LangDataKeys.IDE_VIEW.is(dataId)) {
            return myIdeView;
        }
        if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
            return myDeleteElementProvider;
        }
        if (LangDataKeys.MODULE.is(dataId)) {
            return selectedValue instanceof Module ? selectedValue : null;
        }
        if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
            return selectedValue instanceof ModuleGroup ? new ModuleGroup[] { (ModuleGroup) selectedValue } : null;
        }
        if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
            return selectedValue instanceof LibraryGroupElement
                    ? new LibraryGroupElement[] { (LibraryGroupElement) selectedValue }
                    : null;
        }
        if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
            return selectedValue instanceof NamedLibraryElement
                    ? new NamedLibraryElement[] { (NamedLibraryElement) selectedValue }
                    : null;
        }

        if (myProjectTreeStructure != null) {
            return myProjectTreeStructure.getDataFromProviders(getSelectedNodes(), dataId);
        }

        return null;
    }

    private Navigatable[] getNavigatables() {
        if (myBuilder == null)
            return null;
        final int[] indices = myList.getSelectedIndices();
        if (indices == null || indices.length == 0)
            return null;

        final ArrayList<Navigatable> elements = new ArrayList<Navigatable>();
        for (int index : indices) {
            final Object element = myModel.getElementAt(index);
            if (element instanceof AbstractTreeNode) {
                elements.add((Navigatable) element);
            }
        }

        return elements.toArray(new Navigatable[elements.size()]);
    }

    @Nullable
    private static PsiElement[] filterInvalidElements(final PsiElement[] elements) {
        if (elements == null || elements.length == 0) {
            return null;
        }
        final List<PsiElement> validElements = new ArrayList<PsiElement>(elements.length);
        for (final PsiElement element : elements) {
            if (element.isValid()) {
                validElements.add(element);
            }
        }
        return validElements.size() == elements.length ? elements : PsiUtilCore.toPsiElementArray(validElements);
    }

    protected final Navigatable createEditSourceDescriptor() {
        return EditSourceUtil.getDescriptor(getSelectedElement());
    }

    public void setProjectTreeStructure(final ProjectAbstractTreeStructureBase projectTreeStructure) {
        myProjectTreeStructure = projectTreeStructure;
    }

    private static final class MyTitleLabel extends JLabel {
        private final JPanel myPanel;

        public MyTitleLabel(final JPanel panel) {
            myPanel = panel;
        }

        @Override
        public void setText(String text) {
            if (text == null || text.isEmpty()) {
                text = " ";
            }
            super.setText(text);
            if (myPanel != null) {
                myPanel.setToolTipText(text.trim().isEmpty() ? null : text);
            }
        }
    }

    private final class MyDeleteElementProvider implements DeleteProvider {
        @Override
        public void deleteElement(@NotNull final DataContext dataContext) {
            LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
            try {
                final PsiElement[] elements = getSelectedElements();
                DeleteHandler.deletePsiElement(elements, myProject);
            } finally {
                a.finish();
            }
        }

        @Override
        public boolean canDeleteElement(@NotNull final DataContext dataContext) {
            final PsiElement[] elements = getSelectedElements();
            return DeleteHandler.shouldEnableDeleteAction(elements);
        }
    }

    private final class MyIdeView implements IdeView {
        @Override
        public void selectElement(final PsiElement element) {
            final boolean isDirectory = element instanceof PsiDirectory;
            if (!isDirectory) {
                EditorHelper.openInEditor(element);
            }
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                @Override
                public void run() {
                    myBuilder.selectElement(element, PsiUtilCore.getVirtualFile(element));
                    if (!isDirectory) {
                        ApplicationManager.getApplication().invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                if (myMoveFocus) {
                                    ToolWindowManager.getInstance(myProject).activateEditorComponent();
                                }
                            }
                        });
                    }
                }
            }, ModalityState.NON_MODAL);
        }

        private PsiDirectory getDirectory() {
            if (myBuilder == null)
                return null;
            final Object parentElement = myBuilder.getParentNode();
            if (parentElement instanceof AbstractTreeNode) {
                final AbstractTreeNode parentNode = (AbstractTreeNode) parentElement;
                if (!(parentNode.getValue() instanceof PsiDirectory))
                    return null;
                return (PsiDirectory) parentNode.getValue();
            } else {
                return null;
            }
        }

        @Override
        public PsiDirectory[] getDirectories() {
            PsiDirectory directory = getDirectory();
            return directory == null ? PsiDirectory.EMPTY_ARRAY : new PsiDirectory[] { directory };
        }

        @Override
        public PsiDirectory getOrChooseDirectory() {
            return DirectoryChooserUtil.getOrChooseDirectory(this);
        }
    }

    public static final class MyModel extends AbstractListModel implements AbstractListBuilder.Model {
        final List myElements = new ArrayList();

        @Override
        public void removeAllElements() {
            int index1 = myElements.size() - 1;
            myElements.clear();
            if (index1 >= 0) {
                fireIntervalRemoved(this, 0, index1);
            }
        }

        @Override
        public void addElement(final Object obj) {
            int index = myElements.size();
            myElements.add(obj);
            fireIntervalAdded(this, index, index);
        }

        @Override
        public void replaceElements(final List newElements) {
            removeAllElements();
            myElements.addAll(newElements);
            fireIntervalAdded(this, 0, newElements.size());
        }

        @Override
        public Object[] toArray() {
            return ArrayUtil.toObjectArray(myElements);
        }

        @Override
        public int indexOf(final Object o) {
            return myElements.indexOf(o);
        }

        @Override
        public int getSize() {
            return myElements.size();
        }

        @Override
        public Object getElementAt(final int index) {
            return myElements.get(index);
        }
    }
}