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 if (myActionGroup == null) return;
526 List<AnAction> titleActions = ContainerUtil.newSmartList();
527 myActionGroup.removeAll();
528 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
529 myActionGroup.addAction(new PaneOptionAction(myFlattenPackages, IdeBundle.message("action.flatten.packages"),
530 IdeBundle.message("action.flatten.packages"), PlatformIcons.FLATTEN_PACKAGES_ICON,
531 ourFlattenPackagesDefaults) {
533 public void setSelected(AnActionEvent event, boolean flag) {
534 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
535 final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
536 if (isGlobalOptions()) {
537 setFlattenPackages(flag, viewPane.getId());
539 super.setSelected(event, flag);
541 selectionInfo.apply(viewPane);
545 public boolean isSelected(AnActionEvent event) {
546 if (isGlobalOptions()) return getGlobalOptions().getFlattenPackages();
547 return super.isSelected(event);
549 }).setAsSecondary(true);
552 class FlattenPackagesDependableAction extends PaneOptionAction {
553 FlattenPackagesDependableAction(@NotNull Map<String, Boolean> optionsMap,
554 @NotNull String text,
555 @NotNull String description,
557 boolean optionDefaultValue) {
558 super(optionsMap, text, description, icon, optionDefaultValue);
562 public void setSelected(AnActionEvent event, boolean flag) {
563 if (isGlobalOptions()) {
564 getGlobalOptions().setFlattenPackages(flag);
566 super.setSelected(event, flag);
570 public void update(AnActionEvent e) {
572 final Presentation presentation = e.getPresentation();
573 presentation.setVisible(isFlattenPackages(myCurrentViewId));
576 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) {
577 myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true);
579 if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
580 myActionGroup.addAction(new FlattenPackagesDependableAction(myAbbreviatePackageNames,
581 IdeBundle.message("action.abbreviate.qualified.package.names"),
582 IdeBundle.message("action.abbreviate.qualified.package.names"),
583 AllIcons.ObjectBrowser.AbbreviatePackageNames,
584 ourAbbreviatePackagesDefaults) {
586 public boolean isSelected(AnActionEvent event) {
587 return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId);
592 public void update(AnActionEvent e) {
594 if (ScopeViewPane.ID.equals(myCurrentViewId)) {
595 e.getPresentation().setEnabled(false);
598 }).setAsSecondary(true);
601 if (isShowMembersOptionSupported()) {
602 myActionGroup.addAction(new PaneOptionAction(myShowMembers, IdeBundle.message("action.show.members"),
603 IdeBundle.message("action.show.hide.members"),
604 AllIcons.ObjectBrowser.ShowMembers, ourShowMembersDefaults) {
606 public boolean isSelected(AnActionEvent event) {
607 if (isGlobalOptions()) return getGlobalOptions().getShowMembers();
608 return super.isSelected(event);
612 public void setSelected(AnActionEvent event, boolean flag) {
613 if (isGlobalOptions()) {
614 getGlobalOptions().setShowMembers(flag);
616 super.setSelected(event, flag);
619 .setAsSecondary(true);
621 myActionGroup.addAction(myAutoScrollToSourceHandler.createToggleAction()).setAsSecondary(true);
622 myActionGroup.addAction(myAutoScrollFromSourceHandler.createToggleAction()).setAsSecondary(true);
623 myActionGroup.addAction(new ManualOrderAction()).setAsSecondary(true);
624 myActionGroup.addAction(new SortByTypeAction()).setAsSecondary(true);
625 myActionGroup.addAction(new FoldersAlwaysOnTopAction()).setAsSecondary(true);
627 if (!myAutoScrollFromSourceHandler.isAutoScrollEnabled()) {
628 titleActions.add(new ScrollFromSourceAction());
630 AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() {
632 public void expandAll() {
637 public boolean canExpand() {
642 public void collapseAll() {
643 AbstractProjectViewPane pane = getCurrentProjectViewPane();
644 JTree tree = pane.myTree;
646 TreeUtil.collapseAll(tree, 0);
651 public boolean canCollapse() {
655 collapseAllAction.getTemplatePresentation().setIcon(AllIcons.General.CollapseAll);
656 collapseAllAction.getTemplatePresentation().setHoveredIcon(AllIcons.General.CollapseAllHover);
657 titleActions.add(collapseAllAction);
658 getCurrentProjectViewPane().addToolbarActions(myActionGroup);
660 ToolWindowEx window = (ToolWindowEx)ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW);
661 if (window != null) {
662 window.setTitleActions(titleActions.toArray(new AnAction[titleActions.size()]));
666 protected boolean isShowMembersOptionSupported() {
671 public AbstractProjectViewPane getProjectViewPaneById(String id) {
672 if (!ApplicationManager.getApplication().isUnitTestMode()) { // most tests don't need all panes to be loaded
676 final AbstractProjectViewPane pane = myId2Pane.get(id);
680 for (AbstractProjectViewPane viewPane : myUninitializedPanes) {
681 if (viewPane.getId().equals(id)) {
689 public AbstractProjectViewPane getCurrentProjectViewPane() {
690 return getProjectViewPaneById(myCurrentViewId);
694 public void refresh() {
695 AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
696 if (currentProjectViewPane != null) {
697 // may be null for e.g. default project
698 currentProjectViewPane.updateFromRoot(false);
703 public void select(final Object element, VirtualFile file, boolean requestFocus) {
704 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
705 if (viewPane != null) {
706 viewPane.select(element, file, requestFocus);
712 public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) {
713 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
714 if (viewPane != null && viewPane instanceof AbstractProjectViewPSIPane) {
715 return ((AbstractProjectViewPSIPane)viewPane).selectCB(element, file, requestFocus);
717 select(element, file, requestFocus);
718 return ActionCallback.DONE;
722 public void dispose() {
723 myConnection.disconnect();
726 public JComponent getComponent() {
727 return myDataProvider;
731 public String getCurrentViewId() {
732 return myCurrentViewId;
736 public PsiElement getParentOfCurrentSelection() {
737 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
738 if (viewPane == null) {
741 TreePath path = viewPane.getSelectedPath();
745 path = path.getParentPath();
749 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
750 Object userObject = node.getUserObject();
751 if (userObject instanceof ProjectViewNode) {
752 ProjectViewNode descriptor = (ProjectViewNode)userObject;
753 Object element = descriptor.getValue();
754 if (element instanceof PsiElement) {
755 PsiElement psiElement = (PsiElement)element;
756 if (!psiElement.isValid()) return null;
766 public ContentManager getContentManager() {
767 if (myContentManager == null) {
768 ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).getContentManager();
770 return myContentManager;
774 private class PaneOptionAction extends ToggleAction implements DumbAware {
775 private final Map<String, Boolean> myOptionsMap;
776 private final boolean myOptionDefaultValue;
778 PaneOptionAction(@NotNull Map<String, Boolean> optionsMap,
779 @NotNull String text,
780 @NotNull String description,
782 boolean optionDefaultValue) {
783 super(text, description, icon);
784 myOptionsMap = optionsMap;
785 myOptionDefaultValue = optionDefaultValue;
789 public boolean isSelected(AnActionEvent event) {
790 return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue);
794 public void setSelected(AnActionEvent event, boolean flag) {
795 setPaneOption(myOptionsMap, flag, myCurrentViewId, true);
800 public void changeView() {
801 final List<AbstractProjectViewPane> views = new ArrayList<>(myId2Pane.values());
802 views.remove(getCurrentProjectViewPane());
803 Collections.sort(views, PANE_WEIGHT_COMPARATOR);
805 final JList list = new JBList(ArrayUtil.toObjectArray(views));
806 list.setCellRenderer(new DefaultListCellRenderer() {
808 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
809 super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
810 AbstractProjectViewPane pane = (AbstractProjectViewPane)value;
811 setText(pane.getTitle());
816 if (!views.isEmpty()) {
817 list.setSelectedValue(views.get(0), true);
819 Runnable runnable = () -> {
820 if (list.getSelectedIndex() < 0) return;
821 AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue();
822 changeView(pane.getId());
825 new PopupChooserBuilder(list).
826 setTitle(IdeBundle.message("title.popup.views")).
827 setItemChoosenCallback(runnable).
828 createPopup().showInCenterOf(getComponent());
832 public void changeView(@NotNull String viewId) {
833 changeView(viewId, null);
837 public void changeView(@NotNull String viewId, @Nullable String subId) {
838 changeViewCB(viewId, subId);
843 public ActionCallback changeViewCB(@NotNull String viewId, String subId) {
844 AbstractProjectViewPane pane = getProjectViewPaneById(viewId);
845 LOG.assertTrue(pane != null, "Project view pane not found: " + viewId + "; subId:" + subId);
846 if (!viewId.equals(getCurrentViewId())
847 || subId != null && !subId.equals(pane.getSubId())) {
848 for (Content content : getContentManager().getContents()) {
849 if (viewId.equals(content.getUserData(ID_KEY)) && StringUtil.equals(subId, content.getUserData(SUB_ID_KEY))) {
850 return getContentManager().setSelectedContentCB(content);
854 return ActionCallback.REJECTED;
857 private final class MyDeletePSIElementProvider implements DeleteProvider {
859 public boolean canDeleteElement(@NotNull DataContext dataContext) {
860 final PsiElement[] elements = getElementsToDelete();
861 return DeleteHandler.shouldEnableDeleteAction(elements);
865 public void deleteElement(@NotNull DataContext dataContext) {
866 List<PsiElement> allElements = Arrays.asList(getElementsToDelete());
867 List<PsiElement> validElements = new ArrayList<>();
868 for (PsiElement psiElement : allElements) {
869 if (psiElement != null && psiElement.isValid()) validElements.add(psiElement);
871 final PsiElement[] elements = PsiUtilCore.toPsiElementArray(validElements);
873 LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
875 DeleteHandler.deletePsiElement(elements, myProject);
883 private PsiElement[] getElementsToDelete() {
884 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
885 PsiElement[] elements = viewPane.getSelectedPSIElements();
886 for (int idx = 0; idx < elements.length; idx++) {
887 final PsiElement element = elements[idx];
888 if (element instanceof PsiDirectory) {
889 PsiDirectory directory = (PsiDirectory)element;
890 final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject);
891 if (isHideEmptyMiddlePackages(viewPane.getId()) && directory.getChildren().length == 0 && !directoryHelper.skipDirectory(directory)) {
893 PsiDirectory parent = directory.getParentDirectory();
894 if (parent == null) break;
895 if (directoryHelper.skipDirectory(parent) || PsiDirectoryFactory.getInstance(myProject).getQualifiedName(parent, false).length() == 0) break;
896 PsiElement[] children = parent.getChildren();
897 if (children.length == 0 || children.length == 1 && children[0] == directory) {
904 elements[idx] = directory;
906 final VirtualFile virtualFile = directory.getVirtualFile();
907 final String path = virtualFile.getPath();
908 if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root
909 final VirtualFile vFile =
910 LocalFileSystem.getInstance().findFileByPath(path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length()));
912 final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
913 if (psiFile != null) {
914 elements[idx] = psiFile;
925 private final class MyPanel extends JPanel implements DataProvider {
927 super(new BorderLayout());
931 private Object getSelectedNodeElement() {
932 final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
933 if (currentProjectViewPane == null) { // can happen if not initialized yet
936 DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode();
940 Object userObject = node.getUserObject();
941 if (userObject instanceof AbstractTreeNode) {
942 return ((AbstractTreeNode)userObject).getValue();
944 if (!(userObject instanceof NodeDescriptor)) {
947 return ((NodeDescriptor)userObject).getElement();
951 public Object getData(String dataId) {
952 final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
953 if (currentProjectViewPane != null) {
954 final Object paneSpecificData = currentProjectViewPane.getData(dataId);
955 if (paneSpecificData != null) return paneSpecificData;
958 if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
959 if (currentProjectViewPane == null) return null;
960 final PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
961 return elements.length == 1 ? elements[0] : null;
963 if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
964 if (currentProjectViewPane == null) {
967 PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
968 return elements.length == 0 ? null : elements;
970 if (LangDataKeys.MODULE.is(dataId)) {
971 VirtualFile[] virtualFiles = (VirtualFile[])getData(CommonDataKeys.VIRTUAL_FILE_ARRAY.getName());
972 if (virtualFiles == null || virtualFiles.length <= 1) return null;
973 final Set<Module> modules = new HashSet<>();
974 for (VirtualFile virtualFile : virtualFiles) {
975 modules.add(ModuleUtilCore.findModuleForFile(virtualFile, myProject));
977 return modules.size() == 1 ? modules.iterator().next() : null;
979 if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
982 if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
983 return myCopyPasteDelegator.getCutProvider();
985 if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
986 return myCopyPasteDelegator.getCopyProvider();
988 if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
989 return myCopyPasteDelegator.getPasteProvider();
991 if (LangDataKeys.IDE_VIEW.is(dataId)) {
994 if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
995 final Module[] modules = getSelectedModules();
996 if (modules != null) {
997 return myDeleteModuleProvider;
999 final LibraryOrderEntry orderEntry = getSelectedLibrary();
1000 if (orderEntry != null) {
1001 return new DeleteProvider() {
1003 public void deleteElement(@NotNull DataContext dataContext) {
1004 detachLibrary(orderEntry, myProject);
1008 public boolean canDeleteElement(@NotNull DataContext dataContext) {
1013 return myDeletePSIElementProvider;
1015 if (PlatformDataKeys.HELP_ID.is(dataId)) {
1016 return HelpID.PROJECT_VIEWS;
1018 if (ProjectViewImpl.DATA_KEY.is(dataId)) {
1019 return ProjectViewImpl.this;
1021 if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) {
1022 Object selected = getSelectedNodeElement();
1023 return selected instanceof Project ? selected : null;
1025 if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
1026 Object selected = getSelectedNodeElement();
1027 if (selected instanceof Module) {
1028 return !((Module)selected).isDisposed() ? selected : null;
1030 else if (selected instanceof PsiDirectory) {
1031 return moduleBySingleContentRoot(((PsiDirectory)selected).getVirtualFile());
1033 else if (selected instanceof VirtualFile) {
1034 return moduleBySingleContentRoot((VirtualFile)selected);
1041 if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
1042 return getSelectedModules();
1044 if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
1045 final List<ModuleGroup> selectedElements = getSelectedElements(ModuleGroup.class);
1046 return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]);
1048 if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
1049 final List<LibraryGroupElement> selectedElements = getSelectedElements(LibraryGroupElement.class);
1050 return selectedElements.isEmpty() ? null : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]);
1052 if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
1053 final List<NamedLibraryElement> selectedElements = getSelectedElements(NamedLibraryElement.class);
1054 return selectedElements.isEmpty() ? null : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]);
1061 private LibraryOrderEntry getSelectedLibrary() {
1062 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1063 DefaultMutableTreeNode node = viewPane != null ? viewPane.getSelectedNode() : null;
1064 if (node == null) return null;
1065 DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
1066 if (parent == null) return null;
1067 Object userObject = parent.getUserObject();
1068 if (userObject instanceof LibraryGroupNode) {
1069 userObject = node.getUserObject();
1070 if (userObject instanceof NamedLibraryElementNode) {
1071 NamedLibraryElement element = ((NamedLibraryElementNode)userObject).getValue();
1072 OrderEntry orderEntry = element.getOrderEntry();
1073 return orderEntry instanceof LibraryOrderEntry ? (LibraryOrderEntry)orderEntry : null;
1075 PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue();
1076 VirtualFile virtualFile = directory.getVirtualFile();
1077 Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue();
1079 if (module == null) return null;
1080 ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
1081 OrderEntry entry = index.getOrderEntryForFile(virtualFile);
1082 if (entry instanceof LibraryOrderEntry) {
1083 return (LibraryOrderEntry)entry;
1090 private void detachLibrary(@NotNull final LibraryOrderEntry orderEntry, @NotNull Project project) {
1091 final Module module = orderEntry.getOwnerModule();
1092 String message = IdeBundle.message("detach.library.from.module", orderEntry.getPresentableName(), module.getName());
1093 String title = IdeBundle.message("detach.library");
1094 int ret = Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon());
1095 if (ret != Messages.OK) return;
1096 CommandProcessor.getInstance().executeCommand(module.getProject(), () -> {
1097 final Runnable action = () -> {
1098 ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
1099 OrderEntry[] orderEntries = rootManager.getOrderEntries();
1100 ModifiableRootModel model = rootManager.getModifiableModel();
1101 OrderEntry[] modifiableEntries = model.getOrderEntries();
1102 for (int i = 0; i < orderEntries.length; i++) {
1103 OrderEntry entry = orderEntries[i];
1104 if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == orderEntry.getLibrary()) {
1105 model.removeOrderEntry(modifiableEntries[i]);
1110 ApplicationManager.getApplication().runWriteAction(action);
1115 private Module[] getSelectedModules() {
1116 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1117 if (viewPane == null) return null;
1118 final Object[] elements = viewPane.getSelectedElements();
1119 ArrayList<Module> result = new ArrayList<>();
1120 for (Object element : elements) {
1121 if (element instanceof Module) {
1122 final Module module = (Module)element;
1123 if (!module.isDisposed()) {
1127 else if (element instanceof ModuleGroup) {
1128 Collection<Module> modules = ((ModuleGroup)element).modulesInGroup(myProject, true);
1129 result.addAll(modules);
1131 else if (element instanceof PsiDirectory) {
1132 Module module = moduleBySingleContentRoot(((PsiDirectory)element).getVirtualFile());
1133 if (module != null) result.add(module);
1135 else if (element instanceof VirtualFile) {
1136 Module module = moduleBySingleContentRoot((VirtualFile)element);
1137 if (module != null) result.add(module);
1141 if (result.isEmpty()) {
1145 return result.toArray(new Module[result.size()]);
1150 /** Project view has the same node for module and its single content root
1151 * => MODULE_CONTEXT data key should return the module when its content root is selected
1152 * When there are multiple content roots, they have different nodes under the module node
1153 * => MODULE_CONTEXT should be only available for the module node
1154 * otherwise VirtualFileArrayRule will return all module's content roots when just one of them is selected
1157 private Module moduleBySingleContentRoot(@NotNull VirtualFile file) {
1158 if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) {
1159 Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file);
1160 if (module != null && !module.isDisposed() && ModuleRootManager.getInstance(module).getContentRoots().length == 1) {
1169 private <T> List<T> getSelectedElements(@NotNull Class<T> klass) {
1170 List<T> result = new ArrayList<>();
1171 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1172 if (viewPane == null) return result;
1173 final Object[] elements = viewPane.getSelectedElements();
1174 for (Object element : elements) {
1175 //element still valid
1176 if (element != null && klass.isAssignableFrom(element.getClass())) {
1177 result.add((T)element);
1183 private final class MyIdeView implements IdeView {
1185 public void selectElement(PsiElement element) {
1186 selectPsiElement(element, false);
1187 boolean requestFocus = true;
1188 if (element != null) {
1189 final boolean isDirectory = element instanceof PsiDirectory;
1191 FileEditor editor = EditorHelper.openInEditor(element, false);
1192 if (editor != null) {
1193 ToolWindowManager.getInstance(myProject).activateEditorComponent();
1194 requestFocus = false;
1200 selectPsiElement(element, true);
1206 public PsiDirectory[] getDirectories() {
1207 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1208 if (viewPane != null) {
1209 return viewPane.getSelectedDirectories();
1212 return PsiDirectory.EMPTY_ARRAY;
1216 public PsiDirectory getOrChooseDirectory() {
1217 return DirectoryChooserUtil.getOrChooseDirectory(this);
1222 public void selectPsiElement(PsiElement element, boolean requestFocus) {
1223 if (element == null) return;
1224 VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
1225 select(element, virtualFile, requestFocus);
1229 private static void readOption(Element node, @NotNull Map<String, Boolean> options) {
1230 if (node == null) return;
1231 for (Attribute attribute : node.getAttributes()) {
1232 options.put(attribute.getName(), Boolean.TRUE.toString().equals(attribute.getValue()) ? Boolean.TRUE : Boolean.FALSE);
1236 private static void writeOption(@NotNull Element parentNode, @NotNull Map<String, Boolean> optionsForPanes, @NotNull String optionName) {
1237 Element e = new Element(optionName);
1238 for (Map.Entry<String, Boolean> entry : optionsForPanes.entrySet()) {
1239 final String key = entry.getKey();
1240 if (key != null) { //SCR48267
1241 e.setAttribute(key, Boolean.toString(entry.getValue().booleanValue()));
1245 parentNode.addContent(e);
1249 public void loadState(Element parentNode) {
1250 Element navigatorElement = parentNode.getChild(ELEMENT_NAVIGATOR);
1251 if (navigatorElement != null) {
1252 mySavedPaneId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_VIEW);
1253 mySavedPaneSubId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_SUBVIEW);
1254 if (mySavedPaneId == null) {
1255 mySavedPaneId = ProjectViewPane.ID;
1256 mySavedPaneSubId = null;
1258 readOption(navigatorElement.getChild(ELEMENT_FLATTEN_PACKAGES), myFlattenPackages);
1259 readOption(navigatorElement.getChild(ELEMENT_SHOW_MEMBERS), myShowMembers);
1260 readOption(navigatorElement.getChild(ELEMENT_SHOW_MODULES), myShowModules);
1261 readOption(navigatorElement.getChild(ELEMENT_SHOW_LIBRARY_CONTENTS), myShowLibraryContents);
1262 readOption(navigatorElement.getChild(ELEMENT_HIDE_EMPTY_PACKAGES), myHideEmptyPackages);
1263 readOption(navigatorElement.getChild(ELEMENT_ABBREVIATE_PACKAGE_NAMES), myAbbreviatePackageNames);
1264 readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_TO_SOURCE), myAutoscrollToSource);
1265 readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_FROM_SOURCE), myAutoscrollFromSource);
1266 readOption(navigatorElement.getChild(ELEMENT_SORT_BY_TYPE), mySortByType);
1267 readOption(navigatorElement.getChild(ELEMENT_MANUAL_ORDER), myManualOrder);
1269 Element foldersElement = navigatorElement.getChild(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1270 if (foldersElement != null) myFoldersAlwaysOnTop = Boolean.valueOf(foldersElement.getAttributeValue("value"));
1273 splitterProportions.readExternal(navigatorElement);
1275 catch (InvalidDataException e) {
1279 Element panesElement = parentNode.getChild(ELEMENT_PANES);
1280 if (panesElement != null) {
1281 readPaneState(panesElement);
1285 private void readPaneState(@NotNull Element panesElement) {
1286 @SuppressWarnings({"unchecked"})
1287 final List<Element> paneElements = panesElement.getChildren(ELEMENT_PANE);
1289 for (Element paneElement : paneElements) {
1290 String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID);
1291 final AbstractProjectViewPane pane = myId2Pane.get(paneId);
1294 pane.readExternal(paneElement);
1296 catch (InvalidDataException e) {
1301 myUninitializedPaneState.put(paneId, paneElement);
1307 public Element getState() {
1308 Element parentNode = new Element("projectView");
1309 Element navigatorElement = new Element(ELEMENT_NAVIGATOR);
1310 AbstractProjectViewPane currentPane = getCurrentProjectViewPane();
1311 if (currentPane != null) {
1312 navigatorElement.setAttribute(ATTRIBUTE_CURRENT_VIEW, currentPane.getId());
1313 String subId = currentPane.getSubId();
1314 if (subId != null) {
1315 navigatorElement.setAttribute(ATTRIBUTE_CURRENT_SUBVIEW, subId);
1318 writeOption(navigatorElement, myFlattenPackages, ELEMENT_FLATTEN_PACKAGES);
1319 writeOption(navigatorElement, myShowMembers, ELEMENT_SHOW_MEMBERS);
1320 writeOption(navigatorElement, myShowModules, ELEMENT_SHOW_MODULES);
1321 writeOption(navigatorElement, myShowLibraryContents, ELEMENT_SHOW_LIBRARY_CONTENTS);
1322 writeOption(navigatorElement, myHideEmptyPackages, ELEMENT_HIDE_EMPTY_PACKAGES);
1323 writeOption(navigatorElement, myAbbreviatePackageNames, ELEMENT_ABBREVIATE_PACKAGE_NAMES);
1324 writeOption(navigatorElement, myAutoscrollToSource, ELEMENT_AUTOSCROLL_TO_SOURCE);
1325 writeOption(navigatorElement, myAutoscrollFromSource, ELEMENT_AUTOSCROLL_FROM_SOURCE);
1326 writeOption(navigatorElement, mySortByType, ELEMENT_SORT_BY_TYPE);
1327 writeOption(navigatorElement, myManualOrder, ELEMENT_MANUAL_ORDER);
1329 Element foldersElement = new Element(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1330 foldersElement.setAttribute("value", Boolean.toString(myFoldersAlwaysOnTop));
1331 navigatorElement.addContent(foldersElement);
1333 splitterProportions.saveSplitterProportions(myPanel);
1335 splitterProportions.writeExternal(navigatorElement);
1337 catch (WriteExternalException e) {
1340 parentNode.addContent(navigatorElement);
1342 Element panesElement = new Element(ELEMENT_PANES);
1343 writePaneState(panesElement);
1344 parentNode.addContent(panesElement);
1348 private void writePaneState(@NotNull Element panesElement) {
1349 for (AbstractProjectViewPane pane : myId2Pane.values()) {
1350 Element paneElement = new Element(ELEMENT_PANE);
1351 paneElement.setAttribute(ATTRIBUTE_ID, pane.getId());
1353 pane.writeExternal(paneElement);
1355 catch (WriteExternalException e) {
1358 panesElement.addContent(paneElement);
1360 for (Element element : myUninitializedPaneState.values()) {
1361 panesElement.addContent(element.clone());
1365 boolean isGlobalOptions() {
1366 return Registry.is("ide.projectView.globalOptions");
1369 ProjectViewSharedSettings getGlobalOptions() {
1370 return ProjectViewSharedSettings.Companion.getInstance();
1374 public boolean isAutoscrollToSource(String paneId) {
1375 if (isGlobalOptions()) {
1376 return getGlobalOptions().getAutoscrollToSource();
1379 return getPaneOptionValue(myAutoscrollToSource, paneId, UISettings.getInstance().DEFAULT_AUTOSCROLL_TO_SOURCE);
1382 public void setAutoscrollToSource(boolean autoscrollMode, String paneId) {
1383 if (isGlobalOptions()) {
1384 getGlobalOptions().setAutoscrollToSource(autoscrollMode);
1386 myAutoscrollToSource.put(paneId, autoscrollMode);
1390 public boolean isAutoscrollFromSource(String paneId) {
1391 if (isGlobalOptions()) {
1392 return getGlobalOptions().getAutoscrollFromSource();
1395 return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults);
1398 public void setAutoscrollFromSource(boolean autoscrollMode, String paneId) {
1399 if (isGlobalOptions()) {
1400 getGlobalOptions().setAutoscrollFromSource(autoscrollMode);
1402 setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false);
1406 public boolean isFlattenPackages(String paneId) {
1407 if (isGlobalOptions()) {
1408 return getGlobalOptions().getFlattenPackages();
1411 return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults);
1414 public void setFlattenPackages(boolean flattenPackages, String paneId) {
1415 if (isGlobalOptions()) {
1416 getGlobalOptions().setFlattenPackages(flattenPackages);
1417 for (String pane : myFlattenPackages.keySet()) {
1418 setPaneOption(myFlattenPackages, flattenPackages, pane, true);
1421 setPaneOption(myFlattenPackages, flattenPackages, paneId, true);
1424 public boolean isFoldersAlwaysOnTop() {
1425 if (isGlobalOptions()) {
1426 return getGlobalOptions().getFoldersAlwaysOnTop();
1429 return myFoldersAlwaysOnTop;
1432 public void setFoldersAlwaysOnTop(boolean foldersAlwaysOnTop) {
1433 if (isGlobalOptions()) {
1434 getGlobalOptions().setFoldersAlwaysOnTop(foldersAlwaysOnTop);
1437 if (myFoldersAlwaysOnTop != foldersAlwaysOnTop) {
1438 myFoldersAlwaysOnTop = foldersAlwaysOnTop;
1439 for (AbstractProjectViewPane pane : myId2Pane.values()) {
1440 if (pane.getTree() != null) {
1441 pane.updateFromRoot(false);
1448 public boolean isShowMembers(String paneId) {
1449 if (isGlobalOptions()) {
1450 return getGlobalOptions().getShowMembers();
1453 return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults);
1456 public void setShowMembers(boolean showMembers, String paneId) {
1457 setPaneOption(myShowMembers, showMembers, paneId, true);
1461 public boolean isHideEmptyMiddlePackages(String paneId) {
1462 if (isGlobalOptions()) {
1463 return getGlobalOptions().getHideEmptyPackages();
1466 return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults);
1470 public boolean isAbbreviatePackageNames(String paneId) {
1471 if (isGlobalOptions()) {
1472 return getGlobalOptions().getAbbreviatePackages();
1475 return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults);
1479 public boolean isShowLibraryContents(String paneId) {
1480 if (isGlobalOptions()) {
1481 return getGlobalOptions().getShowLibraryContents();
1484 return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults);
1488 public void setShowLibraryContents(boolean showLibraryContents, @NotNull String paneId) {
1489 if (isGlobalOptions()) {
1490 getGlobalOptions().setShowLibraryContents(showLibraryContents);
1492 setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1496 public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) {
1497 return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1501 public boolean isShowModules(String paneId) {
1502 if (isGlobalOptions()) {
1503 return getGlobalOptions().getShowModules();
1506 return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults);
1510 public void setShowModules(boolean showModules, @NotNull String paneId) {
1511 if (isGlobalOptions()) {
1512 getGlobalOptions().setShowModules(showModules);
1514 setPaneOption(myShowModules, showModules, paneId, true);
1518 public void setHideEmptyPackages(boolean hideEmptyPackages, @NotNull String paneId) {
1519 if (isGlobalOptions()) {
1520 getGlobalOptions().setHideEmptyPackages(hideEmptyPackages);
1521 for (String pane : myHideEmptyPackages.keySet()) {
1522 setPaneOption(myHideEmptyPackages, hideEmptyPackages, pane, true);
1525 setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true);
1529 public void setAbbreviatePackageNames(boolean abbreviatePackageNames, @NotNull String paneId) {
1530 if (isGlobalOptions()) {
1531 getGlobalOptions().setAbbreviatePackages(abbreviatePackageNames);
1533 setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true);
1537 private ActionCallback setPaneOption(@NotNull Map<String, Boolean> optionsMap, boolean value, String paneId, final boolean updatePane) {
1538 if (paneId != null) {
1539 optionsMap.put(paneId, value);
1541 final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1543 return pane.updateFromRoot(false);
1547 return ActionCallback.DONE;
1550 private static boolean getPaneOptionValue(@NotNull Map<String, Boolean> optionsMap, String paneId, boolean defaultValue) {
1551 final Boolean value = optionsMap.get(paneId);
1552 return value == null ? defaultValue : value.booleanValue();
1555 private class HideEmptyMiddlePackagesAction extends PaneOptionAction {
1556 private HideEmptyMiddlePackagesAction() {
1557 super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults);
1561 public void setSelected(AnActionEvent event, boolean flag) {
1562 final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1563 final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
1565 if (isGlobalOptions()) {
1566 getGlobalOptions().setHideEmptyPackages(flag);
1568 super.setSelected(event, flag);
1570 selectionInfo.apply(viewPane);
1574 public boolean isSelected(AnActionEvent event) {
1575 if (isGlobalOptions()) return getGlobalOptions().getHideEmptyPackages();
1576 return super.isSelected(event);
1580 public void update(AnActionEvent e) {
1582 final Presentation presentation = e.getPresentation();
1583 if (isHideEmptyMiddlePackages(myCurrentViewId)) {
1584 presentation.setText(IdeBundle.message("action.hide.empty.middle.packages"));
1585 presentation.setDescription(IdeBundle.message("action.show.hide.empty.middle.packages"));
1588 presentation.setText(IdeBundle.message("action.compact.empty.middle.packages"));
1589 presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages"));
1594 private static class SelectionInfo {
1595 private final Object[] myElements;
1597 private SelectionInfo(@NotNull Object[] elements) {
1598 myElements = elements;
1601 public void apply(final AbstractProjectViewPane viewPane) {
1602 if (viewPane == null) {
1605 AbstractTreeBuilder treeBuilder = viewPane.getTreeBuilder();
1606 JTree tree = viewPane.myTree;
1607 DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
1608 List<TreePath> paths = new ArrayList<>(myElements.length);
1609 for (final Object element : myElements) {
1610 DefaultMutableTreeNode node = treeBuilder.getNodeForElement(element);
1612 treeBuilder.buildNodeForElement(element);
1613 node = treeBuilder.getNodeForElement(element);
1616 paths.add(new TreePath(treeModel.getPathToRoot(node)));
1619 if (!paths.isEmpty()) {
1620 tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
1625 public static SelectionInfo create(final AbstractProjectViewPane viewPane) {
1626 List<Object> selectedElements = Collections.emptyList();
1627 if (viewPane != null) {
1628 final TreePath[] selectionPaths = viewPane.getSelectionPaths();
1629 if (selectionPaths != null) {
1630 selectedElements = new ArrayList<>();
1631 for (TreePath path : selectionPaths) {
1632 final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1633 final Object userObject = node.getUserObject();
1634 if (userObject instanceof NodeDescriptor) {
1635 selectedElements.add(((NodeDescriptor)userObject).getElement());
1640 return new SelectionInfo(selectedElements.toArray());
1644 private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
1645 private MyAutoScrollFromSourceHandler() {
1646 super(ProjectViewImpl.this.myProject, myViewContentPanel, ProjectViewImpl.this);
1650 protected void selectElementFromEditor(@NotNull FileEditor fileEditor) {
1651 if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return;
1652 if (isAutoscrollFromSource(getCurrentViewId())) {
1653 if (fileEditor instanceof TextEditor) {
1654 Editor editor = ((TextEditor)fileEditor).getEditor();
1655 selectElementAtCaretNotLosingFocus(editor);
1658 final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(fileEditor);
1660 final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file);
1661 if (psiFile != null) {
1662 final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1663 if (target != null) {
1664 final MySelectInContext selectInContext = new MySelectInContext(psiFile, null) {
1666 public Object getSelectorInFile() {
1671 if (target.canSelect(selectInContext)) {
1672 target.selectIn(selectInContext, false);
1681 public void scrollFromSource() {
1682 final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
1683 final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor();
1684 if (selectedTextEditor != null) {
1685 selectElementAtCaret(selectedTextEditor);
1688 final FileEditor[] editors = fileEditorManager.getSelectedEditors();
1689 for (FileEditor fileEditor : editors) {
1690 if (fileEditor instanceof TextEditor) {
1691 Editor editor = ((TextEditor)fileEditor).getEditor();
1692 selectElementAtCaret(editor);
1696 final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles();
1697 if (selectedFiles.length > 0) {
1698 final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]);
1700 scrollFromFile(file, null);
1705 private void selectElementAtCaretNotLosingFocus(@NotNull Editor editor) {
1706 AbstractProjectViewPane pane = getCurrentProjectViewPane();
1707 if (pane != null && !IJSwingUtilities.hasFocus(pane.getComponentToFocus())) {
1708 selectElementAtCaret(editor);
1712 private void selectElementAtCaret(@NotNull Editor editor) {
1713 final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
1714 if (file == null) return;
1716 scrollFromFile(file, editor);
1719 private void scrollFromFile(@NotNull PsiFile file, @Nullable Editor editor) {
1720 PsiDocumentManager.getInstance(myProject).performWhenAllCommitted(() -> {
1721 final MySelectInContext selectInContext = new MySelectInContext(file, editor);
1723 final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1724 if (target != null && target.canSelect(selectInContext)) {
1725 target.selectIn(selectInContext, false);
1731 protected boolean isAutoScrollEnabled() {
1732 return isAutoscrollFromSource(myCurrentViewId);
1736 protected void setAutoScrollEnabled(boolean state) {
1737 setAutoscrollFromSource(state, myCurrentViewId);
1739 final Editor editor = myFileEditorManager.getSelectedTextEditor();
1740 if (editor != null) {
1741 selectElementAtCaretNotLosingFocus(editor);
1744 createToolbarActions();
1747 private class MySelectInContext implements SelectInContext {
1748 @NotNull private final PsiFile myPsiFile;
1749 @Nullable private final Editor myEditor;
1751 private MySelectInContext(@NotNull PsiFile psiFile, @Nullable Editor editor) {
1752 myPsiFile = psiFile;
1758 public Project getProject() {
1763 private PsiFile getPsiFile() {
1769 public FileEditorProvider getFileEditorProvider() {
1770 return new FileEditorProvider() {
1772 public FileEditor openFileEditor() {
1773 return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0];
1779 private PsiElement getPsiElement() {
1780 PsiElement e = null;
1781 if (myEditor != null) {
1782 final int offset = myEditor.getCaretModel().getOffset();
1783 if (PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) {
1784 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1786 e = getPsiFile().findElementAt(offset);
1796 public VirtualFile getVirtualFile() {
1797 return getPsiFile().getVirtualFile();
1801 public Object getSelectorInFile() {
1802 return getPsiElement();
1808 public boolean isManualOrder(String paneId) {
1809 return getPaneOptionValue(myManualOrder, paneId, ourManualOrderDefaults);
1813 public void setManualOrder(@NotNull String paneId, final boolean enabled) {
1814 setPaneOption(myManualOrder, enabled, paneId, false);
1815 final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1816 pane.installComparator();
1820 public boolean isSortByType(String paneId) {
1821 return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults);
1825 public void setSortByType(@NotNull String paneId, final boolean sortByType) {
1826 setPaneOption(mySortByType, sortByType, paneId, false);
1827 final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1828 pane.installComparator();
1831 private class ManualOrderAction extends ToggleAction implements DumbAware {
1832 private ManualOrderAction() {
1833 super(IdeBundle.message("action.manual.order"), IdeBundle.message("action.manual.order"), AllIcons.ObjectBrowser.Sorted);
1837 public boolean isSelected(AnActionEvent event) {
1838 return isManualOrder(getCurrentViewId());
1842 public void setSelected(AnActionEvent event, boolean flag) {
1843 setManualOrder(getCurrentViewId(), flag);
1847 public void update(final AnActionEvent e) {
1849 final Presentation presentation = e.getPresentation();
1850 AbstractProjectViewPane pane = getCurrentProjectViewPane();
1851 presentation.setEnabledAndVisible(pane != null && pane.supportsManualOrder());
1855 private class SortByTypeAction extends ToggleAction implements DumbAware {
1856 private SortByTypeAction() {
1857 super(IdeBundle.message("action.sort.by.type"), IdeBundle.message("action.sort.by.type"), AllIcons.ObjectBrowser.SortByType);
1861 public boolean isSelected(AnActionEvent event) {
1862 return isSortByType(getCurrentViewId());
1866 public void setSelected(AnActionEvent event, boolean flag) {
1867 setSortByType(getCurrentViewId(), flag);
1871 public void update(final AnActionEvent e) {
1873 final Presentation presentation = e.getPresentation();
1874 presentation.setVisible(getCurrentProjectViewPane() != null);
1878 private class FoldersAlwaysOnTopAction extends ToggleAction implements DumbAware {
1879 private FoldersAlwaysOnTopAction() {
1880 super("Folders Always on Top");
1884 public boolean isSelected(AnActionEvent event) {
1885 return isFoldersAlwaysOnTop();
1889 public void setSelected(AnActionEvent event, boolean flag) {
1890 setFoldersAlwaysOnTop(flag);
1894 public void update(final AnActionEvent e) {
1896 final Presentation presentation = e.getPresentation();
1897 presentation.setEnabledAndVisible(getCurrentProjectViewPane() != null);
1901 private class ScrollFromSourceAction extends AnAction implements DumbAware {
1902 private ScrollFromSourceAction() {
1903 super("Scroll from Source", "Select the file open in the active editor", AllIcons.General.Locate);
1904 getTemplatePresentation().setHoveredIcon(AllIcons.General.LocateHover);
1908 public void actionPerformed(AnActionEvent e) {
1909 myAutoScrollFromSourceHandler.scrollFromSource();
1915 public Collection<String> getPaneIds() {
1916 return myId2Pane.keySet();
1921 public Collection<SelectInTarget> getSelectInTargets() {
1922 ensurePanesLoaded();
1923 return mySelectInTargets.values();
1928 public ActionCallback getReady(@NotNull Object requestor) {
1929 AbstractProjectViewPane pane = myId2Pane.get(myCurrentViewSubId);
1931 pane = myId2Pane.get(myCurrentViewId);
1933 return pane != null ? pane.getReady(requestor) : ActionCallback.DONE;