/* * 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.projectView.impl; import com.intellij.ProjectTopics; import com.intellij.history.LocalHistory; import com.intellij.history.LocalHistoryAction; import com.intellij.icons.AllIcons; import com.intellij.ide.*; import com.intellij.ide.actions.CollapseAllToolbarAction; import com.intellij.ide.impl.ProjectViewSelectInTarget; import com.intellij.ide.projectView.HelpID; import com.intellij.ide.projectView.ProjectView; import com.intellij.ide.projectView.ProjectViewNode; import com.intellij.ide.projectView.impl.nodes.*; import com.intellij.ide.scopeView.ScopeViewPane; import com.intellij.ide.ui.SplitterProportionsDataImpl; import com.intellij.ide.ui.UISettings; 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.NodeDescriptor; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.ui.SplitterProportionsData; import com.intellij.openapi.ui.popup.PopupChooserBuilder; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.*; import com.intellij.openapi.wm.ex.ToolWindowEx; import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter; import com.intellij.openapi.wm.ex.ToolWindowManagerEx; import com.intellij.openapi.wm.impl.content.ToolWindowContentUi; import com.intellij.psi.*; import com.intellij.psi.impl.file.PsiDirectoryFactory; import com.intellij.psi.util.PsiUtilBase; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.AutoScrollFromSourceHandler; import com.intellij.ui.AutoScrollToSourceHandler; import com.intellij.ui.GuiUtils; import com.intellij.ui.components.JBList; import com.intellij.ui.content.*; import com.intellij.ui.switcher.QuickActionProvider; import com.intellij.util.ArrayUtil; import com.intellij.util.IJSwingUtilities; import com.intellij.util.PlatformIcons; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jdom.Attribute; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import java.awt.*; import java.util.*; import java.util.List; @State( name="ProjectView", storages= { @Storage( file = StoragePathMacros.WORKSPACE_FILE )} ) public class ProjectViewImpl extends ProjectView implements PersistentStateComponent, Disposable, QuickActionProvider, BusyObject { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.projectView.impl.ProjectViewImpl"); private static final Key ID_KEY = Key.create("pane-id"); private static final Key SUB_ID_KEY = Key.create("pane-sub-id"); private final CopyPasteDelegator myCopyPasteDelegator; private boolean isInitialized; private boolean myExtensionsLoaded = false; private final Project myProject; // + options private final Map myFlattenPackages = new THashMap(); private static final boolean ourFlattenPackagesDefaults = false; private final Map myShowMembers = new THashMap(); private static final boolean ourShowMembersDefaults = false; private final Map mySortByType = new THashMap(); private static final boolean ourSortByTypeDefaults = false; private final Map myShowModules = new THashMap(); private static final boolean ourShowModulesDefaults = true; private final Map myShowLibraryContents = new THashMap(); private static final boolean ourShowLibraryContentsDefaults = true; private final Map myHideEmptyPackages = new THashMap(); private static final boolean ourHideEmptyPackagesDefaults = true; private final Map myAbbreviatePackageNames = new THashMap(); private static final boolean ourAbbreviatePackagesDefaults = false; private final Map myAutoscrollToSource = new THashMap(); private final Map myAutoscrollFromSource = new THashMap(); private static final boolean ourAutoscrollFromSourceDefaults = false; private static final boolean ourShowStructureDefaults = false; private String myCurrentViewId; private String myCurrentViewSubId; // - options private final AutoScrollToSourceHandler myAutoScrollToSourceHandler; private final MyAutoScrollFromSourceHandler myAutoScrollFromSourceHandler; private final IdeView myIdeView = new MyIdeView(); private final MyDeletePSIElementProvider myDeletePSIElementProvider = new MyDeletePSIElementProvider(); private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider(); private SimpleToolWindowPanel myPanel; private final Map myId2Pane = new LinkedHashMap(); private final Collection myUninitializedPanes = new THashSet(); static final DataKey DATA_KEY = DataKey.create("com.intellij.ide.projectView.impl.ProjectViewImpl"); private DefaultActionGroup myActionGroup; private String mySavedPaneId = ProjectViewPane.ID; private String mySavedPaneSubId; //private static final Icon COMPACT_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/compactEmptyPackages.png"); //private static final Icon HIDE_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/hideEmptyPackages.png"); @NonNls private static final String ELEMENT_NAVIGATOR = "navigator"; @NonNls private static final String ELEMENT_PANES = "panes"; @NonNls private static final String ELEMENT_PANE = "pane"; @NonNls private static final String ATTRIBUTE_CURRENT_VIEW = "currentView"; @NonNls private static final String ATTRIBUTE_CURRENT_SUBVIEW = "currentSubView"; @NonNls private static final String ELEMENT_FLATTEN_PACKAGES = "flattenPackages"; @NonNls private static final String ELEMENT_SHOW_MEMBERS = "showMembers"; @NonNls private static final String ELEMENT_SHOW_MODULES = "showModules"; @NonNls private static final String ELEMENT_SHOW_LIBRARY_CONTENTS = "showLibraryContents"; @NonNls private static final String ELEMENT_HIDE_EMPTY_PACKAGES = "hideEmptyPackages"; @NonNls private static final String ELEMENT_ABBREVIATE_PACKAGE_NAMES = "abbreviatePackageNames"; @NonNls private static final String ELEMENT_AUTOSCROLL_TO_SOURCE = "autoscrollToSource"; @NonNls private static final String ELEMENT_AUTOSCROLL_FROM_SOURCE = "autoscrollFromSource"; @NonNls private static final String ELEMENT_SORT_BY_TYPE = "sortByType"; @NonNls private static final String ELEMENT_FOLDERS_ALWAYS_ON_TOP = "foldersAlwaysOnTop"; private static final String ATTRIBUTE_ID = "id"; private JPanel myViewContentPanel; private static final Comparator PANE_WEIGHT_COMPARATOR = new Comparator() { @Override public int compare(final AbstractProjectViewPane o1, final AbstractProjectViewPane o2) { return o1.getWeight() - o2.getWeight(); } }; private final FileEditorManager myFileEditorManager; private final MyPanel myDataProvider; private final SplitterProportionsData splitterProportions = new SplitterProportionsDataImpl(); private final MessageBusConnection myConnection; private final Map myUninitializedPaneState = new HashMap(); private final Map mySelectInTargets = new LinkedHashMap(); private ContentManager myContentManager; private boolean myFoldersAlwaysOnTop = true; public ProjectViewImpl(Project project, final FileEditorManager fileEditorManager, final ToolWindowManagerEx toolWindowManager) { myProject = project; constructUi(); Disposer.register(myProject, this); myFileEditorManager = fileEditorManager; myConnection = project.getMessageBus().connect(); myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { @Override public void rootsChanged(ModuleRootEvent event) { refresh(); } }); myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(); myDataProvider = new MyPanel(); myDataProvider.add(myPanel, BorderLayout.CENTER); myCopyPasteDelegator = new CopyPasteDelegator(myProject, myPanel) { @Override @NotNull protected PsiElement[] getSelectedElements() { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); return viewPane == null ? PsiElement.EMPTY_ARRAY : viewPane.getSelectedPSIElements(); } }; myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { @Override protected boolean isAutoScrollMode() { return isAutoscrollToSource(myCurrentViewId); } @Override protected void setAutoScrollMode(boolean state) { setAutoscrollToSource(state, myCurrentViewId); } }; toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter(){ private boolean toolWindowVisible; @Override public void stateChanged() { ToolWindow window = toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW); if (window == null) return; if (window.isVisible() && !toolWindowVisible) { String id = getCurrentViewId(); if (isAutoscrollToSource(id)) { AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); if (currentProjectViewPane != null) { myAutoScrollToSourceHandler.onMouseClicked(currentProjectViewPane.getTree()); } } if (isAutoscrollFromSource(id)) { myAutoScrollFromSourceHandler.setAutoScrollEnabled(true); } } toolWindowVisible = window.isVisible(); } }); } private void constructUi() { myViewContentPanel = new JPanel(); myPanel = new SimpleToolWindowPanel(true).setProvideQuickActions(false); myPanel.setContent(myViewContentPanel); } @Override public String getName() { return "Project"; } @Override public List getActions(boolean originalProvider) { ArrayList result = new ArrayList(); DefaultActionGroup views = new DefaultActionGroup("Change View", true); boolean lastWasHeader = false; boolean lastHeaderHadKids = false; for (int i = 0; i < myContentManager.getContentCount(); i++) { Content each = myContentManager.getContent(i); if (each != null) { if (each.getUserData(SUB_ID_KEY) == null) { if (lastHeaderHadKids) { views.add(new Separator()); } else { if (i + 1 < myContentManager.getContentCount()) { Content next = myContentManager.getContent(i + 1); if (next != null) { if (next.getUserData(SUB_ID_KEY) != null) { views.add(new Separator()); } } } } } else { lastHeaderHadKids = true; } lastWasHeader = each.getUserData(SUB_ID_KEY) == null; views.add(new ChangeViewAction(each.getUserData(ID_KEY), each.getUserData(SUB_ID_KEY))); } } result.add(views); result.add(new Separator()); ArrayList secondary = new ArrayList(); if (myActionGroup != null) { AnAction[] kids = myActionGroup.getChildren(null); for (AnAction each : kids) { if (myActionGroup.isPrimary(each)) { result.add(each); } else { secondary.add(each); } } } result.add(new Separator()); result.addAll(secondary); return result; } private class ChangeViewAction extends AnAction { private final String myId; private final String mySubId; private ChangeViewAction(String id, String subId) { myId = id; mySubId = subId; } @Override public void update(AnActionEvent e) { AbstractProjectViewPane pane = getProjectViewPaneById(myId); e.getPresentation().setText(pane.getTitle() + (mySubId != null ? (" - " + pane.getPresentableSubIdName(mySubId)) : "")); } @Override public void actionPerformed(AnActionEvent e) { changeView(myId, mySubId); } } @Override public boolean isCycleRoot() { return false; } @Override public synchronized void addProjectPane(final AbstractProjectViewPane pane) { myUninitializedPanes.add(pane); SelectInTarget selectInTarget = pane.createSelectInTarget(); if (selectInTarget != null) { mySelectInTargets.put(pane.getId(), selectInTarget); } if (isInitialized) { doAddUninitializedPanes(); } } @Override public synchronized void removeProjectPane(AbstractProjectViewPane pane) { myUninitializedPanes.remove(pane); //assume we are completely initialized here String idToRemove = pane.getId(); if (!myId2Pane.containsKey(idToRemove)) return; pane.removeTreeChangeListener(); for (int i = myContentManager.getContentCount() - 1; i >= 0; i--) { Content content = myContentManager.getContent(i); String id = content != null ? content.getUserData(ID_KEY) : null; if (id != null && id.equals(idToRemove)) { myContentManager.removeContent(content, true); } } myId2Pane.remove(idToRemove); mySelectInTargets.remove(idToRemove); viewSelectionChanged(); } private synchronized void doAddUninitializedPanes() { for (AbstractProjectViewPane pane : myUninitializedPanes) { doAddPane(pane); } final Content[] contents = myContentManager.getContents(); for (int i = 1; i < contents.length; i++) { Content content = contents[i]; Content prev = contents[i - 1]; if (!StringUtil.equals(content.getUserData(ID_KEY), prev.getUserData(ID_KEY)) && prev.getUserData(SUB_ID_KEY) != null && content.getSeparator() == null) { content.setSeparator(""); } } String selectID = null; String selectSubID = null; // try to find saved selected view... for (Content content : contents) { final String id = content.getUserData(ID_KEY); final String subId = content.getUserData(SUB_ID_KEY); if (id != null && id.equals(mySavedPaneId) && StringUtil.equals(subId, mySavedPaneSubId)) { selectID = id; selectSubID = subId; break; } } // saved view not found (plugin disabled, ID changed etc.) - select first available view... if (selectID == null && contents.length > 0) { Content content = contents[0]; selectID = content.getUserData(ID_KEY); selectSubID = content.getUserData(SUB_ID_KEY); } if (selectID != null) { changeView(selectID, selectSubID); mySavedPaneId = null; mySavedPaneSubId = null; } myUninitializedPanes.clear(); } private void doAddPane(final AbstractProjectViewPane newPane) { int index; final ContentManager manager = myContentManager; for (index = 0; index < manager.getContentCount(); index++) { Content content = manager.getContent(index); String id = content.getUserData(ID_KEY); AbstractProjectViewPane pane = myId2Pane.get(id); int comp = PANE_WEIGHT_COMPARATOR.compare(pane, newPane); if (comp == 0) { System.out.println("here"); } LOG.assertTrue(comp != 0); if (comp > 0) { break; } } final String id = newPane.getId(); myId2Pane.put(id, newPane); String[] subIds = newPane.getSubIds(); subIds = subIds.length == 0 ? new String[]{null} : subIds; boolean first = true; for (String subId : subIds) { final String title = subId != null ? newPane.getPresentableSubIdName(subId) : newPane.getTitle(); final Content content = myContentManager.getFactory().createContent(getComponent(), title, false); content.setTabName(title); content.putUserData(ID_KEY, id); content.putUserData(SUB_ID_KEY, subId); content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); content.setIcon(newPane.getIcon()); content.setPopupIcon(subId != null ? AllIcons.General.Bullet : newPane.getIcon()); content.setPreferredFocusedComponent(new Computable() { @Override public JComponent compute() { final AbstractProjectViewPane current = getCurrentProjectViewPane(); return current != null ? current.getComponentToFocus() : null; } }); content.setBusyObject(this); if (first && subId != null) { content.setSeparator(newPane.getTitle()); } manager.addContent(content, index++); first = false; } Disposer.register(this, newPane); } private void showPane(AbstractProjectViewPane newPane) { AbstractProjectViewPane currentPane = getCurrentProjectViewPane(); PsiElement selectedPsiElement = null; if (currentPane != null) { if (currentPane != newPane) { currentPane.saveExpandedPaths(); } final PsiElement[] elements = currentPane.getSelectedPSIElements(); if (elements.length > 0) { selectedPsiElement = elements[0]; } } myViewContentPanel.removeAll(); JComponent component = newPane.createComponent(); UIUtil.removeScrollBorder(component); myViewContentPanel.setLayout(new BorderLayout()); myViewContentPanel.add(component, BorderLayout.CENTER); myCurrentViewId = newPane.getId(); String newSubId = myCurrentViewSubId = newPane.getSubId(); myViewContentPanel.revalidate(); myViewContentPanel.repaint(); createToolbarActions(); updateTitleActions(); myAutoScrollToSourceHandler.install(newPane.myTree); IdeFocusManager.getInstance(myProject).requestFocus(newPane.getComponentToFocus(), false); newPane.restoreExpandedPaths(); if (selectedPsiElement != null) { final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(selectedPsiElement); if (virtualFile != null && ((ProjectViewSelectInTarget)newPane.createSelectInTarget()).isSubIdSelectable(newSubId, new SelectInContext() { @Override @NotNull public Project getProject() { return myProject; } @Override @NotNull public VirtualFile getVirtualFile() { return virtualFile; } @Override public Object getSelectorInFile() { return null; } @Override public FileEditorProvider getFileEditorProvider() { return null; } })) { newPane.select(selectedPsiElement, virtualFile, true); } } myAutoScrollToSourceHandler.onMouseClicked(newPane.myTree); } private void updateTitleActions() { final ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow("Project"); if (!(window instanceof ToolWindowEx)) return; ScrollFromSourceAction scrollAction = null; CollapseAllToolbarAction collapseAction = null; for (AnAction action : myActionGroup.getChildren(null)) { if (action instanceof ScrollFromSourceAction) { scrollAction = (ScrollFromSourceAction)action; myActionGroup.remove(scrollAction); } if (action instanceof CollapseAllToolbarAction) { collapseAction = (CollapseAllToolbarAction)action; collapseAction.getTemplatePresentation().setIcon(AllIcons.General.CollapseAll); collapseAction.getTemplatePresentation().setHoveredIcon(AllIcons.General.CollapseAllHover); myActionGroup.remove(collapseAction); } } ((ToolWindowEx)window).setTitleActions(new AnAction[] {scrollAction, collapseAction}); } // public for tests public synchronized void setupImpl(final ToolWindow toolWindow) { setupImpl(toolWindow, true); } // public for tests public synchronized void setupImpl(final ToolWindow toolWindow, final boolean loadPaneExtensions) { myActionGroup = new DefaultActionGroup(); myAutoScrollFromSourceHandler.install(); if (toolWindow != null) { myContentManager = toolWindow.getContentManager(); if (!ApplicationManager.getApplication().isUnitTestMode()) { toolWindow.setContentUiType(ToolWindowContentUiType.COMBO, null); ((ToolWindowEx)toolWindow).setAdditionalGearActions(myActionGroup); toolWindow.getComponent().putClientProperty(ToolWindowContentUi.HIDE_ID_LABEL, "true"); } } else { final ContentFactory contentFactory = ServiceManager.getService(ContentFactory.class); myContentManager = contentFactory.createContentManager(false, myProject); } GuiUtils.replaceJSplitPaneWithIDEASplitter(myPanel); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { splitterProportions.restoreSplitterProportions(myPanel); } }); if (loadPaneExtensions) { ensurePanesLoaded(); } isInitialized = true; doAddUninitializedPanes(); myContentManager.addContentManagerListener(new ContentManagerAdapter() { @Override public void selectionChanged(ContentManagerEvent event) { if (event.getOperation() == ContentManagerEvent.ContentOperation.add) { viewSelectionChanged(); } } }); } private void ensurePanesLoaded() { if (myExtensionsLoaded) return; myExtensionsLoaded = true; for(AbstractProjectViewPane pane: Extensions.getExtensions(AbstractProjectViewPane.EP_NAME, myProject)) { if (myUninitializedPaneState.containsKey(pane.getId())) { try { pane.readExternal(myUninitializedPaneState.get(pane.getId())); } catch (InvalidDataException e) { // ignore } myUninitializedPaneState.remove(pane.getId()); } if (pane.isInitiallyVisible() && !myId2Pane.containsKey(pane.getId())) { addProjectPane(pane); } Disposer.register(this, pane); } } private boolean viewSelectionChanged() { Content content = myContentManager.getSelectedContent(); if (content == null) return false; final String id = content.getUserData(ID_KEY); String subId = content.getUserData(SUB_ID_KEY); if (content.equals(Pair.create(myCurrentViewId, myCurrentViewSubId))) return false; final AbstractProjectViewPane newPane = getProjectViewPaneById(id); if (newPane == null) return false; newPane.setSubId(subId); showPane(newPane); if (isAutoscrollFromSource(id)) { myAutoScrollFromSourceHandler.scrollFromSource(); } return true; } private void createToolbarActions() { myActionGroup.removeAll(); if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { myActionGroup.addAction(new PaneOptionAction(myFlattenPackages, IdeBundle.message("action.flatten.packages"), IdeBundle.message("action.flatten.packages"), PlatformIcons.FLATTEN_PACKAGES_ICON, ourFlattenPackagesDefaults) { @Override public void setSelected(AnActionEvent event, boolean flag) { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); final SelectionInfo selectionInfo = SelectionInfo.create(viewPane); super.setSelected(event, flag); selectionInfo.apply(viewPane); } }).setAsSecondary(true); } class FlattenPackagesDependableAction extends PaneOptionAction { FlattenPackagesDependableAction(Map optionsMap, final String text, final String description, final Icon icon, boolean optionDefaultValue) { super(optionsMap, text, description, icon, optionDefaultValue); } @Override public void update(AnActionEvent e) { super.update(e); final Presentation presentation = e.getPresentation(); presentation.setVisible(isFlattenPackages(myCurrentViewId)); } } if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) { myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true); } if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) { myActionGroup.addAction(new FlattenPackagesDependableAction(myAbbreviatePackageNames, IdeBundle.message("action.abbreviate.qualified.package.names"), IdeBundle.message("action.abbreviate.qualified.package.names"), AllIcons.ObjectBrowser.AbbreviatePackageNames, ourAbbreviatePackagesDefaults) { @Override public boolean isSelected(AnActionEvent event) { return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId); } @Override public void update(AnActionEvent e) { super.update(e); if (ScopeViewPane.ID.equals(myCurrentViewId)) { e.getPresentation().setEnabled(false); } } }).setAsSecondary(true); } if (isShowMembersOptionSupported()) { myActionGroup.addAction(new PaneOptionAction(myShowMembers, IdeBundle.message("action.show.members"), IdeBundle.message("action.show.hide.members"), AllIcons.ObjectBrowser.ShowMembers, ourShowMembersDefaults)) .setAsSecondary(true); } myActionGroup.addAction(myAutoScrollToSourceHandler.createToggleAction()).setAsSecondary(true); myActionGroup.addAction(myAutoScrollFromSourceHandler.createToggleAction()).setAsSecondary(true); myActionGroup.addAction(new SortByTypeAction()).setAsSecondary(true); myActionGroup.addAction(new FoldersAlwaysOnTopAction()).setAsSecondary(true); if (!myAutoScrollFromSourceHandler.isAutoScrollEnabled()) { myActionGroup.addAction(new ScrollFromSourceAction()); } AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() { @Override public void expandAll() { } @Override public boolean canExpand() { return false; } @Override public void collapseAll() { AbstractProjectViewPane pane = getCurrentProjectViewPane(); JTree tree = pane.myTree; if (tree != null) { TreeUtil.collapseAll(tree, 0); } } @Override public boolean canCollapse() { return true; } }, getComponent()); myActionGroup.add(collapseAllAction); getCurrentProjectViewPane().addToolbarActions(myActionGroup); } protected boolean isShowMembersOptionSupported() { return true; } @Override public AbstractProjectViewPane getProjectViewPaneById(String id) { if (!ApplicationManager.getApplication().isUnitTestMode()) { // most tests don't need all panes to be loaded ensurePanesLoaded(); } final AbstractProjectViewPane pane = myId2Pane.get(id); if (pane != null) { return pane; } for (AbstractProjectViewPane viewPane : myUninitializedPanes) { if (viewPane.getId().equals(id)) { return viewPane; } } return null; } @Override public AbstractProjectViewPane getCurrentProjectViewPane() { return getProjectViewPaneById(myCurrentViewId); } @Override public void refresh() { AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); if (currentProjectViewPane != null) { // may be null for e.g. default project currentProjectViewPane.updateFromRoot(false); } } @Override public void select(final Object element, VirtualFile file, boolean requestFocus) { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane != null) { viewPane.select(element, file, requestFocus); } } @Override public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane != null && viewPane instanceof AbstractProjectViewPSIPane) { return ((AbstractProjectViewPSIPane) viewPane).selectCB(element, file, requestFocus); } else { select(element, file, requestFocus); return new ActionCallback.Done(); } } @Override public void dispose() { myConnection.disconnect(); } @Override public JComponent getComponent() { return myDataProvider; } @Override public String getCurrentViewId() { return myCurrentViewId; } @Override public PsiElement getParentOfCurrentSelection() { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane == null) { return null; } TreePath path = viewPane.getSelectedPath(); if (path == null) { return null; } path = path.getParentPath(); if (path == null) { return null; } DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); Object userObject = node.getUserObject(); if (userObject instanceof ProjectViewNode) { ProjectViewNode descriptor = (ProjectViewNode)userObject; Object element = descriptor.getValue(); if (element instanceof PsiElement) { PsiElement psiElement = (PsiElement)element; if (!psiElement.isValid()) return null; return psiElement; } else { return null; } } return null; } private class PaneOptionAction extends ToggleAction implements DumbAware { private final Map myOptionsMap; private final boolean myOptionDefaultValue; PaneOptionAction(Map optionsMap, final String text, final String description, final Icon icon, boolean optionDefaultValue) { super(text, description, icon); myOptionsMap = optionsMap; myOptionDefaultValue = optionDefaultValue; } @Override public boolean isSelected(AnActionEvent event) { return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue); } @Override public void setSelected(AnActionEvent event, boolean flag) { setPaneOption(myOptionsMap, flag, myCurrentViewId, true); } } @Override public void changeView() { final List views = new ArrayList(myId2Pane.values()); views.remove(getCurrentProjectViewPane()); Collections.sort(views, PANE_WEIGHT_COMPARATOR); final JList list = new JBList(ArrayUtil.toObjectArray(views)); list.setCellRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); AbstractProjectViewPane pane = (AbstractProjectViewPane)value; setText(pane.getTitle()); return this; } }); if (!views.isEmpty()) { list.setSelectedValue(views.get(0), true); } Runnable runnable = new Runnable() { @Override public void run() { if (list.getSelectedIndex() < 0) return; AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue(); changeView(pane.getId()); } }; new PopupChooserBuilder(list). setTitle(IdeBundle.message("title.popup.views")). setItemChoosenCallback(runnable). createPopup().showInCenterOf(getComponent()); } @Override public void changeView(@NotNull String viewId) { changeView(viewId, null); } @Override public void changeView(@NotNull String viewId, @Nullable String subId) { AbstractProjectViewPane pane = getProjectViewPaneById(viewId); LOG.assertTrue(pane != null, "Project view pane not found: " + viewId + "; subId:" + subId); if (!viewId.equals(getCurrentViewId()) || subId != null && !subId.equals(pane.getSubId())) { for (Content content : myContentManager.getContents()) { if (viewId.equals(content.getUserData(ID_KEY)) && StringUtil.equals(subId, content.getUserData(SUB_ID_KEY))) { myContentManager.setSelectedContent(content); break; } } viewSelectionChanged(); } } private final class MyDeletePSIElementProvider implements DeleteProvider { @Override public boolean canDeleteElement(@NotNull DataContext dataContext) { final PsiElement[] elements = getElementsToDelete(); return DeleteHandler.shouldEnableDeleteAction(elements); } @Override public void deleteElement(@NotNull DataContext dataContext) { List allElements = Arrays.asList(getElementsToDelete()); List validElements = new ArrayList(); for (PsiElement psiElement : allElements) { if (psiElement != null && psiElement.isValid()) validElements.add(psiElement); } final PsiElement[] elements = PsiUtilCore.toPsiElementArray(validElements); LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting")); try { DeleteHandler.deletePsiElement(elements, myProject); } finally { a.finish(); } } private PsiElement[] getElementsToDelete() { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); PsiElement[] elements = viewPane.getSelectedPSIElements(); for (int idx = 0; idx < elements.length; idx++) { final PsiElement element = elements[idx]; if (element instanceof PsiDirectory) { PsiDirectory directory = (PsiDirectory)element; final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject); if (isHideEmptyMiddlePackages(viewPane.getId()) && directory.getChildren().length == 0 && !directoryHelper.skipDirectory(directory)) { while (true) { PsiDirectory parent = directory.getParentDirectory(); if (parent == null) break; if (directoryHelper.skipDirectory(parent) || PsiDirectoryFactory.getInstance(myProject).getQualifiedName(parent, false).length() == 0) break; PsiElement[] children = parent.getChildren(); if (children.length == 0 || children.length == 1 && children[0] == directory) { directory = parent; } else { break; } } elements[idx] = directory; } final VirtualFile virtualFile = directory.getVirtualFile(); final String path = virtualFile.getPath(); if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length())); if (vFile != null) { final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile); if (psiFile != null) { elements[idx] = psiFile; } } } } } return elements; } } private final class MyPanel extends JPanel implements DataProvider { MyPanel() { super(new BorderLayout()); } @Nullable private Object getSelectedNodeElement() { final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); if (currentProjectViewPane == null) { // can happen if not initialized yet return null; } DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode(); if (node == null) { return null; } Object userObject = node.getUserObject(); if (userObject instanceof AbstractTreeNode) { return ((AbstractTreeNode)userObject).getValue(); } if (!(userObject instanceof NodeDescriptor)) { return null; } return ((NodeDescriptor)userObject).getElement(); } @Override public Object getData(String dataId) { final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); if (currentProjectViewPane != null) { final Object paneSpecificData = currentProjectViewPane.getData(dataId); if (paneSpecificData != null) return paneSpecificData; } if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { if (currentProjectViewPane == null) return null; final PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements(); return elements.length == 1 ? elements[0] : null; } if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { if (currentProjectViewPane == null) { return null; } PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements(); return elements.length == 0 ? null : elements; } if (LangDataKeys.MODULE.is(dataId)) { VirtualFile[] virtualFiles = (VirtualFile[])getData(CommonDataKeys.VIRTUAL_FILE_ARRAY.getName()); if (virtualFiles == null || virtualFiles.length <= 1) return null; final Set modules = new HashSet(); for (VirtualFile virtualFile : virtualFiles) { modules.add(ModuleUtil.findModuleForFile(virtualFile, myProject)); } return modules.size() == 1 ? modules.iterator().next() : null; } if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) { return null; } if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) { return myCopyPasteDelegator.getCutProvider(); } if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) { return myCopyPasteDelegator.getCopyProvider(); } if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) { return myCopyPasteDelegator.getPasteProvider(); } if (LangDataKeys.IDE_VIEW.is(dataId)) { return myIdeView; } if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { final Module[] modules = getSelectedModules(); if (modules != null) { return myDeleteModuleProvider; } final LibraryOrderEntry orderEntry = getSelectedLibrary(); if (orderEntry != null) { return new DeleteProvider() { @Override public void deleteElement(@NotNull DataContext dataContext) { detachLibrary(orderEntry, myProject); } @Override public boolean canDeleteElement(@NotNull DataContext dataContext) { return true; } }; } return myDeletePSIElementProvider; } if (PlatformDataKeys.HELP_ID.is(dataId)) { return HelpID.PROJECT_VIEWS; } if (ProjectViewImpl.DATA_KEY.is(dataId)) { return ProjectViewImpl.this; } if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) { Object selected = getSelectedNodeElement(); return selected instanceof Project ? selected : null; } if (LangDataKeys.MODULE_CONTEXT.is(dataId)) { Object selected = getSelectedNodeElement(); if (selected instanceof Module) { return !((Module)selected).isDisposed() ? selected : null; } else if (selected instanceof PsiDirectory) { return moduleBySingleContentRoot(((PsiDirectory)selected).getVirtualFile()); } else if (selected instanceof VirtualFile) { return moduleBySingleContentRoot((VirtualFile)selected); } else { return null; } } if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) { return getSelectedModules(); } if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) { final List selectedElements = getSelectedElements(ModuleGroup.class); return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]); } if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) { final List selectedElements = getSelectedElements(LibraryGroupElement.class); return selectedElements.isEmpty() ? null : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]); } if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) { final List selectedElements = getSelectedElements(NamedLibraryElement.class); return selectedElements.isEmpty() ? null : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]); } if (QuickActionProvider.KEY.is(dataId)) { return ProjectViewImpl.this; } return null; } @Nullable private LibraryOrderEntry getSelectedLibrary() { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); DefaultMutableTreeNode node = viewPane != null ? viewPane.getSelectedNode() : null; if (node == null) return null; DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent(); if (parent == null) return null; Object userObject = parent.getUserObject(); if (userObject instanceof LibraryGroupNode) { userObject = node.getUserObject(); if (userObject instanceof NamedLibraryElementNode) { NamedLibraryElement element = ((NamedLibraryElementNode)userObject).getValue(); OrderEntry orderEntry = element.getOrderEntry(); return orderEntry instanceof LibraryOrderEntry ? (LibraryOrderEntry)orderEntry : null; } PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue(); VirtualFile virtualFile = directory.getVirtualFile(); Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue(); if (module == null) return null; ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex(); OrderEntry entry = index.getOrderEntryForFile(virtualFile); if (entry instanceof LibraryOrderEntry) { return (LibraryOrderEntry)entry; } } return null; } private void detachLibrary(final LibraryOrderEntry orderEntry, final Project project) { final Module module = orderEntry.getOwnerModule(); String message = IdeBundle.message("detach.library.from.module", orderEntry.getPresentableName(), module.getName()); String title = IdeBundle.message("detach.library"); int ret = Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon()); if (ret != Messages.OK) return; CommandProcessor.getInstance().executeCommand(module.getProject(), new Runnable() { @Override public void run() { final Runnable action = new Runnable() { @Override public void run() { ModuleRootManager rootManager = ModuleRootManager.getInstance(module); OrderEntry[] orderEntries = rootManager.getOrderEntries(); ModifiableRootModel model = rootManager.getModifiableModel(); OrderEntry[] modifiableEntries = model.getOrderEntries(); for (int i = 0; i < orderEntries.length; i++) { OrderEntry entry = orderEntries[i]; if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == orderEntry.getLibrary()) { model.removeOrderEntry(modifiableEntries[i]); } } model.commit(); } }; ApplicationManager.getApplication().runWriteAction(action); } }, title, null); } @Nullable private Module[] getSelectedModules() { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane == null) return null; final Object[] elements = viewPane.getSelectedElements(); ArrayList result = new ArrayList(); for (Object element : elements) { if (element instanceof Module) { final Module module = (Module)element; if (!module.isDisposed()) { result.add(module); } } else if (element instanceof ModuleGroup) { Collection modules = ((ModuleGroup)element).modulesInGroup(myProject, true); result.addAll(modules); } else if (element instanceof PsiDirectory) { Module module = moduleBySingleContentRoot(((PsiDirectory)element).getVirtualFile()); if (module != null) result.add(module); } else if (element instanceof VirtualFile) { Module module = moduleBySingleContentRoot((VirtualFile)element); if (module != null) result.add(module); } } if (result.isEmpty()) { return null; } else { return result.toArray(new Module[result.size()]); } } } /** Project view has the same node for module and its single content root * => MODULE_CONTEXT data key should return the module when its content root is selected * When there are multiple content roots, they have different nodes under the module node * => MODULE_CONTEXT should be only available for the module node * otherwise VirtualFileArrayRule will return all module's content roots when just one of them is selected */ @Nullable private Module moduleBySingleContentRoot(VirtualFile file) { if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) { Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file); if (module != null && !module.isDisposed() && ModuleRootManager.getInstance(module).getContentRoots().length == 1) { return module; } } return null; } private List getSelectedElements(Class klass) { ArrayList result = new ArrayList(); final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane == null) return result; final Object[] elements = viewPane.getSelectedElements(); for (Object element : elements) { //element still valid if (element != null && klass.isAssignableFrom(element.getClass())) { result.add((T)element); } } return result; } private final class MyIdeView implements IdeView { @Override public void selectElement(PsiElement element) { selectPsiElement(element, false); boolean requestFocus = true; if (element != null) { final boolean isDirectory = element instanceof PsiDirectory; if (!isDirectory) { FileEditor editor = EditorHelper.openInEditor(element, false); if (editor != null) { ToolWindowManager.getInstance(myProject).activateEditorComponent(); requestFocus = false; } } } if (requestFocus) { selectPsiElement(element, true); } } @Override public PsiDirectory[] getDirectories() { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane != null) { return viewPane.getSelectedDirectories(); } return PsiDirectory.EMPTY_ARRAY; } @Override public PsiDirectory getOrChooseDirectory() { return DirectoryChooserUtil.getOrChooseDirectory(this); } } @Override public void selectPsiElement(PsiElement element, boolean requestFocus) { if (element == null) return; VirtualFile virtualFile = PsiUtilBase.getVirtualFile(element); select(element, virtualFile, requestFocus); } private static void readOption(Element node, Map options) { if (node == null) return; List attributes = node.getAttributes(); for (final Object attribute1 : attributes) { Attribute attribute = (Attribute)attribute1; options.put(attribute.getName(), Boolean.TRUE.toString().equals(attribute.getValue()) ? Boolean.TRUE : Boolean.FALSE); } } private static void writeOption(Element parentNode, Map optionsForPanes, String optionName) { Element e = new Element(optionName); for (Map.Entry entry : optionsForPanes.entrySet()) { final String key = entry.getKey(); if (key != null) { //SCR48267 e.setAttribute(key, Boolean.toString(entry.getValue().booleanValue())); } } parentNode.addContent(e); } @Override public void loadState(Element parentNode) { Element navigatorElement = parentNode.getChild(ELEMENT_NAVIGATOR); if (navigatorElement != null) { mySavedPaneId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_VIEW); mySavedPaneSubId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_SUBVIEW); if (mySavedPaneId == null) { mySavedPaneId = ProjectViewPane.ID; mySavedPaneSubId = null; } readOption(navigatorElement.getChild(ELEMENT_FLATTEN_PACKAGES), myFlattenPackages); readOption(navigatorElement.getChild(ELEMENT_SHOW_MEMBERS), myShowMembers); readOption(navigatorElement.getChild(ELEMENT_SHOW_MODULES), myShowModules); readOption(navigatorElement.getChild(ELEMENT_SHOW_LIBRARY_CONTENTS), myShowLibraryContents); readOption(navigatorElement.getChild(ELEMENT_HIDE_EMPTY_PACKAGES), myHideEmptyPackages); readOption(navigatorElement.getChild(ELEMENT_ABBREVIATE_PACKAGE_NAMES), myAbbreviatePackageNames); readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_TO_SOURCE), myAutoscrollToSource); readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_FROM_SOURCE), myAutoscrollFromSource); readOption(navigatorElement.getChild(ELEMENT_SORT_BY_TYPE), mySortByType); try { splitterProportions.readExternal(navigatorElement); } catch (InvalidDataException e) { // ignore } } Element panesElement = parentNode.getChild(ELEMENT_PANES); if (panesElement != null) { readPaneState(panesElement); } } private void readPaneState(Element panesElement) { @SuppressWarnings({"unchecked"}) final List paneElements = panesElement.getChildren(ELEMENT_PANE); for (Element paneElement : paneElements) { String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID); final AbstractProjectViewPane pane = myId2Pane.get(paneId); if (pane != null) { try { pane.readExternal(paneElement); } catch (InvalidDataException e) { // ignore } } else { myUninitializedPaneState.put(paneId, paneElement); } } } @Override public Element getState() { Element parentNode = new Element("projectView"); Element navigatorElement = new Element(ELEMENT_NAVIGATOR); AbstractProjectViewPane currentPane = getCurrentProjectViewPane(); if (currentPane != null) { navigatorElement.setAttribute(ATTRIBUTE_CURRENT_VIEW, currentPane.getId()); String subId = currentPane.getSubId(); if (subId != null) { navigatorElement.setAttribute(ATTRIBUTE_CURRENT_SUBVIEW, subId); } } writeOption(navigatorElement, myFlattenPackages, ELEMENT_FLATTEN_PACKAGES); writeOption(navigatorElement, myShowMembers, ELEMENT_SHOW_MEMBERS); writeOption(navigatorElement, myShowModules, ELEMENT_SHOW_MODULES); writeOption(navigatorElement, myShowLibraryContents, ELEMENT_SHOW_LIBRARY_CONTENTS); writeOption(navigatorElement, myHideEmptyPackages, ELEMENT_HIDE_EMPTY_PACKAGES); writeOption(navigatorElement, myAbbreviatePackageNames, ELEMENT_ABBREVIATE_PACKAGE_NAMES); writeOption(navigatorElement, myAutoscrollToSource, ELEMENT_AUTOSCROLL_TO_SOURCE); writeOption(navigatorElement, myAutoscrollFromSource, ELEMENT_AUTOSCROLL_FROM_SOURCE); writeOption(navigatorElement, mySortByType, ELEMENT_SORT_BY_TYPE); splitterProportions.saveSplitterProportions(myPanel); try { splitterProportions.writeExternal(navigatorElement); } catch (WriteExternalException e) { // ignore } parentNode.addContent(navigatorElement); Element panesElement = new Element(ELEMENT_PANES); writePaneState(panesElement); parentNode.addContent(panesElement); return parentNode; } private void writePaneState(Element panesElement) { for (AbstractProjectViewPane pane : myId2Pane.values()) { Element paneElement = new Element(ELEMENT_PANE); paneElement.setAttribute(ATTRIBUTE_ID, pane.getId()); try { pane.writeExternal(paneElement); } catch (WriteExternalException e) { continue; } panesElement.addContent(paneElement); } for (Element element : myUninitializedPaneState.values()) { panesElement.addContent((Element) element.clone()); } } @Override public boolean isAutoscrollToSource(String paneId) { return getPaneOptionValue(myAutoscrollToSource, paneId, UISettings.getInstance().DEFAULT_AUTOSCROLL_TO_SOURCE); } public void setAutoscrollToSource(boolean autoscrollMode, String paneId) { myAutoscrollToSource.put(paneId, autoscrollMode ? Boolean.TRUE : Boolean.FALSE); } @Override public boolean isAutoscrollFromSource(String paneId) { return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults); } public void setAutoscrollFromSource(boolean autoscrollMode, String paneId) { setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false); } @Override public boolean isFlattenPackages(String paneId) { return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults); } public void setFlattenPackages(boolean flattenPackages, String paneId) { setPaneOption(myFlattenPackages, flattenPackages, paneId, true); } public boolean isFoldersAlwaysOnTop() { return myFoldersAlwaysOnTop; } public void setFoldersAlwaysOnTop(boolean foldersAlwaysOnTop) { if (myFoldersAlwaysOnTop != foldersAlwaysOnTop) { myFoldersAlwaysOnTop = foldersAlwaysOnTop; for (AbstractProjectViewPane pane : myId2Pane.values()) { if (pane.getTree() != null) { pane.updateFromRoot(false); } } } } @Override public boolean isShowMembers(String paneId) { return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults); } public void setShowMembers(boolean showMembers, String paneId) { setPaneOption(myShowMembers, showMembers, paneId, true); } @Override public boolean isHideEmptyMiddlePackages(String paneId) { return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults); } @Override public boolean isAbbreviatePackageNames(String paneId) { return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults); } @Override public boolean isShowLibraryContents(String paneId) { return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults); } @Override public void setShowLibraryContents(boolean showLibraryContents, String paneId) { setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true); } public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) { return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true); } @Override public boolean isShowModules(String paneId) { return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults); } @Override public void setShowModules(boolean showModules, String paneId) { setPaneOption(myShowModules, showModules, paneId, true); } @Override public void setHideEmptyPackages(boolean hideEmptyPackages, String paneId) { setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true); } @Override public void setAbbreviatePackageNames(boolean abbreviatePackageNames, String paneId) { setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true); } private ActionCallback setPaneOption(Map optionsMap, boolean value, String paneId, final boolean updatePane) { optionsMap.put(paneId, value ? Boolean.TRUE : Boolean.FALSE); if (updatePane) { final AbstractProjectViewPane pane = getProjectViewPaneById(paneId); if (pane != null) { return pane.updateFromRoot(false); } } return new ActionCallback.Done(); } private static boolean getPaneOptionValue(Map optionsMap, String paneId, boolean defaultValue) { final Boolean value = optionsMap.get(paneId); return value == null ? defaultValue : value.booleanValue(); } private class HideEmptyMiddlePackagesAction extends PaneOptionAction { private HideEmptyMiddlePackagesAction() { super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults); } @Override public void setSelected(AnActionEvent event, boolean flag) { final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); final SelectionInfo selectionInfo = SelectionInfo.create(viewPane); super.setSelected(event, flag); selectionInfo.apply(viewPane); } @Override public void update(AnActionEvent e) { super.update(e); final Presentation presentation = e.getPresentation(); if (isFlattenPackages(myCurrentViewId)) { presentation.setText(IdeBundle.message("action.hide.empty.middle.packages")); presentation.setDescription(IdeBundle.message("action.show.hide.empty.middle.packages")); } else { presentation.setText(IdeBundle.message("action.compact.empty.middle.packages")); presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages")); } } } private static class SelectionInfo { private final Object[] myElements; private SelectionInfo(Object[] elements) { myElements = elements; } public void apply(final AbstractProjectViewPane viewPane) { if (viewPane == null) { return; } AbstractTreeBuilder treeBuilder = viewPane.getTreeBuilder(); JTree tree = viewPane.myTree; DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); List paths = new ArrayList(myElements.length); for (final Object element : myElements) { DefaultMutableTreeNode node = treeBuilder.getNodeForElement(element); if (node == null) { treeBuilder.buildNodeForElement(element); node = treeBuilder.getNodeForElement(element); } if (node != null) { paths.add(new TreePath(treeModel.getPathToRoot(node))); } } if (!paths.isEmpty()) { tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()])); } } public static SelectionInfo create(final AbstractProjectViewPane viewPane) { List selectedElements = Collections.emptyList(); if (viewPane != null) { final TreePath[] selectionPaths = viewPane.getSelectionPaths(); if (selectionPaths != null) { selectedElements = new ArrayList(); for (TreePath path : selectionPaths) { final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); final Object userObject = node.getUserObject(); if (userObject instanceof NodeDescriptor) { selectedElements.add(((NodeDescriptor)userObject).getElement()); } } } } return new SelectionInfo(selectedElements.toArray()); } } private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler { private MyAutoScrollFromSourceHandler() { super(ProjectViewImpl.this.myProject,ProjectViewImpl.this.myViewContentPanel, ProjectViewImpl.this); } @Override protected void selectElementFromEditor(@NotNull FileEditor fileEditor) { if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return; if (isAutoscrollFromSource(getCurrentViewId())) { if (fileEditor instanceof TextEditor) { Editor editor = ((TextEditor)fileEditor).getEditor(); selectElementAtCaretNotLosingFocus(editor); } else { final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(fileEditor); if (file != null) { final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); if (psiFile != null) { final SelectInTarget target = mySelectInTargets.get(getCurrentViewId()); if (target != null) { final MySelectInContext selectInContext = new MySelectInContext(psiFile, null) { @Override public Object getSelectorInFile() { return psiFile; } }; if (target.canSelect(selectInContext)) { target.selectIn(selectInContext, false); } } } } } } } public void scrollFromSource() { final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject); final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor(); if (selectedTextEditor != null) { selectElementAtCaret(selectedTextEditor); return; } final FileEditor[] editors = fileEditorManager.getSelectedEditors(); for (FileEditor fileEditor : editors) { if (fileEditor instanceof TextEditor) { Editor editor = ((TextEditor)fileEditor).getEditor(); selectElementAtCaret(editor); return; } } final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles(); if (selectedFiles.length > 0) { final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]); if (file != null) { scrollFromFile(file, null); } } } private void selectElementAtCaretNotLosingFocus(final Editor editor) { if (IJSwingUtilities.hasFocus(getCurrentProjectViewPane().getComponentToFocus())) return; selectElementAtCaret(editor); } private void selectElementAtCaret(Editor editor) { final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument()); if (file == null) return; scrollFromFile(file, editor); } private void scrollFromFile(PsiFile file, @Nullable Editor editor) { final MySelectInContext selectInContext = new MySelectInContext(file, editor); final SelectInTarget target = mySelectInTargets.get(getCurrentViewId()); if (target != null && target.canSelect(selectInContext)) { target.selectIn(selectInContext, false); } } @Override protected boolean isAutoScrollEnabled() { return isAutoscrollFromSource(myCurrentViewId); } @Override protected void setAutoScrollEnabled(boolean state) { setAutoscrollFromSource(state, myCurrentViewId); if (state) { final Editor editor = myFileEditorManager.getSelectedTextEditor(); if (editor != null) { selectElementAtCaretNotLosingFocus(editor); } } createToolbarActions(); updateTitleActions(); } private class MySelectInContext implements SelectInContext { private final PsiFile myPsiFile; @Nullable private final Editor myEditor; private MySelectInContext(final PsiFile psiFile, @Nullable Editor editor) { myPsiFile = psiFile; myEditor = editor; } @Override @NotNull public Project getProject() { return myProject; } private PsiFile getPsiFile() { return myPsiFile; } @Override public FileEditorProvider getFileEditorProvider() { if (myPsiFile == null) return null; return new FileEditorProvider() { @Override public FileEditor openFileEditor() { return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0]; } }; } private PsiElement getPsiElement() { PsiElement e = null; if (myEditor != null) { final int offset = myEditor.getCaretModel().getOffset(); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); e = getPsiFile().findElementAt(offset); } if (e == null) { e = getPsiFile(); } return e; } @Override @NotNull public VirtualFile getVirtualFile() { return getPsiFile().getVirtualFile(); } @Override public Object getSelectorInFile() { return getPsiElement(); } } } @Override public boolean isSortByType(String paneId) { return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults); } @Override public void setSortByType(String paneId, final boolean sortByType) { setPaneOption(mySortByType, sortByType, paneId, false); final AbstractProjectViewPane pane = getProjectViewPaneById(paneId); pane.installComparator(); } private class SortByTypeAction extends ToggleAction { private SortByTypeAction() { super(IdeBundle.message("action.sort.by.type"), IdeBundle.message("action.sort.by.type"), AllIcons.ObjectBrowser.SortByType); } @Override public boolean isSelected(AnActionEvent event) { return isSortByType(getCurrentViewId()); } @Override public void setSelected(AnActionEvent event, boolean flag) { setSortByType(getCurrentViewId(), flag); } @Override public void update(final AnActionEvent e) { super.update(e); final Presentation presentation = e.getPresentation(); presentation.setVisible(getCurrentProjectViewPane() != null); } } private class FoldersAlwaysOnTopAction extends ToggleAction { private FoldersAlwaysOnTopAction() { super("Folders Always on Top"); } @Override public boolean isSelected(AnActionEvent event) { return isFoldersAlwaysOnTop(); } @Override public void setSelected(AnActionEvent event, boolean flag) { setFoldersAlwaysOnTop(flag); } @Override public void update(final AnActionEvent e) { super.update(e); final Presentation presentation = e.getPresentation(); presentation.setVisible(getCurrentProjectViewPane() != null); } } private class ScrollFromSourceAction extends AnAction implements DumbAware { private ScrollFromSourceAction() { super("Scroll from Source", "Select the file open in the active editor", AllIcons.General.Locate); getTemplatePresentation().setHoveredIcon(AllIcons.General.LocateHover); } @Override public void actionPerformed(AnActionEvent e) { myAutoScrollFromSourceHandler.scrollFromSource(); } } @Override public Collection getPaneIds() { return myId2Pane.keySet(); } @Override public Collection getSelectInTargets() { ensurePanesLoaded(); return mySelectInTargets.values(); } @NotNull @Override public ActionCallback getReady(@NotNull Object requestor) { AbstractProjectViewPane pane = myId2Pane.get(myCurrentViewSubId); if (pane == null) { pane = myId2Pane.get(myCurrentViewId); } return pane != null ? pane.getReady(requestor) : new ActionCallback.Done(); } }