blob: f32d77131d827ceebed23f8fa4932b0468e67c03 [file] [log] [blame]
/*
* 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 org.jetbrains.plugins.groovy.mvc.projectView;
import com.intellij.icons.AllIcons;
import com.intellij.ide.*;
import com.intellij.ide.projectView.BaseProjectTreeBuilder;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.*;
import com.intellij.ide.util.DeleteHandler;
import com.intellij.ide.util.DirectoryChooserUtil;
import com.intellij.ide.util.EditorHelper;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.AbstractTreeUpdater;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.ex.ToolWindowEx;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.ui.AutoScrollFromSourceHandler;
import com.intellij.ui.AutoScrollToSourceHandler;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.IJSwingUtilities;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.mvc.MvcFramework;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Dmitry Krasislchikov
*/
public class MvcProjectViewPane extends AbstractProjectViewPSIPane implements IdeView {
private final CopyPasteDelegator myCopyPasteDelegator;
private final JComponent myComponent;
private final DeleteProvider myDeletePSIElementProvider;
private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider();
private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
private final MyAutoScrollFromSourceHandler myAutoScrollFromSourceHandler;
@NonNls private final String myId;
private final MvcToolWindowDescriptor myDescriptor;
private final MvcProjectViewState myViewState;
public MvcProjectViewPane(final Project project, MvcToolWindowDescriptor descriptor) {
super(project);
myDescriptor = descriptor;
myId = descriptor.getToolWindowId();
myViewState = descriptor.getProjectViewState(project);
class TreeUpdater implements Runnable, PsiModificationTracker.Listener {
private volatile boolean myInQueue;
@Override
public void run() {
if (getTree() != null && getTreeBuilder() != null) {
updateFromRoot(true);
}
myInQueue = false;
}
@Override
public void modificationCountChanged() {
if (!myInQueue) {
myInQueue = true;
ApplicationManager.getApplication().invokeLater(this);
}
}
}
project.getMessageBus().connect(this).subscribe(PsiModificationTracker.TOPIC, new TreeUpdater());
myComponent = createComponent();
DataManager.registerDataProvider(myComponent, this);
myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler();
myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
@Override
protected boolean isAutoScrollMode() {
return myViewState.autoScrollToSource;
}
@Override
protected void setAutoScrollMode(boolean state) {
myViewState.autoScrollToSource = state;
}
};
myAutoScrollFromSourceHandler.install();
myAutoScrollToSourceHandler.install(getTree());
myAutoScrollToSourceHandler.onMouseClicked(getTree());
myCopyPasteDelegator = new CopyPasteDelegator(project, myComponent) {
@NotNull
@Override
protected PsiElement[] getSelectedElements() {
return MvcProjectViewPane.this.getSelectedPSIElements();
}
};
myDeletePSIElementProvider = new DeleteHandler.DefaultDeleteProvider();
}
public void setup(ToolWindowEx toolWindow) {
JPanel p = new JPanel(new BorderLayout());
p.add(myComponent, BorderLayout.CENTER);
ContentManager contentManager = toolWindow.getContentManager();
Content content = contentManager.getFactory().createContent(p, null, false);
content.setDisposer(this);
content.setCloseable(false);
content.setPreferredFocusableComponent(createComponent());
contentManager.addContent(content);
contentManager.setSelectedContent(content, true);
DefaultActionGroup group = new DefaultActionGroup();
group.add(new HideEmptyMiddlePackagesAction());
group.add(myAutoScrollToSourceHandler.createToggleAction());
group.add(myAutoScrollFromSourceHandler.createToggleAction());
toolWindow.setAdditionalGearActions(group);
TreeExpander expander = new DefaultTreeExpander(myTree);
CommonActionsManager actionsManager = CommonActionsManager.getInstance();
AnAction collapseAction = actionsManager.createCollapseAllAction(expander, myTree);
collapseAction.getTemplatePresentation().setIcon(AllIcons.General.CollapseAll);
toolWindow.setTitleActions(new AnAction[]{new ScrollFromSourceAction(), collapseAction});
}
@Override
public String getTitle() {
throw new UnsupportedOperationException();
}
@Override
public Icon getIcon() {
return myDescriptor.getFramework().getIcon();
}
@Override
@NotNull
public String getId() {
return myId;
}
@Override
public int getWeight() {
throw new UnsupportedOperationException();
}
@Override
public boolean isInitiallyVisible() {
throw new UnsupportedOperationException();
}
@Override
public SelectInTarget createSelectInTarget() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
protected BaseProjectTreeBuilder createBuilder(final DefaultTreeModel treeModel) {
return new ProjectTreeBuilder(myProject, myTree, treeModel, null, (ProjectAbstractTreeStructureBase)myTreeStructure) {
@Override
protected AbstractTreeUpdater createUpdater() {
return createTreeUpdater(this);
}
};
}
@Override
protected ProjectAbstractTreeStructureBase createStructure() {
final Project project = myProject;
final String id = getId();
return new ProjectTreeStructure(project, id) {
@Override
public boolean isHideEmptyMiddlePackages() {
return myViewState.hideEmptyMiddlePackages;
}
@Override
protected AbstractTreeNode createRoot(final Project project, ViewSettings settings) {
return new MvcProjectNode(project, this, myDescriptor);
}
};
}
@Override
protected ProjectViewTree createTree(final DefaultTreeModel treeModel) {
return new ProjectViewTree(myProject, treeModel) {
public String toString() {
return myDescriptor.getFramework().getDisplayName() + " " + super.toString();
}
@Override
public DefaultMutableTreeNode getSelectedNode() {
return MvcProjectViewPane.this.getSelectedNode();
}
};
}
@Override
protected AbstractTreeUpdater createTreeUpdater(final AbstractTreeBuilder treeBuilder) {
return new AbstractTreeUpdater(treeBuilder);
}
@Override
@Nullable
protected PsiElement getPSIElement(@Nullable final Object element) {
// E.g is used by Project View's DataProvider
if (element instanceof NodeId) {
final PsiElement psiElement = ((NodeId)element).getPsiElement();
if (psiElement != null && psiElement.isValid()) {
return psiElement;
}
}
return super.getPSIElement(element);
}
@Override
public Object getData(String dataId) {
if (DataConstants.PSI_ELEMENT.equals(dataId)) {
final PsiElement[] elements = getSelectedPSIElements();
return elements.length == 1 ? elements[0] : null;
}
if (DataConstants.PSI_ELEMENT_ARRAY.equals(dataId)) {
return getSelectedPSIElements();
}
if (DataConstants.MODULE_CONTEXT.equals(dataId)) {
final Object element = getSelectedElement();
if (element instanceof Module) {
return element;
}
return null;
}
if (DataConstants.MODULE_CONTEXT_ARRAY.equals(dataId)) {
final List<Module> moduleList = ContainerUtil.findAll(getSelectedElements(), Module.class);
if (!moduleList.isEmpty()) {
return moduleList.toArray(new Module[moduleList.size()]);
}
return null;
}
if (dataId.equals(DataConstants.IDE_VIEW)) {
return this;
}
if (dataId.equals(DataConstants.HELP_ID)) {
return "reference.toolwindows." + myId.toLowerCase();
}
if (DataConstants.CUT_PROVIDER.equals(dataId)) {
return myCopyPasteDelegator.getCutProvider();
}
if (DataConstants.COPY_PROVIDER.equals(dataId)) {
return myCopyPasteDelegator.getCopyProvider();
}
if (DataConstants.PASTE_PROVIDER.equals(dataId)) {
return myCopyPasteDelegator.getPasteProvider();
}
if (DataConstants.DELETE_ELEMENT_PROVIDER.equals(dataId)) {
for (final Object element : getSelectedElements()) {
if (element instanceof Module) {
return myDeleteModuleProvider;
}
}
return myDeletePSIElementProvider;
}
return super.getData(dataId);
}
@Nullable
public static MvcProjectViewPane getView(final Project project, MvcFramework framework) {
final ToolWindow window = ToolWindowManager.getInstance(project).getToolWindow(MvcToolWindowDescriptor.getToolWindowId(framework));
final Content content = window == null ? null : window.getContentManager().getContent(0);
return content == null ? null : (MvcProjectViewPane)content.getDisposer();
}
@Override
public void selectElement(PsiElement element) {
PsiFileSystemItem psiFile;
if (element instanceof PsiFileSystemItem) {
psiFile = (PsiFileSystemItem)element;
}
else {
psiFile = element.getContainingFile();
if (psiFile == null) {
return;
}
}
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile == null) {
return;
}
selectFile(virtualFile, false);
boolean requestFocus = true;
if (psiFile instanceof PsiFile) {
Editor editor = EditorHelper.openInEditor(element);
if (editor != null) {
ToolWindowManager.getInstance(myProject).activateEditorComponent();
requestFocus = false;
}
}
if (requestFocus) {
selectFile(virtualFile, true);
}
}
@NotNull
@Override
public PsiDirectory[] getDirectories() {
return getSelectedDirectories();
}
@Override
public PsiDirectory getOrChooseDirectory() {
return DirectoryChooserUtil.getOrChooseDirectory(this);
}
public static boolean canSelectFile(@NotNull Project project, @NotNull MvcFramework framework, VirtualFile file) {
return getSelectPath(project, framework, file) != null;
}
@Nullable
private List<Object> getSelectPath(VirtualFile file) {
return getSelectPath(myProject, myDescriptor.getFramework(), file);
}
@Nullable
private static List<Object> getSelectPath(@NotNull Project project, @NotNull MvcFramework framework, VirtualFile file) {
if (file == null) {
return null;
}
final Module module = ModuleUtilCore.findModuleForFile(file, project);
if (module == null || !framework.hasSupport(module)) {
return null;
}
List<Object> result = new ArrayList<Object>();
final MvcProjectViewPane view = getView(project, framework);
if (view == null) {
return null;
}
final MvcProjectNode root = (MvcProjectNode)view.getTreeBuilder().getTreeStructure().getRootElement();
result.add(root);
for (AbstractTreeNode moduleNode : root.getChildren()) {
if (moduleNode.getValue() == module) {
result.add(moduleNode);
AbstractTreeNode<?> cur = moduleNode;
path:
while (true) {
for (AbstractTreeNode descriptor : cur.getChildren()) {
if (descriptor instanceof AbstractFolderNode) {
final AbstractFolderNode folderNode = (AbstractFolderNode)descriptor;
final VirtualFile dir = folderNode.getVirtualFile();
if (dir != null && VfsUtilCore.isAncestor(dir, file, false)) {
cur = folderNode;
result.add(folderNode);
if (dir.equals(file)) {
return result;
}
continue path;
}
}
if (descriptor instanceof AbstractMvcPsiNodeDescriptor) {
if (file.equals(((AbstractMvcPsiNodeDescriptor)descriptor).getVirtualFile())) {
result.add(descriptor);
return result;
}
}
}
return null;
}
}
}
return null;
}
public boolean canSelectFile(VirtualFile file) {
return getSelectPath(file) != null;
}
private void selectElementAtCaret(Editor editor, boolean requestFocus) {
PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
selectFile(file, requestFocus);
}
public void selectFile(@Nullable PsiFile file, boolean requestFocus) {
if (file == null) return;
selectFile(file.getVirtualFile(), requestFocus);
}
public void selectFile(@Nullable VirtualFile file, boolean requestFocus) {
if (file == null) return;
final List<Object> path = getSelectPath(file);
if (path == null) return;
final Object value = ((AbstractTreeNode)path.get(path.size() - 1)).getValue();
select(value, file, requestFocus);
}
public void scrollFromSource() {
FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor();
if (selectedTextEditor != null) {
selectElementAtCaret(selectedTextEditor, false);
return;
}
final FileEditor[] editors = fileEditorManager.getSelectedEditors();
for (FileEditor fileEditor : editors) {
if (fileEditor instanceof TextEditor) {
Editor editor = ((TextEditor)fileEditor).getEditor();
selectElementAtCaret(editor, false);
return;
}
}
final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles();
if (selectedFiles.length > 0) {
PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]);
selectFile(file, false);
}
}
private void selectElementAtCaretNotLosingFocus() {
if (IJSwingUtilities.hasFocus(this.getComponentToFocus())) return;
scrollFromSource();
}
private class ScrollFromSourceAction extends AnAction implements DumbAware {
private ScrollFromSourceAction() {
super("Scroll from Source", "Select the file open in the active editor", AllIcons.General.Locate);
}
@Override
public void actionPerformed(AnActionEvent e) {
scrollFromSource();
}
}
private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
protected MyAutoScrollFromSourceHandler() {
super(MvcProjectViewPane.this.myProject, myComponent, MvcProjectViewPane.this);
}
@Override
protected boolean isAutoScrollEnabled() {
return myViewState.autoScrollFromSource;
}
@Override
protected void setAutoScrollEnabled(boolean state) {
myViewState.autoScrollFromSource = state;
if (state) {
selectElementAtCaretNotLosingFocus();
}
}
@Override
protected void selectElementFromEditor(@NotNull FileEditor editor) {
selectElementAtCaretNotLosingFocus();
}
}
private class HideEmptyMiddlePackagesAction extends ToggleAction implements DumbAware {
private HideEmptyMiddlePackagesAction() {
super("Compact Empty Middle Packages", "Show/Compact Empty Middle Packages",
AllIcons.ObjectBrowser.CompactEmptyPackages);
}
@Override
public boolean isSelected(AnActionEvent e) {
return myViewState.hideEmptyMiddlePackages;
}
@Override
public void setSelected(AnActionEvent event, boolean flag) {
myViewState.hideEmptyMiddlePackages = flag;
TreeUtil.collapseAll(myTree, 1);
}
}
}