2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.ide.projectView.impl;
19 import com.intellij.ProjectTopics;
20 import com.intellij.history.LocalHistory;
21 import com.intellij.history.LocalHistoryAction;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.ide.*;
24 import com.intellij.ide.impl.ProjectViewSelectInTarget;
25 import com.intellij.ide.projectView.HelpID;
26 import com.intellij.ide.projectView.ProjectView;
27 import com.intellij.ide.projectView.ProjectViewNode;
28 import com.intellij.ide.projectView.impl.nodes.*;
29 import com.intellij.ide.scopeView.ScopeViewPane;
30 import com.intellij.ide.ui.SplitterProportionsDataImpl;
31 import com.intellij.ide.ui.UISettings;
32 import com.intellij.ide.util.DeleteHandler;
33 import com.intellij.ide.util.DirectoryChooserUtil;
34 import com.intellij.ide.util.EditorHelper;
35 import com.intellij.ide.util.treeView.AbstractTreeBuilder;
36 import com.intellij.ide.util.treeView.AbstractTreeNode;
37 import com.intellij.ide.util.treeView.NodeDescriptor;
38 import com.intellij.openapi.Disposable;
39 import com.intellij.openapi.actionSystem.*;
40 import com.intellij.openapi.application.ApplicationManager;
41 import com.intellij.openapi.command.CommandProcessor;
42 import com.intellij.openapi.components.PersistentStateComponent;
43 import com.intellij.openapi.components.State;
44 import com.intellij.openapi.components.Storage;
45 import com.intellij.openapi.components.StoragePathMacros;
46 import com.intellij.openapi.diagnostic.Logger;
47 import com.intellij.openapi.editor.Editor;
48 import com.intellij.openapi.extensions.Extensions;
49 import com.intellij.openapi.fileEditor.FileEditor;
50 import com.intellij.openapi.fileEditor.FileEditorManager;
51 import com.intellij.openapi.fileEditor.TextEditor;
52 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
53 import com.intellij.openapi.module.Module;
54 import com.intellij.openapi.module.ModuleUtilCore;
55 import com.intellij.openapi.project.DumbAware;
56 import com.intellij.openapi.project.Project;
57 import com.intellij.openapi.roots.*;
58 import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider;
59 import com.intellij.openapi.ui.Messages;
60 import com.intellij.openapi.ui.SimpleToolWindowPanel;
61 import com.intellij.openapi.ui.SplitterProportionsData;
62 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
63 import com.intellij.openapi.util.*;
64 import com.intellij.openapi.util.registry.Registry;
65 import com.intellij.openapi.util.text.StringUtil;
66 import com.intellij.openapi.vfs.JarFileSystem;
67 import com.intellij.openapi.vfs.LocalFileSystem;
68 import com.intellij.openapi.vfs.VirtualFile;
69 import com.intellij.openapi.wm.*;
70 import com.intellij.openapi.wm.ex.ToolWindowEx;
71 import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter;
72 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
73 import com.intellij.openapi.wm.impl.content.ToolWindowContentUi;
74 import com.intellij.psi.*;
75 import com.intellij.psi.impl.file.PsiDirectoryFactory;
76 import com.intellij.psi.util.PsiUtilCore;
77 import com.intellij.ui.AutoScrollFromSourceHandler;
78 import com.intellij.ui.AutoScrollToSourceHandler;
79 import com.intellij.ui.GuiUtils;
80 import com.intellij.ui.components.JBList;
81 import com.intellij.ui.content.Content;
82 import com.intellij.ui.content.ContentManager;
83 import com.intellij.ui.content.ContentManagerAdapter;
84 import com.intellij.ui.content.ContentManagerEvent;
85 import com.intellij.util.ArrayUtil;
86 import com.intellij.util.IJSwingUtilities;
87 import com.intellij.util.PlatformIcons;
88 import com.intellij.util.containers.ContainerUtil;
89 import com.intellij.util.messages.MessageBusConnection;
90 import com.intellij.util.ui.UIUtil;
91 import com.intellij.util.ui.tree.TreeUtil;
92 import gnu.trove.THashMap;
93 import gnu.trove.THashSet;
94 import org.jdom.Attribute;
95 import org.jdom.Element;
96 import org.jetbrains.annotations.NonNls;
97 import org.jetbrains.annotations.NotNull;
98 import org.jetbrains.annotations.Nullable;
100 import javax.swing.*;
101 import javax.swing.tree.DefaultMutableTreeNode;
102 import javax.swing.tree.DefaultTreeModel;
103 import javax.swing.tree.TreePath;
106 import java.util.List;
108 @State(name = "ProjectView", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
109 public class ProjectViewImpl extends ProjectView implements PersistentStateComponent<Element>, Disposable, BusyObject {
110 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.projectView.impl.ProjectViewImpl");
111 private static final Key<String> ID_KEY = Key.create("pane-id");
112 private static final Key<String> SUB_ID_KEY = Key.create("pane-sub-id");
113 private final CopyPasteDelegator myCopyPasteDelegator;
114 private boolean isInitialized;
115 private boolean myExtensionsLoaded = false;
116 @NotNull private final Project myProject;
119 private final Map<String, Boolean> myFlattenPackages = new THashMap<>();
120 private static final boolean ourFlattenPackagesDefaults = false;
121 private final Map<String, Boolean> myShowMembers = new THashMap<>();
122 private static final boolean ourShowMembersDefaults = false;
123 private final Map<String, Boolean> myManualOrder = new THashMap<>();
124 private static final boolean ourManualOrderDefaults = false;
125 private final Map<String, Boolean> mySortByType = new THashMap<>();
126 private static final boolean ourSortByTypeDefaults = false;
127 private final Map<String, Boolean> myShowModules = new THashMap<>();
128 private static final boolean ourShowModulesDefaults = true;
129 private final Map<String, Boolean> myShowLibraryContents = new THashMap<>();
130 private static final boolean ourShowLibraryContentsDefaults = true;
131 private final Map<String, Boolean> myHideEmptyPackages = new THashMap<>();
132 private static final boolean ourHideEmptyPackagesDefaults = true;
133 private final Map<String, Boolean> myAbbreviatePackageNames = new THashMap<>();
134 private static final boolean ourAbbreviatePackagesDefaults = false;
135 private final Map<String, Boolean> myAutoscrollToSource = new THashMap<>();
136 private final Map<String, Boolean> myAutoscrollFromSource = new THashMap<>();
137 private static final boolean ourAutoscrollFromSourceDefaults = false;
139 private boolean myFoldersAlwaysOnTop = true;
142 private String myCurrentViewId;
143 private String myCurrentViewSubId;
146 private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
147 private final MyAutoScrollFromSourceHandler myAutoScrollFromSourceHandler;
149 private final IdeView myIdeView = new MyIdeView();
150 private final MyDeletePSIElementProvider myDeletePSIElementProvider = new MyDeletePSIElementProvider();
151 private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider();
153 private SimpleToolWindowPanel myPanel;
154 private final Map<String, AbstractProjectViewPane> myId2Pane = new LinkedHashMap<>();
155 private final Collection<AbstractProjectViewPane> myUninitializedPanes = new THashSet<>();
157 static final DataKey<ProjectViewImpl> DATA_KEY = DataKey.create("com.intellij.ide.projectView.impl.ProjectViewImpl");
159 private DefaultActionGroup myActionGroup;
160 private String mySavedPaneId = ProjectViewPane.ID;
161 private String mySavedPaneSubId;
162 //private static final Icon COMPACT_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/compactEmptyPackages.png");
163 //private static final Icon HIDE_EMPTY_MIDDLE_PACKAGES_ICON = IconLoader.getIcon("/objectBrowser/hideEmptyPackages.png");
164 @NonNls private static final String ELEMENT_NAVIGATOR = "navigator";
165 @NonNls private static final String ELEMENT_PANES = "panes";
166 @NonNls private static final String ELEMENT_PANE = "pane";
167 @NonNls private static final String ATTRIBUTE_CURRENT_VIEW = "currentView";
168 @NonNls private static final String ATTRIBUTE_CURRENT_SUBVIEW = "currentSubView";
169 @NonNls private static final String ELEMENT_FLATTEN_PACKAGES = "flattenPackages";
170 @NonNls private static final String ELEMENT_SHOW_MEMBERS = "showMembers";
171 @NonNls private static final String ELEMENT_SHOW_MODULES = "showModules";
172 @NonNls private static final String ELEMENT_SHOW_LIBRARY_CONTENTS = "showLibraryContents";
173 @NonNls private static final String ELEMENT_HIDE_EMPTY_PACKAGES = "hideEmptyPackages";
174 @NonNls private static final String ELEMENT_ABBREVIATE_PACKAGE_NAMES = "abbreviatePackageNames";
175 @NonNls private static final String ELEMENT_AUTOSCROLL_TO_SOURCE = "autoscrollToSource";
176 @NonNls private static final String ELEMENT_AUTOSCROLL_FROM_SOURCE = "autoscrollFromSource";
177 @NonNls private static final String ELEMENT_SORT_BY_TYPE = "sortByType";
178 @NonNls private static final String ELEMENT_FOLDERS_ALWAYS_ON_TOP = "foldersAlwaysOnTop";
179 @NonNls private static final String ELEMENT_MANUAL_ORDER = "manualOrder";
181 private static final String ATTRIBUTE_ID = "id";
182 private JPanel myViewContentPanel;
183 private static final Comparator<AbstractProjectViewPane> PANE_WEIGHT_COMPARATOR = (o1, o2) -> o1.getWeight() - o2.getWeight();
184 private final FileEditorManager myFileEditorManager;
185 private final MyPanel myDataProvider;
186 private final SplitterProportionsData splitterProportions = new SplitterProportionsDataImpl();
187 private final MessageBusConnection myConnection;
188 private final Map<String, Element> myUninitializedPaneState = new HashMap<>();
189 private final Map<String, SelectInTarget> mySelectInTargets = new LinkedHashMap<>();
190 private ContentManager myContentManager;
192 public ProjectViewImpl(@NotNull Project project, final FileEditorManager fileEditorManager, final ToolWindowManagerEx toolWindowManager) {
197 myFileEditorManager = fileEditorManager;
199 myConnection = project.getMessageBus().connect();
200 myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
202 public void rootsChanged(ModuleRootEvent event) {
207 myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler();
209 myDataProvider = new MyPanel();
210 myDataProvider.add(myPanel, BorderLayout.CENTER);
211 myCopyPasteDelegator = new CopyPasteDelegator(myProject, myPanel) {
214 protected PsiElement[] getSelectedElements() {
215 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
216 return viewPane == null ? PsiElement.EMPTY_ARRAY : viewPane.getSelectedPSIElements();
219 myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
221 protected boolean isAutoScrollMode() {
222 return isAutoscrollToSource(myCurrentViewId);
226 protected void setAutoScrollMode(boolean state) {
227 setAutoscrollToSource(state, myCurrentViewId);
230 toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter(){
231 private boolean toolWindowVisible;
234 public void stateChanged() {
235 ToolWindow window = toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW);
236 if (window == null) return;
237 if (window.isVisible() && !toolWindowVisible) {
238 String id = getCurrentViewId();
239 if (isAutoscrollToSource(id)) {
240 AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
242 if (currentProjectViewPane != null) {
243 myAutoScrollToSourceHandler.onMouseClicked(currentProjectViewPane.getTree());
246 if (isAutoscrollFromSource(id)) {
247 myAutoScrollFromSourceHandler.setAutoScrollEnabled(true);
250 toolWindowVisible = window.isVisible();
255 private void constructUi() {
256 myViewContentPanel = new JPanel();
257 myPanel = new SimpleToolWindowPanel(true);
258 myPanel.setContent(myViewContentPanel);
263 public List<AnAction> getActions(boolean originalProvider) {
264 return Collections.emptyList();
268 public synchronized void addProjectPane(@NotNull final AbstractProjectViewPane pane) {
269 myUninitializedPanes.add(pane);
270 SelectInTarget selectInTarget = pane.createSelectInTarget();
271 if (selectInTarget != null) {
272 mySelectInTargets.put(pane.getId(), selectInTarget);
275 doAddUninitializedPanes();
280 public synchronized void removeProjectPane(@NotNull AbstractProjectViewPane pane) {
281 ApplicationManager.getApplication().assertIsDispatchThread();
282 myUninitializedPanes.remove(pane);
283 //assume we are completely initialized here
284 String idToRemove = pane.getId();
286 if (!myId2Pane.containsKey(idToRemove)) return;
287 pane.removeTreeChangeListener();
288 for (int i = getContentManager().getContentCount() - 1; i >= 0; i--) {
289 Content content = getContentManager().getContent(i);
290 String id = content != null ? content.getUserData(ID_KEY) : null;
291 if (id != null && id.equals(idToRemove)) {
292 getContentManager().removeContent(content, true);
295 myId2Pane.remove(idToRemove);
296 mySelectInTargets.remove(idToRemove);
297 viewSelectionChanged();
300 private synchronized void doAddUninitializedPanes() {
301 for (AbstractProjectViewPane pane : myUninitializedPanes) {
304 final Content[] contents = getContentManager().getContents();
305 for (int i = 1; i < contents.length; i++) {
306 Content content = contents[i];
307 Content prev = contents[i - 1];
308 if (!StringUtil.equals(content.getUserData(ID_KEY), prev.getUserData(ID_KEY)) &&
309 prev.getUserData(SUB_ID_KEY) != null && content.getSeparator() == null) {
310 content.setSeparator("");
314 String selectID = null;
315 String selectSubID = null;
317 // try to find saved selected view...
318 for (Content content : contents) {
319 final String id = content.getUserData(ID_KEY);
320 final String subId = content.getUserData(SUB_ID_KEY);
322 id.equals(mySavedPaneId) &&
323 StringUtil.equals(subId, mySavedPaneSubId)) {
330 // saved view not found (plugin disabled, ID changed etc.) - select first available view...
331 if (selectID == null && contents.length > 0) {
332 Content content = contents[0];
333 selectID = content.getUserData(ID_KEY);
334 selectSubID = content.getUserData(SUB_ID_KEY);
337 if (selectID != null) {
338 changeView(selectID, selectSubID);
339 mySavedPaneId = null;
340 mySavedPaneSubId = null;
343 myUninitializedPanes.clear();
346 private void doAddPane(@NotNull final AbstractProjectViewPane newPane) {
347 ApplicationManager.getApplication().assertIsDispatchThread();
349 final ContentManager manager = getContentManager();
350 for (index = 0; index < manager.getContentCount(); index++) {
351 Content content = manager.getContent(index);
352 String id = content.getUserData(ID_KEY);
353 AbstractProjectViewPane pane = myId2Pane.get(id);
355 int comp = PANE_WEIGHT_COMPARATOR.compare(pane, newPane);
356 LOG.assertTrue(comp != 0, "Project view pane " + newPane + " has the same weight as " + pane +
357 ". Please make sure that you overload getWeight() and return a distinct weight value.");
362 final String id = newPane.getId();
363 myId2Pane.put(id, newPane);
364 String[] subIds = newPane.getSubIds();
365 subIds = subIds.length == 0 ? new String[]{null} : subIds;
366 boolean first = true;
367 for (String subId : subIds) {
368 final String title = subId != null ? newPane.getPresentableSubIdName(subId) : newPane.getTitle();
369 final Content content = getContentManager().getFactory().createContent(getComponent(), title, false);
370 content.setTabName(title);
371 content.putUserData(ID_KEY, id);
372 content.putUserData(SUB_ID_KEY, subId);
373 content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
374 content.setIcon(newPane.getIcon());
375 content.setPopupIcon(subId != null ? AllIcons.General.Bullet : newPane.getIcon());
376 content.setPreferredFocusedComponent(() -> {
377 final AbstractProjectViewPane current = getCurrentProjectViewPane();
378 return current != null ? current.getComponentToFocus() : null;
380 content.setBusyObject(this);
381 if (first && subId != null) {
382 content.setSeparator(newPane.getTitle());
384 manager.addContent(content, index++);
389 private void showPane(@NotNull AbstractProjectViewPane newPane) {
390 AbstractProjectViewPane currentPane = getCurrentProjectViewPane();
391 PsiElement selectedPsiElement = null;
392 if (currentPane != null) {
393 if (currentPane != newPane) {
394 currentPane.saveExpandedPaths();
396 final PsiElement[] elements = currentPane.getSelectedPSIElements();
397 if (elements.length > 0) {
398 selectedPsiElement = elements[0];
401 myViewContentPanel.removeAll();
402 JComponent component = newPane.createComponent();
403 UIUtil.removeScrollBorder(component);
404 myViewContentPanel.setLayout(new BorderLayout());
405 myViewContentPanel.add(component, BorderLayout.CENTER);
406 myCurrentViewId = newPane.getId();
407 String newSubId = myCurrentViewSubId = newPane.getSubId();
408 myViewContentPanel.revalidate();
409 myViewContentPanel.repaint();
410 createToolbarActions();
412 myAutoScrollToSourceHandler.install(newPane.myTree);
414 IdeFocusManager.getInstance(myProject).requestFocus(newPane.getComponentToFocus(), false);
416 newPane.restoreExpandedPaths();
417 if (selectedPsiElement != null && newSubId != null) {
418 final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(selectedPsiElement);
419 if (virtualFile != null && ((ProjectViewSelectInTarget)newPane.createSelectInTarget()).isSubIdSelectable(newSubId, new SelectInContext() {
422 public Project getProject() {
428 public VirtualFile getVirtualFile() {
433 public Object getSelectorInFile() {
438 public FileEditorProvider getFileEditorProvider() {
442 newPane.select(selectedPsiElement, virtualFile, true);
445 myAutoScrollToSourceHandler.onMouseClicked(newPane.myTree);
449 public synchronized void setupImpl(@NotNull ToolWindow toolWindow) {
450 setupImpl(toolWindow, true);
454 public synchronized void setupImpl(@NotNull ToolWindow toolWindow, final boolean loadPaneExtensions) {
455 ApplicationManager.getApplication().assertIsDispatchThread();
456 myActionGroup = new DefaultActionGroup();
458 myAutoScrollFromSourceHandler.install();
460 myContentManager = toolWindow.getContentManager();
461 if (!ApplicationManager.getApplication().isUnitTestMode()) {
462 toolWindow.setDefaultContentUiType(ToolWindowContentUiType.COMBO);
463 ((ToolWindowEx)toolWindow).setAdditionalGearActions(myActionGroup);
464 toolWindow.getComponent().putClientProperty(ToolWindowContentUi.HIDE_ID_LABEL, "true");
467 GuiUtils.replaceJSplitPaneWithIDEASplitter(myPanel);
468 SwingUtilities.invokeLater(() -> splitterProportions.restoreSplitterProportions(myPanel));
470 if (loadPaneExtensions) {
473 isInitialized = true;
474 doAddUninitializedPanes();
476 getContentManager().addContentManagerListener(new ContentManagerAdapter() {
478 public void selectionChanged(ContentManagerEvent event) {
479 if (event.getOperation() == ContentManagerEvent.ContentOperation.add) {
480 viewSelectionChanged();
484 viewSelectionChanged();
487 private void ensurePanesLoaded() {
488 if (myExtensionsLoaded) return;
489 myExtensionsLoaded = true;
490 AbstractProjectViewPane[] extensions = Extensions.getExtensions(AbstractProjectViewPane.EP_NAME, myProject);
491 Arrays.sort(extensions, PANE_WEIGHT_COMPARATOR);
492 for(AbstractProjectViewPane pane: extensions) {
493 if (myUninitializedPaneState.containsKey(pane.getId())) {
495 pane.readExternal(myUninitializedPaneState.get(pane.getId()));
497 catch (InvalidDataException e) {
500 myUninitializedPaneState.remove(pane.getId());
502 if (pane.isInitiallyVisible() && !myId2Pane.containsKey(pane.getId())) {
503 addProjectPane(pane);
508 private boolean viewSelectionChanged() {
509 Content content = getContentManager().getSelectedContent();
510 if (content == null) return false;
511 final String id = content.getUserData(ID_KEY);
512 String subId = content.getUserData(SUB_ID_KEY);
513 if (content.equals(Pair.create(myCurrentViewId, myCurrentViewSubId))) return false;
514 final AbstractProjectViewPane newPane = getProjectViewPaneById(id);
515 if (newPane == null) return false;
516 newPane.setSubId(subId);
518 if (isAutoscrollFromSource(id)) {
519 myAutoScrollFromSourceHandler.scrollFromSource();
524 private void createToolbarActions() {
525 List<AnAction> titleActions = ContainerUtil.newSmartList();
526 myActionGroup.removeAll();
527 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
528 myActionGroup.addAction(new PaneOptionAction(myFlattenPackages, IdeBundle.message("action.flatten.packages"),
529 IdeBundle.message("action.flatten.packages"), PlatformIcons.FLATTEN_PACKAGES_ICON,
530 ourFlattenPackagesDefaults) {
532 public void setSelected(AnActionEvent event, boolean flag) {
533 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
534 final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
535 if (isGlobalOptions()) {
536 setFlattenPackages(flag, viewPane.getId());
538 super.setSelected(event, flag);
540 selectionInfo.apply(viewPane);
544 public boolean isSelected(AnActionEvent event) {
545 if (isGlobalOptions()) return getGlobalOptions().getFlattenPackages();
546 return super.isSelected(event);
548 }).setAsSecondary(true);
551 class FlattenPackagesDependableAction extends PaneOptionAction {
552 FlattenPackagesDependableAction(@NotNull Map<String, Boolean> optionsMap,
553 @NotNull String text,
554 @NotNull String description,
556 boolean optionDefaultValue) {
557 super(optionsMap, text, description, icon, optionDefaultValue);
561 public void setSelected(AnActionEvent event, boolean flag) {
562 if (isGlobalOptions()) {
563 getGlobalOptions().setFlattenPackages(flag);
565 super.setSelected(event, flag);
569 public void update(AnActionEvent e) {
571 final Presentation presentation = e.getPresentation();
572 presentation.setVisible(isFlattenPackages(myCurrentViewId));
575 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) {
576 myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true);
578 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
579 myActionGroup.addAction(new FlattenPackagesDependableAction(myAbbreviatePackageNames,
580 IdeBundle.message("action.abbreviate.qualified.package.names"),
581 IdeBundle.message("action.abbreviate.qualified.package.names"),
582 AllIcons.ObjectBrowser.AbbreviatePackageNames,
583 ourAbbreviatePackagesDefaults) {
585 public boolean isSelected(AnActionEvent event) {
586 return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId);
591 public void update(AnActionEvent e) {
593 if (ScopeViewPane.ID.equals(myCurrentViewId)) {
594 e.getPresentation().setEnabled(false);
597 }).setAsSecondary(true);
600 if (isShowMembersOptionSupported()) {
601 myActionGroup.addAction(new PaneOptionAction(myShowMembers, IdeBundle.message("action.show.members"),
602 IdeBundle.message("action.show.hide.members"),
603 AllIcons.ObjectBrowser.ShowMembers, ourShowMembersDefaults) {
605 public boolean isSelected(AnActionEvent event) {
606 if (isGlobalOptions()) return getGlobalOptions().getShowMembers();
607 return super.isSelected(event);
611 public void setSelected(AnActionEvent event, boolean flag) {
612 if (isGlobalOptions()) {
613 getGlobalOptions().setShowMembers(flag);
615 super.setSelected(event, flag);
618 .setAsSecondary(true);
620 myActionGroup.addAction(myAutoScrollToSourceHandler.createToggleAction()).setAsSecondary(true);
621 myActionGroup.addAction(myAutoScrollFromSourceHandler.createToggleAction()).setAsSecondary(true);
622 myActionGroup.addAction(new ManualOrderAction()).setAsSecondary(true);
623 myActionGroup.addAction(new SortByTypeAction()).setAsSecondary(true);
624 myActionGroup.addAction(new FoldersAlwaysOnTopAction()).setAsSecondary(true);
626 if (!myAutoScrollFromSourceHandler.isAutoScrollEnabled()) {
627 titleActions.add(new ScrollFromSourceAction());
629 AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() {
631 public void expandAll() {
636 public boolean canExpand() {
641 public void collapseAll() {
642 AbstractProjectViewPane pane = getCurrentProjectViewPane();
643 JTree tree = pane.myTree;
645 TreeUtil.collapseAll(tree, 0);
650 public boolean canCollapse() {
654 collapseAllAction.getTemplatePresentation().setIcon(AllIcons.General.CollapseAll);
655 collapseAllAction.getTemplatePresentation().setHoveredIcon(AllIcons.General.CollapseAllHover);
656 titleActions.add(collapseAllAction);
657 getCurrentProjectViewPane().addToolbarActions(myActionGroup);
659 ToolWindowEx window = (ToolWindowEx)ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW);
660 if (window != null) {
661 window.setTitleActions(titleActions.toArray(new AnAction[titleActions.size()]));
665 protected boolean isShowMembersOptionSupported() {
670 public AbstractProjectViewPane getProjectViewPaneById(String id) {
671 if (!ApplicationManager.getApplication().isUnitTestMode()) { // most tests don't need all panes to be loaded
675 final AbstractProjectViewPane pane = myId2Pane.get(id);
679 for (AbstractProjectViewPane viewPane : myUninitializedPanes) {
680 if (viewPane.getId().equals(id)) {
688 public AbstractProjectViewPane getCurrentProjectViewPane() {
689 return getProjectViewPaneById(myCurrentViewId);
693 public void refresh() {
694 AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
695 if (currentProjectViewPane != null) {
696 // may be null for e.g. default project
697 currentProjectViewPane.updateFromRoot(false);
702 public void select(final Object element, VirtualFile file, boolean requestFocus) {
703 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
704 if (viewPane != null) {
705 viewPane.select(element, file, requestFocus);
711 public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) {
712 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
713 if (viewPane != null && viewPane instanceof AbstractProjectViewPSIPane) {
714 return ((AbstractProjectViewPSIPane)viewPane).selectCB(element, file, requestFocus);
716 select(element, file, requestFocus);
717 return ActionCallback.DONE;
721 public void dispose() {
722 myConnection.disconnect();
725 public JComponent getComponent() {
726 return myDataProvider;
730 public String getCurrentViewId() {
731 return myCurrentViewId;
735 public PsiElement getParentOfCurrentSelection() {
736 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
737 if (viewPane == null) {
740 TreePath path = viewPane.getSelectedPath();
744 path = path.getParentPath();
748 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
749 Object userObject = node.getUserObject();
750 if (userObject instanceof ProjectViewNode) {
751 ProjectViewNode descriptor = (ProjectViewNode)userObject;
752 Object element = descriptor.getValue();
753 if (element instanceof PsiElement) {
754 PsiElement psiElement = (PsiElement)element;
755 if (!psiElement.isValid()) return null;
765 public ContentManager getContentManager() {
766 if (myContentManager == null) {
767 ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).getContentManager();
769 return myContentManager;
773 private class PaneOptionAction extends ToggleAction implements DumbAware {
774 private final Map<String, Boolean> myOptionsMap;
775 private final boolean myOptionDefaultValue;
777 PaneOptionAction(@NotNull Map<String, Boolean> optionsMap,
778 @NotNull String text,
779 @NotNull String description,
781 boolean optionDefaultValue) {
782 super(text, description, icon);
783 myOptionsMap = optionsMap;
784 myOptionDefaultValue = optionDefaultValue;
788 public boolean isSelected(AnActionEvent event) {
789 return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue);
793 public void setSelected(AnActionEvent event, boolean flag) {
794 setPaneOption(myOptionsMap, flag, myCurrentViewId, true);
799 public void changeView() {
800 final List<AbstractProjectViewPane> views = new ArrayList<>(myId2Pane.values());
801 views.remove(getCurrentProjectViewPane());
802 Collections.sort(views, PANE_WEIGHT_COMPARATOR);
804 final JList list = new JBList(ArrayUtil.toObjectArray(views));
805 list.setCellRenderer(new DefaultListCellRenderer() {
807 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
808 super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
809 AbstractProjectViewPane pane = (AbstractProjectViewPane)value;
810 setText(pane.getTitle());
815 if (!views.isEmpty()) {
816 list.setSelectedValue(views.get(0), true);
818 Runnable runnable = () -> {
819 if (list.getSelectedIndex() < 0) return;
820 AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue();
821 changeView(pane.getId());
824 new PopupChooserBuilder(list).
825 setTitle(IdeBundle.message("title.popup.views")).
826 setItemChoosenCallback(runnable).
827 createPopup().showInCenterOf(getComponent());
831 public void changeView(@NotNull String viewId) {
832 changeView(viewId, null);
836 public void changeView(@NotNull String viewId, @Nullable String subId) {
837 changeViewCB(viewId, subId);
842 public ActionCallback changeViewCB(@NotNull String viewId, String subId) {
843 AbstractProjectViewPane pane = getProjectViewPaneById(viewId);
844 LOG.assertTrue(pane != null, "Project view pane not found: " + viewId + "; subId:" + subId);
845 if (!viewId.equals(getCurrentViewId())
846 || subId != null && !subId.equals(pane.getSubId())) {
847 for (Content content : getContentManager().getContents()) {
848 if (viewId.equals(content.getUserData(ID_KEY)) && StringUtil.equals(subId, content.getUserData(SUB_ID_KEY))) {
849 return getContentManager().setSelectedContentCB(content);
853 return ActionCallback.REJECTED;
856 private final class MyDeletePSIElementProvider implements DeleteProvider {
858 public boolean canDeleteElement(@NotNull DataContext dataContext) {
859 final PsiElement[] elements = getElementsToDelete();
860 return DeleteHandler.shouldEnableDeleteAction(elements);
864 public void deleteElement(@NotNull DataContext dataContext) {
865 List<PsiElement> allElements = Arrays.asList(getElementsToDelete());
866 List<PsiElement> validElements = new ArrayList<>();
867 for (PsiElement psiElement : allElements) {
868 if (psiElement != null && psiElement.isValid()) validElements.add(psiElement);
870 final PsiElement[] elements = PsiUtilCore.toPsiElementArray(validElements);
872 LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
874 DeleteHandler.deletePsiElement(elements, myProject);
882 private PsiElement[] getElementsToDelete() {
883 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
884 PsiElement[] elements = viewPane.getSelectedPSIElements();
885 for (int idx = 0; idx < elements.length; idx++) {
886 final PsiElement element = elements[idx];
887 if (element instanceof PsiDirectory) {
888 PsiDirectory directory = (PsiDirectory)element;
889 final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject);
890 if (isHideEmptyMiddlePackages(viewPane.getId()) && directory.getChildren().length == 0 && !directoryHelper.skipDirectory(directory)) {
892 PsiDirectory parent = directory.getParentDirectory();
893 if (parent == null) break;
894 if (directoryHelper.skipDirectory(parent) || PsiDirectoryFactory.getInstance(myProject).getQualifiedName(parent, false).length() == 0) break;
895 PsiElement[] children = parent.getChildren();
896 if (children.length == 0 || children.length == 1 && children[0] == directory) {
903 elements[idx] = directory;
905 final VirtualFile virtualFile = directory.getVirtualFile();
906 final String path = virtualFile.getPath();
907 if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root
908 final VirtualFile vFile =
909 LocalFileSystem.getInstance().findFileByPath(path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length()));
911 final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
912 if (psiFile != null) {
913 elements[idx] = psiFile;
924 private final class MyPanel extends JPanel implements DataProvider {
926 super(new BorderLayout());
930 private Object getSelectedNodeElement() {
931 final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
932 if (currentProjectViewPane == null) { // can happen if not initialized yet
935 DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode();
939 Object userObject = node.getUserObject();
940 if (userObject instanceof AbstractTreeNode) {
941 return ((AbstractTreeNode)userObject).getValue();
943 if (!(userObject instanceof NodeDescriptor)) {
946 return ((NodeDescriptor)userObject).getElement();
950 public Object getData(String dataId) {
951 final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
952 if (currentProjectViewPane != null) {
953 final Object paneSpecificData = currentProjectViewPane.getData(dataId);
954 if (paneSpecificData != null) return paneSpecificData;
957 if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
958 if (currentProjectViewPane == null) return null;
959 final PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
960 return elements.length == 1 ? elements[0] : null;
962 if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
963 if (currentProjectViewPane == null) {
966 PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
967 return elements.length == 0 ? null : elements;
969 if (LangDataKeys.MODULE.is(dataId)) {
970 VirtualFile[] virtualFiles = (VirtualFile[])getData(CommonDataKeys.VIRTUAL_FILE_ARRAY.getName());
971 if (virtualFiles == null || virtualFiles.length <= 1) return null;
972 final Set<Module> modules = new HashSet<>();
973 for (VirtualFile virtualFile : virtualFiles) {
974 modules.add(ModuleUtilCore.findModuleForFile(virtualFile, myProject));
976 return modules.size() == 1 ? modules.iterator().next() : null;
978 if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
981 if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
982 return myCopyPasteDelegator.getCutProvider();
984 if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
985 return myCopyPasteDelegator.getCopyProvider();
987 if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
988 return myCopyPasteDelegator.getPasteProvider();
990 if (LangDataKeys.IDE_VIEW.is(dataId)) {
993 if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
994 final Module[] modules = getSelectedModules();
995 if (modules != null) {
996 return myDeleteModuleProvider;
998 final LibraryOrderEntry orderEntry = getSelectedLibrary();
999 if (orderEntry != null) {
1000 return new DeleteProvider() {
1002 public void deleteElement(@NotNull DataContext dataContext) {
1003 detachLibrary(orderEntry, myProject);
1007 public boolean canDeleteElement(@NotNull DataContext dataContext) {
1012 return myDeletePSIElementProvider;
1014 if (PlatformDataKeys.HELP_ID.is(dataId)) {
1015 return HelpID.PROJECT_VIEWS;
1017 if (ProjectViewImpl.DATA_KEY.is(dataId)) {
1018 return ProjectViewImpl.this;
1020 if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) {
1021 Object selected = getSelectedNodeElement();
1022 return selected instanceof Project ? selected : null;
1024 if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
1025 Object selected = getSelectedNodeElement();
1026 if (selected instanceof Module) {
1027 return !((Module)selected).isDisposed() ? selected : null;
1029 else if (selected instanceof PsiDirectory) {
1030 return moduleBySingleContentRoot(((PsiDirectory)selected).getVirtualFile());
1032 else if (selected instanceof VirtualFile) {
1033 return moduleBySingleContentRoot((VirtualFile)selected);
1040 if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
1041 return getSelectedModules();
1043 if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
1044 final List<ModuleGroup> selectedElements = getSelectedElements(ModuleGroup.class);
1045 return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]);
1047 if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
1048 final List<LibraryGroupElement> selectedElements = getSelectedElements(LibraryGroupElement.class);
1049 return selectedElements.isEmpty() ? null : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]);
1051 if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
1052 final List<NamedLibraryElement> selectedElements = getSelectedElements(NamedLibraryElement.class);
1053 return selectedElements.isEmpty() ? null : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]);
1060 private LibraryOrderEntry getSelectedLibrary() {
1061 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1062 DefaultMutableTreeNode node = viewPane != null ? viewPane.getSelectedNode() : null;
1063 if (node == null) return null;
1064 DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
1065 if (parent == null) return null;
1066 Object userObject = parent.getUserObject();
1067 if (userObject instanceof LibraryGroupNode) {
1068 userObject = node.getUserObject();
1069 if (userObject instanceof NamedLibraryElementNode) {
1070 NamedLibraryElement element = ((NamedLibraryElementNode)userObject).getValue();
1071 OrderEntry orderEntry = element.getOrderEntry();
1072 return orderEntry instanceof LibraryOrderEntry ? (LibraryOrderEntry)orderEntry : null;
1074 PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue();
1075 VirtualFile virtualFile = directory.getVirtualFile();
1076 Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue();
1078 if (module == null) return null;
1079 ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
1080 OrderEntry entry = index.getOrderEntryForFile(virtualFile);
1081 if (entry instanceof LibraryOrderEntry) {
1082 return (LibraryOrderEntry)entry;
1089 private void detachLibrary(@NotNull final LibraryOrderEntry orderEntry, @NotNull Project project) {
1090 final Module module = orderEntry.getOwnerModule();
1091 String message = IdeBundle.message("detach.library.from.module", orderEntry.getPresentableName(), module.getName());
1092 String title = IdeBundle.message("detach.library");
1093 int ret = Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon());
1094 if (ret != Messages.OK) return;
1095 CommandProcessor.getInstance().executeCommand(module.getProject(), () -> {
1096 final Runnable action = () -> {
1097 ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
1098 OrderEntry[] orderEntries = rootManager.getOrderEntries();
1099 ModifiableRootModel model = rootManager.getModifiableModel();
1100 OrderEntry[] modifiableEntries = model.getOrderEntries();
1101 for (int i = 0; i < orderEntries.length; i++) {
1102 OrderEntry entry = orderEntries[i];
1103 if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == orderEntry.getLibrary()) {
1104 model.removeOrderEntry(modifiableEntries[i]);
1109 ApplicationManager.getApplication().runWriteAction(action);
1114 private Module[] getSelectedModules() {
1115 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1116 if (viewPane == null) return null;
1117 final Object[] elements = viewPane.getSelectedElements();
1118 ArrayList<Module> result = new ArrayList<>();
1119 for (Object element : elements) {
1120 if (element instanceof Module) {
1121 final Module module = (Module)element;
1122 if (!module.isDisposed()) {
1126 else if (element instanceof ModuleGroup) {
1127 Collection<Module> modules = ((ModuleGroup)element).modulesInGroup(myProject, true);
1128 result.addAll(modules);
1130 else if (element instanceof PsiDirectory) {
1131 Module module = moduleBySingleContentRoot(((PsiDirectory)element).getVirtualFile());
1132 if (module != null) result.add(module);
1134 else if (element instanceof VirtualFile) {
1135 Module module = moduleBySingleContentRoot((VirtualFile)element);
1136 if (module != null) result.add(module);
1140 if (result.isEmpty()) {
1144 return result.toArray(new Module[result.size()]);
1149 /** Project view has the same node for module and its single content root
1150 * => MODULE_CONTEXT data key should return the module when its content root is selected
1151 * When there are multiple content roots, they have different nodes under the module node
1152 * => MODULE_CONTEXT should be only available for the module node
1153 * otherwise VirtualFileArrayRule will return all module's content roots when just one of them is selected
1156 private Module moduleBySingleContentRoot(@NotNull VirtualFile file) {
1157 if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) {
1158 Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file);
1159 if (module != null && !module.isDisposed() && ModuleRootManager.getInstance(module).getContentRoots().length == 1) {
1168 private <T> List<T> getSelectedElements(@NotNull Class<T> klass) {
1169 List<T> result = new ArrayList<>();
1170 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1171 if (viewPane == null) return result;
1172 final Object[] elements = viewPane.getSelectedElements();
1173 for (Object element : elements) {
1174 //element still valid
1175 if (element != null && klass.isAssignableFrom(element.getClass())) {
1176 result.add((T)element);
1182 private final class MyIdeView implements IdeView {
1184 public void selectElement(PsiElement element) {
1185 selectPsiElement(element, false);
1186 boolean requestFocus = true;
1187 if (element != null) {
1188 final boolean isDirectory = element instanceof PsiDirectory;
1190 FileEditor editor = EditorHelper.openInEditor(element, false);
1191 if (editor != null) {
1192 ToolWindowManager.getInstance(myProject).activateEditorComponent();
1193 requestFocus = false;
1199 selectPsiElement(element, true);
1205 public PsiDirectory[] getDirectories() {
1206 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1207 if (viewPane != null) {
1208 return viewPane.getSelectedDirectories();
1211 return PsiDirectory.EMPTY_ARRAY;
1215 public PsiDirectory getOrChooseDirectory() {
1216 return DirectoryChooserUtil.getOrChooseDirectory(this);
1221 public void selectPsiElement(PsiElement element, boolean requestFocus) {
1222 if (element == null) return;
1223 VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
1224 select(element, virtualFile, requestFocus);
1228 private static void readOption(Element node, @NotNull Map<String, Boolean> options) {
1229 if (node == null) return;
1230 for (Attribute attribute : node.getAttributes()) {
1231 options.put(attribute.getName(), Boolean.TRUE.toString().equals(attribute.getValue()) ? Boolean.TRUE : Boolean.FALSE);
1235 private static void writeOption(@NotNull Element parentNode, @NotNull Map<String, Boolean> optionsForPanes, @NotNull String optionName) {
1236 Element e = new Element(optionName);
1237 for (Map.Entry<String, Boolean> entry : optionsForPanes.entrySet()) {
1238 final String key = entry.getKey();
1239 if (key != null) { //SCR48267
1240 e.setAttribute(key, Boolean.toString(entry.getValue().booleanValue()));
1244 parentNode.addContent(e);
1248 public void loadState(Element parentNode) {
1249 Element navigatorElement = parentNode.getChild(ELEMENT_NAVIGATOR);
1250 if (navigatorElement != null) {
1251 mySavedPaneId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_VIEW);
1252 mySavedPaneSubId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_SUBVIEW);
1253 if (mySavedPaneId == null) {
1254 mySavedPaneId = ProjectViewPane.ID;
1255 mySavedPaneSubId = null;
1257 readOption(navigatorElement.getChild(ELEMENT_FLATTEN_PACKAGES), myFlattenPackages);
1258 readOption(navigatorElement.getChild(ELEMENT_SHOW_MEMBERS), myShowMembers);
1259 readOption(navigatorElement.getChild(ELEMENT_SHOW_MODULES), myShowModules);
1260 readOption(navigatorElement.getChild(ELEMENT_SHOW_LIBRARY_CONTENTS), myShowLibraryContents);
1261 readOption(navigatorElement.getChild(ELEMENT_HIDE_EMPTY_PACKAGES), myHideEmptyPackages);
1262 readOption(navigatorElement.getChild(ELEMENT_ABBREVIATE_PACKAGE_NAMES), myAbbreviatePackageNames);
1263 readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_TO_SOURCE), myAutoscrollToSource);
1264 readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_FROM_SOURCE), myAutoscrollFromSource);
1265 readOption(navigatorElement.getChild(ELEMENT_SORT_BY_TYPE), mySortByType);
1266 readOption(navigatorElement.getChild(ELEMENT_MANUAL_ORDER), myManualOrder);
1268 Element foldersElement = navigatorElement.getChild(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1269 if (foldersElement != null) myFoldersAlwaysOnTop = Boolean.valueOf(foldersElement.getAttributeValue("value"));
1272 splitterProportions.readExternal(navigatorElement);
1274 catch (InvalidDataException e) {
1278 Element panesElement = parentNode.getChild(ELEMENT_PANES);
1279 if (panesElement != null) {
1280 readPaneState(panesElement);
1284 private void readPaneState(@NotNull Element panesElement) {
1285 @SuppressWarnings({"unchecked"})
1286 final List<Element> paneElements = panesElement.getChildren(ELEMENT_PANE);
1288 for (Element paneElement : paneElements) {
1289 String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID);
1290 final AbstractProjectViewPane pane = myId2Pane.get(paneId);
1293 pane.readExternal(paneElement);
1295 catch (InvalidDataException e) {
1300 myUninitializedPaneState.put(paneId, paneElement);
1306 public Element getState() {
1307 Element parentNode = new Element("projectView");
1308 Element navigatorElement = new Element(ELEMENT_NAVIGATOR);
1309 AbstractProjectViewPane currentPane = getCurrentProjectViewPane();
1310 if (currentPane != null) {
1311 navigatorElement.setAttribute(ATTRIBUTE_CURRENT_VIEW, currentPane.getId());
1312 String subId = currentPane.getSubId();
1313 if (subId != null) {
1314 navigatorElement.setAttribute(ATTRIBUTE_CURRENT_SUBVIEW, subId);
1317 writeOption(navigatorElement, myFlattenPackages, ELEMENT_FLATTEN_PACKAGES);
1318 writeOption(navigatorElement, myShowMembers, ELEMENT_SHOW_MEMBERS);
1319 writeOption(navigatorElement, myShowModules, ELEMENT_SHOW_MODULES);
1320 writeOption(navigatorElement, myShowLibraryContents, ELEMENT_SHOW_LIBRARY_CONTENTS);
1321 writeOption(navigatorElement, myHideEmptyPackages, ELEMENT_HIDE_EMPTY_PACKAGES);
1322 writeOption(navigatorElement, myAbbreviatePackageNames, ELEMENT_ABBREVIATE_PACKAGE_NAMES);
1323 writeOption(navigatorElement, myAutoscrollToSource, ELEMENT_AUTOSCROLL_TO_SOURCE);
1324 writeOption(navigatorElement, myAutoscrollFromSource, ELEMENT_AUTOSCROLL_FROM_SOURCE);
1325 writeOption(navigatorElement, mySortByType, ELEMENT_SORT_BY_TYPE);
1326 writeOption(navigatorElement, myManualOrder, ELEMENT_MANUAL_ORDER);
1328 Element foldersElement = new Element(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1329 foldersElement.setAttribute("value", Boolean.toString(myFoldersAlwaysOnTop));
1330 navigatorElement.addContent(foldersElement);
1332 splitterProportions.saveSplitterProportions(myPanel);
1334 splitterProportions.writeExternal(navigatorElement);
1336 catch (WriteExternalException e) {
1339 parentNode.addContent(navigatorElement);
1341 Element panesElement = new Element(ELEMENT_PANES);
1342 writePaneState(panesElement);
1343 parentNode.addContent(panesElement);
1347 private void writePaneState(@NotNull Element panesElement) {
1348 for (AbstractProjectViewPane pane : myId2Pane.values()) {
1349 Element paneElement = new Element(ELEMENT_PANE);
1350 paneElement.setAttribute(ATTRIBUTE_ID, pane.getId());
1352 pane.writeExternal(paneElement);
1354 catch (WriteExternalException e) {
1357 panesElement.addContent(paneElement);
1359 for (Element element : myUninitializedPaneState.values()) {
1360 panesElement.addContent(element.clone());
1364 boolean isGlobalOptions() {
1365 return Registry.is("ide.projectView.globalOptions");
1368 ProjectViewSharedSettings getGlobalOptions() {
1369 return ProjectViewSharedSettings.Companion.getInstance();
1373 public boolean isAutoscrollToSource(String paneId) {
1374 if (isGlobalOptions()) {
1375 return getGlobalOptions().getAutoscrollToSource();
1378 return getPaneOptionValue(myAutoscrollToSource, paneId, UISettings.getInstance().DEFAULT_AUTOSCROLL_TO_SOURCE);
1381 public void setAutoscrollToSource(boolean autoscrollMode, String paneId) {
1382 if (isGlobalOptions()) {
1383 getGlobalOptions().setAutoscrollToSource(autoscrollMode);
1385 myAutoscrollToSource.put(paneId, autoscrollMode);
1389 public boolean isAutoscrollFromSource(String paneId) {
1390 if (isGlobalOptions()) {
1391 return getGlobalOptions().getAutoscrollFromSource();
1394 return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults);
1397 public void setAutoscrollFromSource(boolean autoscrollMode, String paneId) {
1398 if (isGlobalOptions()) {
1399 getGlobalOptions().setAutoscrollFromSource(autoscrollMode);
1401 setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false);
1405 public boolean isFlattenPackages(String paneId) {
1406 if (isGlobalOptions()) {
1407 return getGlobalOptions().getFlattenPackages();
1410 return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults);
1413 public void setFlattenPackages(boolean flattenPackages, String paneId) {
1414 if (isGlobalOptions()) {
1415 getGlobalOptions().setFlattenPackages(flattenPackages);
1416 for (String pane : myFlattenPackages.keySet()) {
1417 setPaneOption(myFlattenPackages, flattenPackages, pane, true);
1420 setPaneOption(myFlattenPackages, flattenPackages, paneId, true);
1423 public boolean isFoldersAlwaysOnTop() {
1424 if (isGlobalOptions()) {
1425 return getGlobalOptions().getFoldersAlwaysOnTop();
1428 return myFoldersAlwaysOnTop;
1431 public void setFoldersAlwaysOnTop(boolean foldersAlwaysOnTop) {
1432 if (isGlobalOptions()) {
1433 getGlobalOptions().setFoldersAlwaysOnTop(foldersAlwaysOnTop);
1436 if (myFoldersAlwaysOnTop != foldersAlwaysOnTop) {
1437 myFoldersAlwaysOnTop = foldersAlwaysOnTop;
1438 for (AbstractProjectViewPane pane : myId2Pane.values()) {
1439 if (pane.getTree() != null) {
1440 pane.updateFromRoot(false);
1447 public boolean isShowMembers(String paneId) {
1448 if (isGlobalOptions()) {
1449 return getGlobalOptions().getShowMembers();
1452 return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults);
1455 public void setShowMembers(boolean showMembers, String paneId) {
1456 setPaneOption(myShowMembers, showMembers, paneId, true);
1460 public boolean isHideEmptyMiddlePackages(String paneId) {
1461 if (isGlobalOptions()) {
1462 return getGlobalOptions().getHideEmptyPackages();
1465 return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults);
1469 public boolean isAbbreviatePackageNames(String paneId) {
1470 if (isGlobalOptions()) {
1471 return getGlobalOptions().getAbbreviatePackages();
1474 return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults);
1478 public boolean isShowLibraryContents(String paneId) {
1479 if (isGlobalOptions()) {
1480 return getGlobalOptions().getShowLibraryContents();
1483 return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults);
1487 public void setShowLibraryContents(boolean showLibraryContents, @NotNull String paneId) {
1488 if (isGlobalOptions()) {
1489 getGlobalOptions().setShowLibraryContents(showLibraryContents);
1491 setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1495 public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) {
1496 return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1500 public boolean isShowModules(String paneId) {
1501 if (isGlobalOptions()) {
1502 return getGlobalOptions().getShowModules();
1505 return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults);
1509 public void setShowModules(boolean showModules, @NotNull String paneId) {
1510 if (isGlobalOptions()) {
1511 getGlobalOptions().setShowModules(showModules);
1513 setPaneOption(myShowModules, showModules, paneId, true);
1517 public void setHideEmptyPackages(boolean hideEmptyPackages, @NotNull String paneId) {
1518 if (isGlobalOptions()) {
1519 getGlobalOptions().setHideEmptyPackages(hideEmptyPackages);
1520 for (String pane : myHideEmptyPackages.keySet()) {
1521 setPaneOption(myHideEmptyPackages, hideEmptyPackages, pane, true);
1524 setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true);
1528 public void setAbbreviatePackageNames(boolean abbreviatePackageNames, @NotNull String paneId) {
1529 if (isGlobalOptions()) {
1530 getGlobalOptions().setAbbreviatePackages(abbreviatePackageNames);
1532 setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true);
1536 private ActionCallback setPaneOption(@NotNull Map<String, Boolean> optionsMap, boolean value, String paneId, final boolean updatePane) {
1537 if (paneId != null) {
1538 optionsMap.put(paneId, value);
1540 final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1542 return pane.updateFromRoot(false);
1546 return ActionCallback.DONE;
1549 private static boolean getPaneOptionValue(@NotNull Map<String, Boolean> optionsMap, String paneId, boolean defaultValue) {
1550 final Boolean value = optionsMap.get(paneId);
1551 return value == null ? defaultValue : value.booleanValue();
1554 private class HideEmptyMiddlePackagesAction extends PaneOptionAction {
1555 private HideEmptyMiddlePackagesAction() {
1556 super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults);
1560 public void setSelected(AnActionEvent event, boolean flag) {
1561 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1562 final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
1564 if (isGlobalOptions()) {
1565 getGlobalOptions().setHideEmptyPackages(flag);
1567 super.setSelected(event, flag);
1569 selectionInfo.apply(viewPane);
1573 public boolean isSelected(AnActionEvent event) {
1574 if (isGlobalOptions()) return getGlobalOptions().getHideEmptyPackages();
1575 return super.isSelected(event);
1579 public void update(AnActionEvent e) {
1581 final Presentation presentation = e.getPresentation();
1582 if (isHideEmptyMiddlePackages(myCurrentViewId)) {
1583 presentation.setText(IdeBundle.message("action.hide.empty.middle.packages"));
1584 presentation.setDescription(IdeBundle.message("action.show.hide.empty.middle.packages"));
1587 presentation.setText(IdeBundle.message("action.compact.empty.middle.packages"));
1588 presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages"));
1593 private static class SelectionInfo {
1594 private final Object[] myElements;
1596 private SelectionInfo(@NotNull Object[] elements) {
1597 myElements = elements;
1600 public void apply(final AbstractProjectViewPane viewPane) {
1601 if (viewPane == null) {
1604 AbstractTreeBuilder treeBuilder = viewPane.getTreeBuilder();
1605 JTree tree = viewPane.myTree;
1606 DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
1607 List<TreePath> paths = new ArrayList<>(myElements.length);
1608 for (final Object element : myElements) {
1609 DefaultMutableTreeNode node = treeBuilder.getNodeForElement(element);
1611 treeBuilder.buildNodeForElement(element);
1612 node = treeBuilder.getNodeForElement(element);
1615 paths.add(new TreePath(treeModel.getPathToRoot(node)));
1618 if (!paths.isEmpty()) {
1619 tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
1624 public static SelectionInfo create(final AbstractProjectViewPane viewPane) {
1625 List<Object> selectedElements = Collections.emptyList();
1626 if (viewPane != null) {
1627 final TreePath[] selectionPaths = viewPane.getSelectionPaths();
1628 if (selectionPaths != null) {
1629 selectedElements = new ArrayList<>();
1630 for (TreePath path : selectionPaths) {
1631 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1632 final Object userObject = node.getUserObject();
1633 if (userObject instanceof NodeDescriptor) {
1634 selectedElements.add(((NodeDescriptor)userObject).getElement());
1639 return new SelectionInfo(selectedElements.toArray());
1643 private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
1644 private MyAutoScrollFromSourceHandler() {
1645 super(ProjectViewImpl.this.myProject, myViewContentPanel, ProjectViewImpl.this);
1649 protected void selectElementFromEditor(@NotNull FileEditor fileEditor) {
1650 if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return;
1651 if (isAutoscrollFromSource(getCurrentViewId())) {
1652 if (fileEditor instanceof TextEditor) {
1653 Editor editor = ((TextEditor)fileEditor).getEditor();
1654 selectElementAtCaretNotLosingFocus(editor);
1657 final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(fileEditor);
1659 final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file);
1660 if (psiFile != null) {
1661 final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1662 if (target != null) {
1663 final MySelectInContext selectInContext = new MySelectInContext(psiFile, null) {
1665 public Object getSelectorInFile() {
1670 if (target.canSelect(selectInContext)) {
1671 target.selectIn(selectInContext, false);
1680 public void scrollFromSource() {
1681 final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
1682 final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor();
1683 if (selectedTextEditor != null) {
1684 selectElementAtCaret(selectedTextEditor);
1687 final FileEditor[] editors = fileEditorManager.getSelectedEditors();
1688 for (FileEditor fileEditor : editors) {
1689 if (fileEditor instanceof TextEditor) {
1690 Editor editor = ((TextEditor)fileEditor).getEditor();
1691 selectElementAtCaret(editor);
1695 final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles();
1696 if (selectedFiles.length > 0) {
1697 final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]);
1699 scrollFromFile(file, null);
1704 private void selectElementAtCaretNotLosingFocus(@NotNull Editor editor) {
1705 AbstractProjectViewPane pane = getCurrentProjectViewPane();
1706 if (pane != null && !IJSwingUtilities.hasFocus(pane.getComponentToFocus())) {
1707 selectElementAtCaret(editor);
1711 private void selectElementAtCaret(@NotNull Editor editor) {
1712 final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
1713 if (file == null) return;
1715 scrollFromFile(file, editor);
1718 private void scrollFromFile(@NotNull PsiFile file, @Nullable Editor editor) {
1719 PsiDocumentManager.getInstance(myProject).performWhenAllCommitted(() -> {
1720 final MySelectInContext selectInContext = new MySelectInContext(file, editor);
1722 final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1723 if (target != null && target.canSelect(selectInContext)) {
1724 target.selectIn(selectInContext, false);
1730 protected boolean isAutoScrollEnabled() {
1731 return isAutoscrollFromSource(myCurrentViewId);
1735 protected void setAutoScrollEnabled(boolean state) {
1736 setAutoscrollFromSource(state, myCurrentViewId);
1738 final Editor editor = myFileEditorManager.getSelectedTextEditor();
1739 if (editor != null) {
1740 selectElementAtCaretNotLosingFocus(editor);
1743 createToolbarActions();
1746 private class MySelectInContext implements SelectInContext {
1747 @NotNull private final PsiFile myPsiFile;
1748 @Nullable private final Editor myEditor;
1750 private MySelectInContext(@NotNull PsiFile psiFile, @Nullable Editor editor) {
1751 myPsiFile = psiFile;
1757 public Project getProject() {
1762 private PsiFile getPsiFile() {
1768 public FileEditorProvider getFileEditorProvider() {
1769 return new FileEditorProvider() {
1771 public FileEditor openFileEditor() {
1772 return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0];
1778 private PsiElement getPsiElement() {
1779 PsiElement e = null;
1780 if (myEditor != null) {
1781 final int offset = myEditor.getCaretModel().getOffset();
1782 if (PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) {
1783 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1785 e = getPsiFile().findElementAt(offset);
1795 public VirtualFile getVirtualFile() {
1796 return getPsiFile().getVirtualFile();
1800 public Object getSelectorInFile() {
1801 return getPsiElement();
1807 public boolean isManualOrder(String paneId) {
1808 return getPaneOptionValue(myManualOrder, paneId, ourManualOrderDefaults);
1812 public void setManualOrder(@NotNull String paneId, final boolean enabled) {
1813 setPaneOption(myManualOrder, enabled, paneId, false);
1814 final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1815 pane.installComparator();
1819 public boolean isSortByType(String paneId) {
1820 return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults);
1824 public void setSortByType(@NotNull String paneId, final boolean sortByType) {
1825 setPaneOption(mySortByType, sortByType, paneId, false);
1826 final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1827 pane.installComparator();
1830 private class ManualOrderAction extends ToggleAction implements DumbAware {
1831 private ManualOrderAction() {
1832 super(IdeBundle.message("action.manual.order"), IdeBundle.message("action.manual.order"), AllIcons.ObjectBrowser.Sorted);
1836 public boolean isSelected(AnActionEvent event) {
1837 return isManualOrder(getCurrentViewId());
1841 public void setSelected(AnActionEvent event, boolean flag) {
1842 setManualOrder(getCurrentViewId(), flag);
1846 public void update(final AnActionEvent e) {
1848 final Presentation presentation = e.getPresentation();
1849 AbstractProjectViewPane pane = getCurrentProjectViewPane();
1850 presentation.setEnabledAndVisible(pane != null && pane.supportsManualOrder());
1854 private class SortByTypeAction extends ToggleAction implements DumbAware {
1855 private SortByTypeAction() {
1856 super(IdeBundle.message("action.sort.by.type"), IdeBundle.message("action.sort.by.type"), AllIcons.ObjectBrowser.SortByType);
1860 public boolean isSelected(AnActionEvent event) {
1861 return isSortByType(getCurrentViewId());
1865 public void setSelected(AnActionEvent event, boolean flag) {
1866 setSortByType(getCurrentViewId(), flag);
1870 public void update(final AnActionEvent e) {
1872 final Presentation presentation = e.getPresentation();
1873 presentation.setVisible(getCurrentProjectViewPane() != null);
1877 private class FoldersAlwaysOnTopAction extends ToggleAction implements DumbAware {
1878 private FoldersAlwaysOnTopAction() {
1879 super("Folders Always on Top");
1883 public boolean isSelected(AnActionEvent event) {
1884 return isFoldersAlwaysOnTop();
1888 public void setSelected(AnActionEvent event, boolean flag) {
1889 setFoldersAlwaysOnTop(flag);
1893 public void update(final AnActionEvent e) {
1895 final Presentation presentation = e.getPresentation();
1896 presentation.setEnabledAndVisible(getCurrentProjectViewPane() != null);
1900 private class ScrollFromSourceAction extends AnAction implements DumbAware {
1901 private ScrollFromSourceAction() {
1902 super("Scroll from Source", "Select the file open in the active editor", AllIcons.General.Locate);
1903 getTemplatePresentation().setHoveredIcon(AllIcons.General.LocateHover);
1907 public void actionPerformed(AnActionEvent e) {
1908 myAutoScrollFromSourceHandler.scrollFromSource();
1914 public Collection<String> getPaneIds() {
1915 return myId2Pane.keySet();
1920 public Collection<SelectInTarget> getSelectInTargets() {
1921 ensurePanesLoaded();
1922 return mySelectInTargets.values();
1927 public ActionCallback getReady(@NotNull Object requestor) {
1928 AbstractProjectViewPane pane = myId2Pane.get(myCurrentViewSubId);
1930 pane = myId2Pane.get(myCurrentViewId);
1932 return pane != null ? pane.getReady(requestor) : ActionCallback.DONE;