NPE fix (IDEA-164074)
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / projectView / impl / ProjectViewImpl.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.ide.projectView.impl;
18
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;
99
100 import javax.swing.*;
101 import javax.swing.tree.DefaultMutableTreeNode;
102 import javax.swing.tree.DefaultTreeModel;
103 import javax.swing.tree.TreePath;
104 import java.awt.*;
105 import java.util.*;
106 import java.util.List;
107
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;
117
118   // + options
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;
138   
139   private boolean myFoldersAlwaysOnTop = true;
140   
141
142   private String myCurrentViewId;
143   private String myCurrentViewSubId;
144   // - options
145
146   private final AutoScrollToSourceHandler myAutoScrollToSourceHandler;
147   private final MyAutoScrollFromSourceHandler myAutoScrollFromSourceHandler;
148
149   private final IdeView myIdeView = new MyIdeView();
150   private final MyDeletePSIElementProvider myDeletePSIElementProvider = new MyDeletePSIElementProvider();
151   private final ModuleDeleteProvider myDeleteModuleProvider = new ModuleDeleteProvider();
152
153   private SimpleToolWindowPanel myPanel;
154   private final Map<String, AbstractProjectViewPane> myId2Pane = new LinkedHashMap<>();
155   private final Collection<AbstractProjectViewPane> myUninitializedPanes = new THashSet<>();
156
157   static final DataKey<ProjectViewImpl> DATA_KEY = DataKey.create("com.intellij.ide.projectView.impl.ProjectViewImpl");
158
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";
180
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;
191
192   public ProjectViewImpl(@NotNull Project project, final FileEditorManager fileEditorManager, final ToolWindowManagerEx toolWindowManager) {
193     myProject = project;
194
195     constructUi();
196
197     myFileEditorManager = fileEditorManager;
198
199     myConnection = project.getMessageBus().connect();
200     myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
201       @Override
202       public void rootsChanged(ModuleRootEvent event) {
203         refresh();
204       }
205     });
206
207     myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler();
208
209     myDataProvider = new MyPanel();
210     myDataProvider.add(myPanel, BorderLayout.CENTER);
211     myCopyPasteDelegator = new CopyPasteDelegator(myProject, myPanel) {
212       @Override
213       @NotNull
214       protected PsiElement[] getSelectedElements() {
215         final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
216         return viewPane == null ? PsiElement.EMPTY_ARRAY : viewPane.getSelectedPSIElements();
217       }
218     };
219     myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() {
220       @Override
221       protected boolean isAutoScrollMode() {
222         return isAutoscrollToSource(myCurrentViewId);
223       }
224
225       @Override
226       protected void setAutoScrollMode(boolean state) {
227         setAutoscrollToSource(state, myCurrentViewId);
228       }
229     };
230     toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter(){
231       private boolean toolWindowVisible;
232
233       @Override
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();
241
242             if (currentProjectViewPane != null) {
243               myAutoScrollToSourceHandler.onMouseClicked(currentProjectViewPane.getTree());
244             }
245           }
246           if (isAutoscrollFromSource(id)) {
247             myAutoScrollFromSourceHandler.setAutoScrollEnabled(true);
248           }
249         }
250         toolWindowVisible = window.isVisible();
251       }
252     });
253   }
254
255   private void constructUi() {
256     myViewContentPanel = new JPanel();
257     myPanel = new SimpleToolWindowPanel(true);
258     myPanel.setContent(myViewContentPanel);
259   }
260
261   @NotNull
262   @Deprecated
263   public List<AnAction> getActions(boolean originalProvider) {
264     return Collections.emptyList();
265   }
266
267   @Override
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);
273     }
274     if (isInitialized) {
275       doAddUninitializedPanes();
276     }
277   }
278
279   @Override
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();
285
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);
293       }
294     }
295     myId2Pane.remove(idToRemove);
296     mySelectInTargets.remove(idToRemove);
297     viewSelectionChanged();
298   }
299
300   private synchronized void doAddUninitializedPanes() {
301     for (AbstractProjectViewPane pane : myUninitializedPanes) {
302       doAddPane(pane);
303     }
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("");
311       }
312     }
313
314     String selectID = null;
315     String selectSubID = null;
316
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);
321       if (id != null &&
322           id.equals(mySavedPaneId) &&
323           StringUtil.equals(subId, mySavedPaneSubId)) {
324         selectID = id;
325         selectSubID = subId;
326         break;
327       }
328     }
329
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);
335     }
336
337     if (selectID != null) {
338       changeView(selectID, selectSubID);
339       mySavedPaneId = null;
340       mySavedPaneSubId = null;
341     }
342
343     myUninitializedPanes.clear();
344   }
345
346   private void doAddPane(@NotNull final AbstractProjectViewPane newPane) {
347     ApplicationManager.getApplication().assertIsDispatchThread();
348     int index;
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);
354
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.");
358       if (comp > 0) {
359         break;
360       }
361     }
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;
379       });
380       content.setBusyObject(this);
381       if (first && subId != null) {
382         content.setSeparator(newPane.getTitle());
383       }
384       manager.addContent(content, index++);
385       first = false;
386     }
387   }
388
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();
395       }
396       final PsiElement[] elements = currentPane.getSelectedPSIElements();
397       if (elements.length > 0) {
398         selectedPsiElement = elements[0];
399       }
400     }
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();
411
412     myAutoScrollToSourceHandler.install(newPane.myTree);
413
414     IdeFocusManager.getInstance(myProject).requestFocus(newPane.getComponentToFocus(), false);
415
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() {
420         @Override
421         @NotNull
422         public Project getProject() {
423           return myProject;
424         }
425
426         @Override
427         @NotNull
428         public VirtualFile getVirtualFile() {
429           return virtualFile;
430         }
431
432         @Override
433         public Object getSelectorInFile() {
434           return null;
435         }
436
437         @Override
438         public FileEditorProvider getFileEditorProvider() {
439           return null;
440         }
441       })) {
442         newPane.select(selectedPsiElement, virtualFile, true);
443       }
444     }
445     myAutoScrollToSourceHandler.onMouseClicked(newPane.myTree);
446   }
447
448   // public for tests
449   public synchronized void setupImpl(@NotNull ToolWindow toolWindow) {
450     setupImpl(toolWindow, true);
451   }
452
453   // public for tests
454   public synchronized void setupImpl(@NotNull ToolWindow toolWindow, final boolean loadPaneExtensions) {
455     ApplicationManager.getApplication().assertIsDispatchThread();
456     myActionGroup = new DefaultActionGroup();
457
458     myAutoScrollFromSourceHandler.install();
459
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");
465     }
466
467     GuiUtils.replaceJSplitPaneWithIDEASplitter(myPanel);
468     SwingUtilities.invokeLater(() -> splitterProportions.restoreSplitterProportions(myPanel));
469
470     if (loadPaneExtensions) {
471       ensurePanesLoaded();
472     }
473     isInitialized = true;
474     doAddUninitializedPanes();
475
476     getContentManager().addContentManagerListener(new ContentManagerAdapter() {
477       @Override
478       public void selectionChanged(ContentManagerEvent event) {
479         if (event.getOperation() == ContentManagerEvent.ContentOperation.add) {
480           viewSelectionChanged();
481         }
482       }
483     });
484     viewSelectionChanged();
485   }
486
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())) {
494         try {
495           pane.readExternal(myUninitializedPaneState.get(pane.getId()));
496         }
497         catch (InvalidDataException e) {
498           // ignore
499         }
500         myUninitializedPaneState.remove(pane.getId());
501       }
502       if (pane.isInitiallyVisible() && !myId2Pane.containsKey(pane.getId())) {
503         addProjectPane(pane);
504       }
505     }
506   }
507
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);
517     showPane(newPane);
518     if (isAutoscrollFromSource(id)) {
519       myAutoScrollFromSourceHandler.scrollFromSource();
520     }
521     return true;
522   }
523
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) {
532         @Override
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());
538           }
539           super.setSelected(event, flag);
540
541           selectionInfo.apply(viewPane);
542         }
543
544         @Override
545         public boolean isSelected(AnActionEvent event) {
546           if (isGlobalOptions()) return getGlobalOptions().getFlattenPackages();
547           return super.isSelected(event);
548         }
549       }).setAsSecondary(true);
550     }
551
552     class FlattenPackagesDependableAction extends PaneOptionAction {
553       FlattenPackagesDependableAction(@NotNull Map<String, Boolean> optionsMap,
554                                       @NotNull String text,
555                                       @NotNull String description,
556                                       @NotNull Icon icon,
557                                       boolean optionDefaultValue) {
558         super(optionsMap, text, description, icon, optionDefaultValue);
559       }
560
561       @Override
562       public void setSelected(AnActionEvent event, boolean flag) {
563         if (isGlobalOptions()) {
564           getGlobalOptions().setFlattenPackages(flag);
565         }
566         super.setSelected(event, flag);
567       }
568
569       @Override
570       public void update(AnActionEvent e) {
571         super.update(e);
572         final Presentation presentation = e.getPresentation();
573         presentation.setVisible(isFlattenPackages(myCurrentViewId));
574       }
575     }
576     if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) {
577       myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true);
578     }
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) {
585         @Override
586         public boolean isSelected(AnActionEvent event) {
587           return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId);
588         }
589
590
591         @Override
592         public void update(AnActionEvent e) {
593           super.update(e);
594           if (ScopeViewPane.ID.equals(myCurrentViewId)) {
595             e.getPresentation().setEnabled(false);
596           }
597         }
598       }).setAsSecondary(true);
599     }
600
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) {
605         @Override
606         public boolean isSelected(AnActionEvent event) {
607           if (isGlobalOptions()) return getGlobalOptions().getShowMembers();
608           return super.isSelected(event);
609         }
610
611         @Override
612         public void setSelected(AnActionEvent event, boolean flag) {
613           if (isGlobalOptions()) {
614             getGlobalOptions().setShowMembers(flag);
615           }
616           super.setSelected(event, flag);
617         }
618       })
619         .setAsSecondary(true);
620     }
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);
626
627     if (!myAutoScrollFromSourceHandler.isAutoScrollEnabled()) {
628       titleActions.add(new ScrollFromSourceAction());
629     }
630     AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() {
631       @Override
632       public void expandAll() {
633
634       }
635
636       @Override
637       public boolean canExpand() {
638         return false;
639       }
640
641       @Override
642       public void collapseAll() {
643         AbstractProjectViewPane pane = getCurrentProjectViewPane();
644         JTree tree = pane.myTree;
645         if (tree != null) {
646           TreeUtil.collapseAll(tree, 0);
647         }
648       }
649
650       @Override
651       public boolean canCollapse() {
652         return true;
653       }
654     }, getComponent());
655     collapseAllAction.getTemplatePresentation().setIcon(AllIcons.General.CollapseAll);
656     collapseAllAction.getTemplatePresentation().setHoveredIcon(AllIcons.General.CollapseAllHover);
657     titleActions.add(collapseAllAction);
658     getCurrentProjectViewPane().addToolbarActions(myActionGroup);
659
660     ToolWindowEx window = (ToolWindowEx)ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW);
661     if (window != null) {
662       window.setTitleActions(titleActions.toArray(new AnAction[titleActions.size()]));
663     }
664   }
665
666   protected boolean isShowMembersOptionSupported() {
667     return true;
668   }
669
670   @Override
671   public AbstractProjectViewPane getProjectViewPaneById(String id) {
672     if (!ApplicationManager.getApplication().isUnitTestMode()) {   // most tests don't need all panes to be loaded
673       ensurePanesLoaded();
674     }
675
676     final AbstractProjectViewPane pane = myId2Pane.get(id);
677     if (pane != null) {
678       return pane;
679     }
680     for (AbstractProjectViewPane viewPane : myUninitializedPanes) {
681       if (viewPane.getId().equals(id)) {
682         return viewPane;
683       }
684     }
685     return null;
686   }
687
688   @Override
689   public AbstractProjectViewPane getCurrentProjectViewPane() {
690     return getProjectViewPaneById(myCurrentViewId);
691   }
692
693   @Override
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);
699     }
700   }
701
702   @Override
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);
707     }
708   }
709
710   @NotNull
711   @Override
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);
716     }
717     select(element, file, requestFocus);
718     return ActionCallback.DONE;
719   }
720
721   @Override
722   public void dispose() {
723     myConnection.disconnect();
724   }
725
726   public JComponent getComponent() {
727     return myDataProvider;
728   }
729
730   @Override
731   public String getCurrentViewId() {
732     return myCurrentViewId;
733   }
734
735   @Override
736   public PsiElement getParentOfCurrentSelection() {
737     final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
738     if (viewPane == null) {
739       return null;
740     }
741     TreePath path = viewPane.getSelectedPath();
742     if (path == null) {
743       return null;
744     }
745     path = path.getParentPath();
746     if (path == null) {
747       return null;
748     }
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;
757         return psiElement;
758       }
759       else {
760         return null;
761       }
762     }
763     return null;
764   }
765
766   public ContentManager getContentManager() {
767     if (myContentManager == null) {
768       ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).getContentManager();
769     }
770     return myContentManager;
771   }
772
773
774   private class PaneOptionAction extends ToggleAction implements DumbAware {
775     private final Map<String, Boolean> myOptionsMap;
776     private final boolean myOptionDefaultValue;
777
778     PaneOptionAction(@NotNull Map<String, Boolean> optionsMap,
779                      @NotNull String text,
780                      @NotNull String description,
781                      Icon icon,
782                      boolean optionDefaultValue) {
783       super(text, description, icon);
784       myOptionsMap = optionsMap;
785       myOptionDefaultValue = optionDefaultValue;
786     }
787
788     @Override
789     public boolean isSelected(AnActionEvent event) {
790       return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue);
791     }
792
793     @Override
794     public void setSelected(AnActionEvent event, boolean flag) {
795       setPaneOption(myOptionsMap, flag, myCurrentViewId, true);
796     }
797   }
798
799   @Override
800   public void changeView() {
801     final List<AbstractProjectViewPane> views = new ArrayList<>(myId2Pane.values());
802     views.remove(getCurrentProjectViewPane());
803     Collections.sort(views, PANE_WEIGHT_COMPARATOR);
804
805     final JList list = new JBList(ArrayUtil.toObjectArray(views));
806     list.setCellRenderer(new DefaultListCellRenderer() {
807       @Override
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());
812         return this;
813       }
814     });
815
816     if (!views.isEmpty()) {
817       list.setSelectedValue(views.get(0), true);
818     }
819     Runnable runnable = () -> {
820       if (list.getSelectedIndex() < 0) return;
821       AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue();
822       changeView(pane.getId());
823     };
824
825     new PopupChooserBuilder(list).
826       setTitle(IdeBundle.message("title.popup.views")).
827       setItemChoosenCallback(runnable).
828       createPopup().showInCenterOf(getComponent());
829   }
830
831   @Override
832   public void changeView(@NotNull String viewId) {
833     changeView(viewId, null);
834   }
835
836   @Override
837   public void changeView(@NotNull String viewId, @Nullable String subId) {
838     changeViewCB(viewId, subId);
839   }
840
841   @NotNull
842   @Override
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);
851         }
852       }
853     }
854     return ActionCallback.REJECTED;
855   }
856
857   private final class MyDeletePSIElementProvider implements DeleteProvider {
858     @Override
859     public boolean canDeleteElement(@NotNull DataContext dataContext) {
860       final PsiElement[] elements = getElementsToDelete();
861       return DeleteHandler.shouldEnableDeleteAction(elements);
862     }
863
864     @Override
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);
870       }
871       final PsiElement[] elements = PsiUtilCore.toPsiElementArray(validElements);
872
873       LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
874       try {
875         DeleteHandler.deletePsiElement(elements, myProject);
876       }
877       finally {
878         a.finish();
879       }
880     }
881
882     @NotNull
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)) {
892             while (true) {
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) {
898                 directory = parent;
899               }
900               else {
901                 break;
902               }
903             }
904             elements[idx] = directory;
905           }
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()));
911             if (vFile != null) {
912               final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
913               if (psiFile != null) {
914                 elements[idx] = psiFile;
915               }
916             }
917           }
918         }
919       }
920       return elements;
921     }
922
923   }
924
925   private final class MyPanel extends JPanel implements DataProvider {
926     MyPanel() {
927       super(new BorderLayout());
928     }
929
930     @Nullable
931     private Object getSelectedNodeElement() {
932       final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
933       if (currentProjectViewPane == null) { // can happen if not initialized yet
934         return null;
935       }
936       DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode();
937       if (node == null) {
938         return null;
939       }
940       Object userObject = node.getUserObject();
941       if (userObject instanceof AbstractTreeNode) {
942         return ((AbstractTreeNode)userObject).getValue();
943       }
944       if (!(userObject instanceof NodeDescriptor)) {
945         return null;
946       }
947       return ((NodeDescriptor)userObject).getElement();
948     }
949
950     @Override
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;
956       }
957
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;
962       }
963       if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
964         if (currentProjectViewPane == null) {
965           return null;
966         }
967         PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
968         return elements.length == 0 ? null : elements;
969       }
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));
976         }
977         return modules.size() == 1 ? modules.iterator().next() : null;
978       }
979       if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
980         return null;
981       }
982       if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
983         return myCopyPasteDelegator.getCutProvider();
984       }
985       if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
986         return myCopyPasteDelegator.getCopyProvider();
987       }
988       if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
989         return myCopyPasteDelegator.getPasteProvider();
990       }
991       if (LangDataKeys.IDE_VIEW.is(dataId)) {
992         return myIdeView;
993       }
994       if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
995         final Module[] modules = getSelectedModules();
996         if (modules != null) {
997           return myDeleteModuleProvider;
998         }
999         final LibraryOrderEntry orderEntry = getSelectedLibrary();
1000         if (orderEntry != null) {
1001           return new DeleteProvider() {
1002             @Override
1003             public void deleteElement(@NotNull DataContext dataContext) {
1004               detachLibrary(orderEntry, myProject);
1005             }
1006
1007             @Override
1008             public boolean canDeleteElement(@NotNull DataContext dataContext) {
1009               return true;
1010             }
1011           };
1012         }
1013         return myDeletePSIElementProvider;
1014       }
1015       if (PlatformDataKeys.HELP_ID.is(dataId)) {
1016         return HelpID.PROJECT_VIEWS;
1017       }
1018       if (ProjectViewImpl.DATA_KEY.is(dataId)) {
1019         return ProjectViewImpl.this;
1020       }
1021       if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) {
1022         Object selected = getSelectedNodeElement();
1023         return selected instanceof Project ? selected : null;
1024       }
1025       if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
1026         Object selected = getSelectedNodeElement();
1027         if (selected instanceof Module) {
1028           return !((Module)selected).isDisposed() ? selected : null;
1029         }
1030         else if (selected instanceof PsiDirectory) {
1031           return moduleBySingleContentRoot(((PsiDirectory)selected).getVirtualFile());
1032         }
1033         else if (selected instanceof VirtualFile) {
1034           return moduleBySingleContentRoot((VirtualFile)selected);
1035         }
1036         else {
1037           return null;
1038         }
1039       }
1040
1041       if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
1042         return getSelectedModules();
1043       }
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()]);
1047       }
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()]);
1051       }
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()]);
1055       }
1056
1057       return null;
1058     }
1059
1060     @Nullable
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;
1074         }
1075         PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue();
1076         VirtualFile virtualFile = directory.getVirtualFile();
1077         Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue();
1078
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;
1084         }
1085       }
1086
1087       return null;
1088     }
1089
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]);
1106             }
1107           }
1108           model.commit();
1109         };
1110         ApplicationManager.getApplication().runWriteAction(action);
1111       }, title, null);
1112     }
1113
1114     @Nullable
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()) {
1124             result.add(module);
1125           }
1126         }
1127         else if (element instanceof ModuleGroup) {
1128           Collection<Module> modules = ((ModuleGroup)element).modulesInGroup(myProject, true);
1129           result.addAll(modules);
1130         }
1131         else if (element instanceof PsiDirectory) {
1132           Module module = moduleBySingleContentRoot(((PsiDirectory)element).getVirtualFile());
1133           if (module != null) result.add(module);
1134         }
1135         else if (element instanceof VirtualFile) {
1136           Module module = moduleBySingleContentRoot((VirtualFile)element);
1137           if (module != null) result.add(module);
1138         }
1139       }
1140
1141       if (result.isEmpty()) {
1142         return null;
1143       }
1144       else {
1145         return result.toArray(new Module[result.size()]);
1146       }
1147     }
1148   }
1149
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
1155    */
1156   @Nullable
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) {
1161         return module;
1162       }
1163     }
1164
1165     return null;
1166   }
1167
1168   @NotNull
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);
1178       }
1179     }
1180     return result;
1181   }
1182
1183   private final class MyIdeView implements IdeView {
1184     @Override
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;
1190         if (!isDirectory) {
1191           FileEditor editor = EditorHelper.openInEditor(element, false);
1192           if (editor != null) {
1193             ToolWindowManager.getInstance(myProject).activateEditorComponent();
1194             requestFocus = false;
1195           }
1196         }
1197       }
1198
1199       if (requestFocus) {
1200         selectPsiElement(element, true);
1201       }
1202     }
1203
1204     @NotNull
1205     @Override
1206     public PsiDirectory[] getDirectories() {
1207       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1208       if (viewPane != null) {
1209         return viewPane.getSelectedDirectories();
1210       }
1211
1212       return PsiDirectory.EMPTY_ARRAY;
1213     }
1214
1215     @Override
1216     public PsiDirectory getOrChooseDirectory() {
1217       return DirectoryChooserUtil.getOrChooseDirectory(this);
1218     }
1219   }
1220
1221   @Override
1222   public void selectPsiElement(PsiElement element, boolean requestFocus) {
1223     if (element == null) return;
1224     VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
1225     select(element, virtualFile, requestFocus);
1226   }
1227
1228
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);
1233     }
1234   }
1235
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()));
1242       }
1243     }
1244
1245     parentNode.addContent(e);
1246   }
1247
1248   @Override
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;
1257       }
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);
1268
1269       Element foldersElement = navigatorElement.getChild(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1270       if (foldersElement != null) myFoldersAlwaysOnTop = Boolean.valueOf(foldersElement.getAttributeValue("value"));
1271       
1272       try {
1273         splitterProportions.readExternal(navigatorElement);
1274       }
1275       catch (InvalidDataException e) {
1276         // ignore
1277       }
1278     }
1279     Element panesElement = parentNode.getChild(ELEMENT_PANES);
1280     if (panesElement != null) {
1281       readPaneState(panesElement);
1282     }
1283   }
1284
1285   private void readPaneState(@NotNull Element panesElement) {
1286     @SuppressWarnings({"unchecked"})
1287     final List<Element> paneElements = panesElement.getChildren(ELEMENT_PANE);
1288
1289     for (Element paneElement : paneElements) {
1290       String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID);
1291       final AbstractProjectViewPane pane = myId2Pane.get(paneId);
1292       if (pane != null) {
1293         try {
1294           pane.readExternal(paneElement);
1295         }
1296         catch (InvalidDataException e) {
1297           // ignore
1298         }
1299       }
1300       else {
1301         myUninitializedPaneState.put(paneId, paneElement);
1302       }
1303     }
1304   }
1305
1306   @Override
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);
1316       }
1317     }
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);
1328     
1329     Element foldersElement = new Element(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1330     foldersElement.setAttribute("value", Boolean.toString(myFoldersAlwaysOnTop));
1331     navigatorElement.addContent(foldersElement);
1332
1333     splitterProportions.saveSplitterProportions(myPanel);
1334     try {
1335       splitterProportions.writeExternal(navigatorElement);
1336     }
1337     catch (WriteExternalException e) {
1338       // ignore
1339     }
1340     parentNode.addContent(navigatorElement);
1341
1342     Element panesElement = new Element(ELEMENT_PANES);
1343     writePaneState(panesElement);
1344     parentNode.addContent(panesElement);
1345     return parentNode;
1346   }
1347
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());
1352       try {
1353         pane.writeExternal(paneElement);
1354       }
1355       catch (WriteExternalException e) {
1356         continue;
1357       }
1358       panesElement.addContent(paneElement);
1359     }
1360     for (Element element : myUninitializedPaneState.values()) {
1361       panesElement.addContent(element.clone());
1362     }
1363   }
1364
1365   boolean isGlobalOptions() {
1366     return Registry.is("ide.projectView.globalOptions");
1367   }
1368
1369   ProjectViewSharedSettings getGlobalOptions() {
1370     return ProjectViewSharedSettings.Companion.getInstance();
1371   }
1372
1373   @Override
1374   public boolean isAutoscrollToSource(String paneId) {
1375     if (isGlobalOptions()) {
1376       return getGlobalOptions().getAutoscrollToSource();
1377     }
1378
1379     return getPaneOptionValue(myAutoscrollToSource, paneId, UISettings.getInstance().DEFAULT_AUTOSCROLL_TO_SOURCE);
1380   }
1381
1382   public void setAutoscrollToSource(boolean autoscrollMode, String paneId) {
1383     if (isGlobalOptions()) {
1384       getGlobalOptions().setAutoscrollToSource(autoscrollMode);
1385     }
1386     myAutoscrollToSource.put(paneId, autoscrollMode);
1387   }
1388
1389   @Override
1390   public boolean isAutoscrollFromSource(String paneId) {
1391     if (isGlobalOptions()) {
1392       return getGlobalOptions().getAutoscrollFromSource();
1393     }
1394
1395     return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults);
1396   }
1397
1398   public void setAutoscrollFromSource(boolean autoscrollMode, String paneId) {
1399     if (isGlobalOptions()) {
1400       getGlobalOptions().setAutoscrollFromSource(autoscrollMode);
1401     }
1402     setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false);
1403   }
1404
1405   @Override
1406   public boolean isFlattenPackages(String paneId) {
1407     if (isGlobalOptions()) {
1408       return getGlobalOptions().getFlattenPackages();
1409     }
1410
1411     return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults);
1412   }
1413
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);
1419       }
1420     }
1421     setPaneOption(myFlattenPackages, flattenPackages, paneId, true);
1422   }
1423
1424   public boolean isFoldersAlwaysOnTop() {
1425     if (isGlobalOptions()) {
1426       return getGlobalOptions().getFoldersAlwaysOnTop();
1427     }
1428
1429     return myFoldersAlwaysOnTop;
1430   }
1431
1432   public void setFoldersAlwaysOnTop(boolean foldersAlwaysOnTop) {
1433     if (isGlobalOptions()) {
1434       getGlobalOptions().setFoldersAlwaysOnTop(foldersAlwaysOnTop);
1435     }
1436
1437     if (myFoldersAlwaysOnTop != foldersAlwaysOnTop) {
1438       myFoldersAlwaysOnTop = foldersAlwaysOnTop;
1439       for (AbstractProjectViewPane pane : myId2Pane.values()) {
1440         if (pane.getTree() != null) {
1441           pane.updateFromRoot(false);
1442         }
1443       }
1444     }
1445   }
1446
1447   @Override
1448   public boolean isShowMembers(String paneId) {
1449     if (isGlobalOptions()) {
1450       return getGlobalOptions().getShowMembers();
1451     }
1452
1453     return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults);
1454   }
1455
1456   public void setShowMembers(boolean showMembers, String paneId) {
1457     setPaneOption(myShowMembers, showMembers, paneId, true);
1458   }
1459
1460   @Override
1461   public boolean isHideEmptyMiddlePackages(String paneId) {
1462     if (isGlobalOptions()) {
1463       return getGlobalOptions().getHideEmptyPackages();
1464     }
1465
1466     return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults);
1467   }
1468
1469   @Override
1470   public boolean isAbbreviatePackageNames(String paneId) {
1471     if (isGlobalOptions()) {
1472       return getGlobalOptions().getAbbreviatePackages();
1473     }
1474
1475     return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults);
1476   }
1477
1478   @Override
1479   public boolean isShowLibraryContents(String paneId) {
1480     if (isGlobalOptions()) {
1481       return getGlobalOptions().getShowLibraryContents();
1482     }
1483
1484     return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults);
1485   }
1486
1487   @Override
1488   public void setShowLibraryContents(boolean showLibraryContents, @NotNull String paneId) {
1489     if (isGlobalOptions()) {
1490       getGlobalOptions().setShowLibraryContents(showLibraryContents);
1491     }
1492     setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1493   }
1494
1495   @NotNull
1496   public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) {
1497     return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1498   }
1499
1500   @Override
1501   public boolean isShowModules(String paneId) {
1502     if (isGlobalOptions()) {
1503       return getGlobalOptions().getShowModules();
1504     }
1505
1506     return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults);
1507   }
1508
1509   @Override
1510   public void setShowModules(boolean showModules, @NotNull String paneId) {
1511     if (isGlobalOptions()) {
1512       getGlobalOptions().setShowModules(showModules);
1513     }
1514     setPaneOption(myShowModules, showModules, paneId, true);
1515   }
1516
1517   @Override
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);
1523       }
1524     }
1525     setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true);
1526   }
1527
1528   @Override
1529   public void setAbbreviatePackageNames(boolean abbreviatePackageNames, @NotNull String paneId) {
1530     if (isGlobalOptions()) {
1531       getGlobalOptions().setAbbreviatePackages(abbreviatePackageNames);
1532     }
1533     setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true);
1534   }
1535
1536   @NotNull
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);
1540       if (updatePane) {
1541         final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1542         if (pane != null) {
1543           return pane.updateFromRoot(false);
1544         }
1545       }
1546     }
1547     return ActionCallback.DONE;
1548   }
1549
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();
1553   }
1554
1555   private class HideEmptyMiddlePackagesAction extends PaneOptionAction {
1556     private HideEmptyMiddlePackagesAction() {
1557       super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults);
1558     }
1559
1560     @Override
1561     public void setSelected(AnActionEvent event, boolean flag) {
1562       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1563       final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
1564
1565       if (isGlobalOptions()) {
1566         getGlobalOptions().setHideEmptyPackages(flag);
1567       }
1568       super.setSelected(event, flag);
1569
1570       selectionInfo.apply(viewPane);
1571     }
1572
1573     @Override
1574     public boolean isSelected(AnActionEvent event) {
1575       if (isGlobalOptions()) return getGlobalOptions().getHideEmptyPackages();
1576       return super.isSelected(event);
1577     }
1578
1579     @Override
1580     public void update(AnActionEvent e) {
1581       super.update(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"));
1586       }
1587       else {
1588         presentation.setText(IdeBundle.message("action.compact.empty.middle.packages"));
1589         presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages"));
1590       }
1591     }
1592   }
1593
1594   private static class SelectionInfo {
1595     private final Object[] myElements;
1596
1597     private SelectionInfo(@NotNull Object[] elements) {
1598       myElements = elements;
1599     }
1600
1601     public void apply(final AbstractProjectViewPane viewPane) {
1602       if (viewPane == null) {
1603         return;
1604       }
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);
1611         if (node == null) {
1612           treeBuilder.buildNodeForElement(element);
1613           node = treeBuilder.getNodeForElement(element);
1614         }
1615         if (node != null) {
1616           paths.add(new TreePath(treeModel.getPathToRoot(node)));
1617         }
1618       }
1619       if (!paths.isEmpty()) {
1620         tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
1621       }
1622     }
1623
1624     @NotNull
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());
1636             }
1637           }
1638         }
1639       }
1640       return new SelectionInfo(selectedElements.toArray());
1641     }
1642   }
1643
1644   private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
1645     private MyAutoScrollFromSourceHandler() {
1646       super(ProjectViewImpl.this.myProject, myViewContentPanel, ProjectViewImpl.this);
1647     }
1648
1649     @Override
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);
1656         }
1657         else {
1658           final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(fileEditor);
1659           if (file != null) {
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) {
1665                   @Override
1666                   public Object getSelectorInFile() {
1667                     return psiFile;
1668                   }
1669                 };
1670
1671                 if (target.canSelect(selectInContext)) {
1672                   target.selectIn(selectInContext, false);
1673                 }
1674               }
1675             }
1676           }
1677         }
1678       }
1679     }
1680
1681     public void scrollFromSource() {
1682       final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
1683       final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor();
1684       if (selectedTextEditor != null) {
1685         selectElementAtCaret(selectedTextEditor);
1686         return;
1687       }
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);
1693           return;
1694         }
1695       }
1696       final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles();
1697       if (selectedFiles.length > 0) {
1698         final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]);
1699         if (file != null) {
1700           scrollFromFile(file, null);
1701         }
1702       }
1703     }
1704
1705     private void selectElementAtCaretNotLosingFocus(@NotNull Editor editor) {
1706       AbstractProjectViewPane pane = getCurrentProjectViewPane();
1707       if (pane != null && !IJSwingUtilities.hasFocus(pane.getComponentToFocus())) {
1708         selectElementAtCaret(editor);
1709       }
1710     }
1711
1712     private void selectElementAtCaret(@NotNull Editor editor) {
1713       final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
1714       if (file == null) return;
1715
1716       scrollFromFile(file, editor);
1717     }
1718
1719     private void scrollFromFile(@NotNull PsiFile file, @Nullable Editor editor) {
1720       PsiDocumentManager.getInstance(myProject).performWhenAllCommitted(() -> {
1721         final MySelectInContext selectInContext = new MySelectInContext(file, editor);
1722
1723         final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1724         if (target != null && target.canSelect(selectInContext)) {
1725           target.selectIn(selectInContext, false);
1726         }
1727       });
1728     }
1729
1730     @Override
1731     protected boolean isAutoScrollEnabled() {
1732       return isAutoscrollFromSource(myCurrentViewId);
1733     }
1734
1735     @Override
1736     protected void setAutoScrollEnabled(boolean state) {
1737       setAutoscrollFromSource(state, myCurrentViewId);
1738       if (state) {
1739         final Editor editor = myFileEditorManager.getSelectedTextEditor();
1740         if (editor != null) {
1741           selectElementAtCaretNotLosingFocus(editor);
1742         }
1743       }
1744       createToolbarActions();
1745     }
1746
1747     private class MySelectInContext implements SelectInContext {
1748       @NotNull private final PsiFile myPsiFile;
1749       @Nullable private final Editor myEditor;
1750
1751       private MySelectInContext(@NotNull PsiFile psiFile, @Nullable Editor editor) {
1752         myPsiFile = psiFile;
1753         myEditor = editor;
1754       }
1755
1756       @Override
1757       @NotNull
1758       public Project getProject() {
1759         return myProject;
1760       }
1761
1762       @NotNull
1763       private PsiFile getPsiFile() {
1764         return myPsiFile;
1765       }
1766
1767       @Override
1768       @NotNull
1769       public FileEditorProvider getFileEditorProvider() {
1770         return new FileEditorProvider() {
1771           @Override
1772           public FileEditor openFileEditor() {
1773             return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0];
1774           }
1775         };
1776       }
1777
1778       @NotNull
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();
1785           }
1786           e = getPsiFile().findElementAt(offset);
1787         }
1788         if (e == null) {
1789           e = getPsiFile();
1790         }
1791         return e;
1792       }
1793
1794       @Override
1795       @NotNull
1796       public VirtualFile getVirtualFile() {
1797         return getPsiFile().getVirtualFile();
1798       }
1799
1800       @Override
1801       public Object getSelectorInFile() {
1802         return getPsiElement();
1803       }
1804     }
1805   }
1806
1807   @Override
1808   public boolean isManualOrder(String paneId) {
1809     return getPaneOptionValue(myManualOrder, paneId, ourManualOrderDefaults);
1810   }
1811
1812   @Override
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();
1817   }
1818
1819   @Override
1820   public boolean isSortByType(String paneId) {
1821     return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults);
1822   }
1823
1824   @Override
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();
1829   }
1830
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);
1834     }
1835
1836     @Override
1837     public boolean isSelected(AnActionEvent event) {
1838       return isManualOrder(getCurrentViewId());
1839     }
1840
1841     @Override
1842     public void setSelected(AnActionEvent event, boolean flag) {
1843       setManualOrder(getCurrentViewId(), flag);
1844     }
1845
1846     @Override
1847     public void update(final AnActionEvent e) {
1848       super.update(e);
1849       final Presentation presentation = e.getPresentation();
1850       AbstractProjectViewPane pane = getCurrentProjectViewPane();
1851       presentation.setEnabledAndVisible(pane != null && pane.supportsManualOrder());
1852     }
1853   }
1854   
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);
1858     }
1859
1860     @Override
1861     public boolean isSelected(AnActionEvent event) {
1862       return isSortByType(getCurrentViewId());
1863     }
1864
1865     @Override
1866     public void setSelected(AnActionEvent event, boolean flag) {
1867       setSortByType(getCurrentViewId(), flag);
1868     }
1869
1870     @Override
1871     public void update(final AnActionEvent e) {
1872       super.update(e);
1873       final Presentation presentation = e.getPresentation();
1874       presentation.setVisible(getCurrentProjectViewPane() != null);
1875     }
1876   }
1877
1878   private class FoldersAlwaysOnTopAction extends ToggleAction implements DumbAware {
1879     private FoldersAlwaysOnTopAction() {
1880       super("Folders Always on Top");
1881     }
1882
1883     @Override
1884     public boolean isSelected(AnActionEvent event) {
1885       return isFoldersAlwaysOnTop();
1886     }
1887
1888     @Override
1889     public void setSelected(AnActionEvent event, boolean flag) {
1890       setFoldersAlwaysOnTop(flag);
1891     }
1892
1893     @Override
1894     public void update(final AnActionEvent e) {
1895       super.update(e);
1896       final Presentation presentation = e.getPresentation();
1897       presentation.setEnabledAndVisible(getCurrentProjectViewPane() != null);
1898     }
1899   }
1900
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);
1905     }
1906
1907     @Override
1908     public void actionPerformed(AnActionEvent e) {
1909       myAutoScrollFromSourceHandler.scrollFromSource();
1910     }
1911   }
1912
1913   @NotNull
1914   @Override
1915   public Collection<String> getPaneIds() {
1916     return myId2Pane.keySet();
1917   }
1918
1919   @NotNull
1920   @Override
1921   public Collection<SelectInTarget> getSelectInTargets() {
1922     ensurePanesLoaded();
1923     return mySelectInTargets.values();
1924   }
1925
1926   @NotNull
1927   @Override
1928   public ActionCallback getReady(@NotNull Object requestor) {
1929     AbstractProjectViewPane pane = myId2Pane.get(myCurrentViewSubId);
1930     if (pane == null) {
1931       pane = myId2Pane.get(myCurrentViewId);
1932     }
1933     return pane != null ? pane.getReady(requestor) : ActionCallback.DONE;
1934   }
1935 }