55e45c235803b089e1fe838bab33af7dd1d885ff
[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     List<AnAction> titleActions = ContainerUtil.newSmartList();
526     myActionGroup.removeAll();
527     if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
528       myActionGroup.addAction(new PaneOptionAction(myFlattenPackages, IdeBundle.message("action.flatten.packages"),
529                                              IdeBundle.message("action.flatten.packages"), PlatformIcons.FLATTEN_PACKAGES_ICON,
530                                              ourFlattenPackagesDefaults) {
531         @Override
532         public void setSelected(AnActionEvent event, boolean flag) {
533           final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
534           final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
535           if (isGlobalOptions()) {
536             setFlattenPackages(flag, viewPane.getId());
537           }
538           super.setSelected(event, flag);
539
540           selectionInfo.apply(viewPane);
541         }
542
543         @Override
544         public boolean isSelected(AnActionEvent event) {
545           if (isGlobalOptions()) return getGlobalOptions().getFlattenPackages();
546           return super.isSelected(event);
547         }
548       }).setAsSecondary(true);
549     }
550
551     class FlattenPackagesDependableAction extends PaneOptionAction {
552       FlattenPackagesDependableAction(@NotNull Map<String, Boolean> optionsMap,
553                                       @NotNull String text,
554                                       @NotNull String description,
555                                       @NotNull Icon icon,
556                                       boolean optionDefaultValue) {
557         super(optionsMap, text, description, icon, optionDefaultValue);
558       }
559
560       @Override
561       public void setSelected(AnActionEvent event, boolean flag) {
562         if (isGlobalOptions()) {
563           getGlobalOptions().setFlattenPackages(flag);
564         }
565         super.setSelected(event, flag);
566       }
567
568       @Override
569       public void update(AnActionEvent e) {
570         super.update(e);
571         final Presentation presentation = e.getPresentation();
572         presentation.setVisible(isFlattenPackages(myCurrentViewId));
573       }
574     }
575     if (ProjectViewDirectoryHelper.getInstance(myProject).supportsHideEmptyMiddlePackages()) {
576       myActionGroup.addAction(new HideEmptyMiddlePackagesAction()).setAsSecondary(true);
577     }
578     if (ProjectViewDirectoryHelper.getInstance(myProject).supportsFlattenPackages()) {
579       myActionGroup.addAction(new FlattenPackagesDependableAction(myAbbreviatePackageNames,
580                                                             IdeBundle.message("action.abbreviate.qualified.package.names"),
581                                                             IdeBundle.message("action.abbreviate.qualified.package.names"),
582                                                             AllIcons.ObjectBrowser.AbbreviatePackageNames,
583                                                             ourAbbreviatePackagesDefaults) {
584         @Override
585         public boolean isSelected(AnActionEvent event) {
586           return super.isSelected(event) && isAbbreviatePackageNames(myCurrentViewId);
587         }
588
589
590         @Override
591         public void update(AnActionEvent e) {
592           super.update(e);
593           if (ScopeViewPane.ID.equals(myCurrentViewId)) {
594             e.getPresentation().setEnabled(false);
595           }
596         }
597       }).setAsSecondary(true);
598     }
599
600     if (isShowMembersOptionSupported()) {
601       myActionGroup.addAction(new PaneOptionAction(myShowMembers, IdeBundle.message("action.show.members"),
602                                                    IdeBundle.message("action.show.hide.members"),
603                                                    AllIcons.ObjectBrowser.ShowMembers, ourShowMembersDefaults) {
604         @Override
605         public boolean isSelected(AnActionEvent event) {
606           if (isGlobalOptions()) return getGlobalOptions().getShowMembers();
607           return super.isSelected(event);
608         }
609
610         @Override
611         public void setSelected(AnActionEvent event, boolean flag) {
612           if (isGlobalOptions()) {
613             getGlobalOptions().setShowMembers(flag);
614           }
615           super.setSelected(event, flag);
616         }
617       })
618         .setAsSecondary(true);
619     }
620     myActionGroup.addAction(myAutoScrollToSourceHandler.createToggleAction()).setAsSecondary(true);
621     myActionGroup.addAction(myAutoScrollFromSourceHandler.createToggleAction()).setAsSecondary(true);
622     myActionGroup.addAction(new ManualOrderAction()).setAsSecondary(true);
623     myActionGroup.addAction(new SortByTypeAction()).setAsSecondary(true);
624     myActionGroup.addAction(new FoldersAlwaysOnTopAction()).setAsSecondary(true);
625
626     if (!myAutoScrollFromSourceHandler.isAutoScrollEnabled()) {
627       titleActions.add(new ScrollFromSourceAction());
628     }
629     AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() {
630       @Override
631       public void expandAll() {
632
633       }
634
635       @Override
636       public boolean canExpand() {
637         return false;
638       }
639
640       @Override
641       public void collapseAll() {
642         AbstractProjectViewPane pane = getCurrentProjectViewPane();
643         JTree tree = pane.myTree;
644         if (tree != null) {
645           TreeUtil.collapseAll(tree, 0);
646         }
647       }
648
649       @Override
650       public boolean canCollapse() {
651         return true;
652       }
653     }, getComponent());
654     collapseAllAction.getTemplatePresentation().setIcon(AllIcons.General.CollapseAll);
655     collapseAllAction.getTemplatePresentation().setHoveredIcon(AllIcons.General.CollapseAllHover);
656     titleActions.add(collapseAllAction);
657     getCurrentProjectViewPane().addToolbarActions(myActionGroup);
658
659     ToolWindowEx window = (ToolWindowEx)ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW);
660     if (window != null) {
661       window.setTitleActions(titleActions.toArray(new AnAction[titleActions.size()]));
662     }
663   }
664
665   protected boolean isShowMembersOptionSupported() {
666     return true;
667   }
668
669   @Override
670   public AbstractProjectViewPane getProjectViewPaneById(String id) {
671     if (!ApplicationManager.getApplication().isUnitTestMode()) {   // most tests don't need all panes to be loaded
672       ensurePanesLoaded();
673     }
674
675     final AbstractProjectViewPane pane = myId2Pane.get(id);
676     if (pane != null) {
677       return pane;
678     }
679     for (AbstractProjectViewPane viewPane : myUninitializedPanes) {
680       if (viewPane.getId().equals(id)) {
681         return viewPane;
682       }
683     }
684     return null;
685   }
686
687   @Override
688   public AbstractProjectViewPane getCurrentProjectViewPane() {
689     return getProjectViewPaneById(myCurrentViewId);
690   }
691
692   @Override
693   public void refresh() {
694     AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
695     if (currentProjectViewPane != null) {
696       // may be null for e.g. default project
697       currentProjectViewPane.updateFromRoot(false);
698     }
699   }
700
701   @Override
702   public void select(final Object element, VirtualFile file, boolean requestFocus) {
703     final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
704     if (viewPane != null) {
705       viewPane.select(element, file, requestFocus);
706     }
707   }
708
709   @NotNull
710   @Override
711   public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) {
712     final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
713     if (viewPane != null && viewPane instanceof AbstractProjectViewPSIPane) {
714       return ((AbstractProjectViewPSIPane)viewPane).selectCB(element, file, requestFocus);
715     }
716     select(element, file, requestFocus);
717     return ActionCallback.DONE;
718   }
719
720   @Override
721   public void dispose() {
722     myConnection.disconnect();
723   }
724
725   public JComponent getComponent() {
726     return myDataProvider;
727   }
728
729   @Override
730   public String getCurrentViewId() {
731     return myCurrentViewId;
732   }
733
734   @Override
735   public PsiElement getParentOfCurrentSelection() {
736     final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
737     if (viewPane == null) {
738       return null;
739     }
740     TreePath path = viewPane.getSelectedPath();
741     if (path == null) {
742       return null;
743     }
744     path = path.getParentPath();
745     if (path == null) {
746       return null;
747     }
748     DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
749     Object userObject = node.getUserObject();
750     if (userObject instanceof ProjectViewNode) {
751       ProjectViewNode descriptor = (ProjectViewNode)userObject;
752       Object element = descriptor.getValue();
753       if (element instanceof PsiElement) {
754         PsiElement psiElement = (PsiElement)element;
755         if (!psiElement.isValid()) return null;
756         return psiElement;
757       }
758       else {
759         return null;
760       }
761     }
762     return null;
763   }
764
765   public ContentManager getContentManager() {
766     if (myContentManager == null) {
767       ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).getContentManager();
768     }
769     return myContentManager;
770   }
771
772
773   private class PaneOptionAction extends ToggleAction implements DumbAware {
774     private final Map<String, Boolean> myOptionsMap;
775     private final boolean myOptionDefaultValue;
776
777     PaneOptionAction(@NotNull Map<String, Boolean> optionsMap,
778                      @NotNull String text,
779                      @NotNull String description,
780                      Icon icon,
781                      boolean optionDefaultValue) {
782       super(text, description, icon);
783       myOptionsMap = optionsMap;
784       myOptionDefaultValue = optionDefaultValue;
785     }
786
787     @Override
788     public boolean isSelected(AnActionEvent event) {
789       return getPaneOptionValue(myOptionsMap, myCurrentViewId, myOptionDefaultValue);
790     }
791
792     @Override
793     public void setSelected(AnActionEvent event, boolean flag) {
794       setPaneOption(myOptionsMap, flag, myCurrentViewId, true);
795     }
796   }
797
798   @Override
799   public void changeView() {
800     final List<AbstractProjectViewPane> views = new ArrayList<>(myId2Pane.values());
801     views.remove(getCurrentProjectViewPane());
802     Collections.sort(views, PANE_WEIGHT_COMPARATOR);
803
804     final JList list = new JBList(ArrayUtil.toObjectArray(views));
805     list.setCellRenderer(new DefaultListCellRenderer() {
806       @Override
807       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
808         super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
809         AbstractProjectViewPane pane = (AbstractProjectViewPane)value;
810         setText(pane.getTitle());
811         return this;
812       }
813     });
814
815     if (!views.isEmpty()) {
816       list.setSelectedValue(views.get(0), true);
817     }
818     Runnable runnable = () -> {
819       if (list.getSelectedIndex() < 0) return;
820       AbstractProjectViewPane pane = (AbstractProjectViewPane)list.getSelectedValue();
821       changeView(pane.getId());
822     };
823
824     new PopupChooserBuilder(list).
825       setTitle(IdeBundle.message("title.popup.views")).
826       setItemChoosenCallback(runnable).
827       createPopup().showInCenterOf(getComponent());
828   }
829
830   @Override
831   public void changeView(@NotNull String viewId) {
832     changeView(viewId, null);
833   }
834
835   @Override
836   public void changeView(@NotNull String viewId, @Nullable String subId) {
837     changeViewCB(viewId, subId);
838   }
839
840   @NotNull
841   @Override
842   public ActionCallback changeViewCB(@NotNull String viewId, String subId) {
843     AbstractProjectViewPane pane = getProjectViewPaneById(viewId);
844     LOG.assertTrue(pane != null, "Project view pane not found: " + viewId + "; subId:" + subId);
845     if (!viewId.equals(getCurrentViewId())
846         || subId != null && !subId.equals(pane.getSubId())) {
847       for (Content content : getContentManager().getContents()) {
848         if (viewId.equals(content.getUserData(ID_KEY)) && StringUtil.equals(subId, content.getUserData(SUB_ID_KEY))) {
849           return getContentManager().setSelectedContentCB(content);
850         }
851       }
852     }
853     return ActionCallback.REJECTED;
854   }
855
856   private final class MyDeletePSIElementProvider implements DeleteProvider {
857     @Override
858     public boolean canDeleteElement(@NotNull DataContext dataContext) {
859       final PsiElement[] elements = getElementsToDelete();
860       return DeleteHandler.shouldEnableDeleteAction(elements);
861     }
862
863     @Override
864     public void deleteElement(@NotNull DataContext dataContext) {
865       List<PsiElement> allElements = Arrays.asList(getElementsToDelete());
866       List<PsiElement> validElements = new ArrayList<>();
867       for (PsiElement psiElement : allElements) {
868         if (psiElement != null && psiElement.isValid()) validElements.add(psiElement);
869       }
870       final PsiElement[] elements = PsiUtilCore.toPsiElementArray(validElements);
871
872       LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
873       try {
874         DeleteHandler.deletePsiElement(elements, myProject);
875       }
876       finally {
877         a.finish();
878       }
879     }
880
881     @NotNull
882     private PsiElement[] getElementsToDelete() {
883       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
884       PsiElement[] elements = viewPane.getSelectedPSIElements();
885       for (int idx = 0; idx < elements.length; idx++) {
886         final PsiElement element = elements[idx];
887         if (element instanceof PsiDirectory) {
888           PsiDirectory directory = (PsiDirectory)element;
889           final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject);
890           if (isHideEmptyMiddlePackages(viewPane.getId()) && directory.getChildren().length == 0 && !directoryHelper.skipDirectory(directory)) {
891             while (true) {
892               PsiDirectory parent = directory.getParentDirectory();
893               if (parent == null) break;
894               if (directoryHelper.skipDirectory(parent) || PsiDirectoryFactory.getInstance(myProject).getQualifiedName(parent, false).length() == 0) break;
895               PsiElement[] children = parent.getChildren();
896               if (children.length == 0 || children.length == 1 && children[0] == directory) {
897                 directory = parent;
898               }
899               else {
900                 break;
901               }
902             }
903             elements[idx] = directory;
904           }
905           final VirtualFile virtualFile = directory.getVirtualFile();
906           final String path = virtualFile.getPath();
907           if (path.endsWith(JarFileSystem.JAR_SEPARATOR)) { // if is jar-file root
908             final VirtualFile vFile =
909               LocalFileSystem.getInstance().findFileByPath(path.substring(0, path.length() - JarFileSystem.JAR_SEPARATOR.length()));
910             if (vFile != null) {
911               final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
912               if (psiFile != null) {
913                 elements[idx] = psiFile;
914               }
915             }
916           }
917         }
918       }
919       return elements;
920     }
921
922   }
923
924   private final class MyPanel extends JPanel implements DataProvider {
925     MyPanel() {
926       super(new BorderLayout());
927     }
928
929     @Nullable
930     private Object getSelectedNodeElement() {
931       final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
932       if (currentProjectViewPane == null) { // can happen if not initialized yet
933         return null;
934       }
935       DefaultMutableTreeNode node = currentProjectViewPane.getSelectedNode();
936       if (node == null) {
937         return null;
938       }
939       Object userObject = node.getUserObject();
940       if (userObject instanceof AbstractTreeNode) {
941         return ((AbstractTreeNode)userObject).getValue();
942       }
943       if (!(userObject instanceof NodeDescriptor)) {
944         return null;
945       }
946       return ((NodeDescriptor)userObject).getElement();
947     }
948
949     @Override
950     public Object getData(String dataId) {
951       final AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane();
952       if (currentProjectViewPane != null) {
953         final Object paneSpecificData = currentProjectViewPane.getData(dataId);
954         if (paneSpecificData != null) return paneSpecificData;
955       }
956
957       if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
958         if (currentProjectViewPane == null) return null;
959         final PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
960         return elements.length == 1 ? elements[0] : null;
961       }
962       if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
963         if (currentProjectViewPane == null) {
964           return null;
965         }
966         PsiElement[] elements = currentProjectViewPane.getSelectedPSIElements();
967         return elements.length == 0 ? null : elements;
968       }
969       if (LangDataKeys.MODULE.is(dataId)) {
970         VirtualFile[] virtualFiles = (VirtualFile[])getData(CommonDataKeys.VIRTUAL_FILE_ARRAY.getName());
971         if (virtualFiles == null || virtualFiles.length <= 1) return null;
972         final Set<Module> modules = new HashSet<>();
973         for (VirtualFile virtualFile : virtualFiles) {
974           modules.add(ModuleUtilCore.findModuleForFile(virtualFile, myProject));
975         }
976         return modules.size() == 1 ? modules.iterator().next() : null;
977       }
978       if (LangDataKeys.TARGET_PSI_ELEMENT.is(dataId)) {
979         return null;
980       }
981       if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
982         return myCopyPasteDelegator.getCutProvider();
983       }
984       if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
985         return myCopyPasteDelegator.getCopyProvider();
986       }
987       if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
988         return myCopyPasteDelegator.getPasteProvider();
989       }
990       if (LangDataKeys.IDE_VIEW.is(dataId)) {
991         return myIdeView;
992       }
993       if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
994         final Module[] modules = getSelectedModules();
995         if (modules != null) {
996           return myDeleteModuleProvider;
997         }
998         final LibraryOrderEntry orderEntry = getSelectedLibrary();
999         if (orderEntry != null) {
1000           return new DeleteProvider() {
1001             @Override
1002             public void deleteElement(@NotNull DataContext dataContext) {
1003               detachLibrary(orderEntry, myProject);
1004             }
1005
1006             @Override
1007             public boolean canDeleteElement(@NotNull DataContext dataContext) {
1008               return true;
1009             }
1010           };
1011         }
1012         return myDeletePSIElementProvider;
1013       }
1014       if (PlatformDataKeys.HELP_ID.is(dataId)) {
1015         return HelpID.PROJECT_VIEWS;
1016       }
1017       if (ProjectViewImpl.DATA_KEY.is(dataId)) {
1018         return ProjectViewImpl.this;
1019       }
1020       if (PlatformDataKeys.PROJECT_CONTEXT.is(dataId)) {
1021         Object selected = getSelectedNodeElement();
1022         return selected instanceof Project ? selected : null;
1023       }
1024       if (LangDataKeys.MODULE_CONTEXT.is(dataId)) {
1025         Object selected = getSelectedNodeElement();
1026         if (selected instanceof Module) {
1027           return !((Module)selected).isDisposed() ? selected : null;
1028         }
1029         else if (selected instanceof PsiDirectory) {
1030           return moduleBySingleContentRoot(((PsiDirectory)selected).getVirtualFile());
1031         }
1032         else if (selected instanceof VirtualFile) {
1033           return moduleBySingleContentRoot((VirtualFile)selected);
1034         }
1035         else {
1036           return null;
1037         }
1038       }
1039
1040       if (LangDataKeys.MODULE_CONTEXT_ARRAY.is(dataId)) {
1041         return getSelectedModules();
1042       }
1043       if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
1044         final List<ModuleGroup> selectedElements = getSelectedElements(ModuleGroup.class);
1045         return selectedElements.isEmpty() ? null : selectedElements.toArray(new ModuleGroup[selectedElements.size()]);
1046       }
1047       if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
1048         final List<LibraryGroupElement> selectedElements = getSelectedElements(LibraryGroupElement.class);
1049         return selectedElements.isEmpty() ? null : selectedElements.toArray(new LibraryGroupElement[selectedElements.size()]);
1050       }
1051       if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
1052         final List<NamedLibraryElement> selectedElements = getSelectedElements(NamedLibraryElement.class);
1053         return selectedElements.isEmpty() ? null : selectedElements.toArray(new NamedLibraryElement[selectedElements.size()]);
1054       }
1055
1056       return null;
1057     }
1058
1059     @Nullable
1060     private LibraryOrderEntry getSelectedLibrary() {
1061       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1062       DefaultMutableTreeNode node = viewPane != null ? viewPane.getSelectedNode() : null;
1063       if (node == null) return null;
1064       DefaultMutableTreeNode parent = (DefaultMutableTreeNode)node.getParent();
1065       if (parent == null) return null;
1066       Object userObject = parent.getUserObject();
1067       if (userObject instanceof LibraryGroupNode) {
1068         userObject = node.getUserObject();
1069         if (userObject instanceof NamedLibraryElementNode) {
1070           NamedLibraryElement element = ((NamedLibraryElementNode)userObject).getValue();
1071           OrderEntry orderEntry = element.getOrderEntry();
1072           return orderEntry instanceof LibraryOrderEntry ? (LibraryOrderEntry)orderEntry : null;
1073         }
1074         PsiDirectory directory = ((PsiDirectoryNode)userObject).getValue();
1075         VirtualFile virtualFile = directory.getVirtualFile();
1076         Module module = (Module)((AbstractTreeNode)((DefaultMutableTreeNode)parent.getParent()).getUserObject()).getValue();
1077
1078         if (module == null) return null;
1079         ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
1080         OrderEntry entry = index.getOrderEntryForFile(virtualFile);
1081         if (entry instanceof LibraryOrderEntry) {
1082           return (LibraryOrderEntry)entry;
1083         }
1084       }
1085
1086       return null;
1087     }
1088
1089     private void detachLibrary(@NotNull final LibraryOrderEntry orderEntry, @NotNull Project project) {
1090       final Module module = orderEntry.getOwnerModule();
1091       String message = IdeBundle.message("detach.library.from.module", orderEntry.getPresentableName(), module.getName());
1092       String title = IdeBundle.message("detach.library");
1093       int ret = Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon());
1094       if (ret != Messages.OK) return;
1095       CommandProcessor.getInstance().executeCommand(module.getProject(), () -> {
1096         final Runnable action = () -> {
1097           ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
1098           OrderEntry[] orderEntries = rootManager.getOrderEntries();
1099           ModifiableRootModel model = rootManager.getModifiableModel();
1100           OrderEntry[] modifiableEntries = model.getOrderEntries();
1101           for (int i = 0; i < orderEntries.length; i++) {
1102             OrderEntry entry = orderEntries[i];
1103             if (entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() == orderEntry.getLibrary()) {
1104               model.removeOrderEntry(modifiableEntries[i]);
1105             }
1106           }
1107           model.commit();
1108         };
1109         ApplicationManager.getApplication().runWriteAction(action);
1110       }, title, null);
1111     }
1112
1113     @Nullable
1114     private Module[] getSelectedModules() {
1115       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1116       if (viewPane == null) return null;
1117       final Object[] elements = viewPane.getSelectedElements();
1118       ArrayList<Module> result = new ArrayList<>();
1119       for (Object element : elements) {
1120         if (element instanceof Module) {
1121           final Module module = (Module)element;
1122           if (!module.isDisposed()) {
1123             result.add(module);
1124           }
1125         }
1126         else if (element instanceof ModuleGroup) {
1127           Collection<Module> modules = ((ModuleGroup)element).modulesInGroup(myProject, true);
1128           result.addAll(modules);
1129         }
1130         else if (element instanceof PsiDirectory) {
1131           Module module = moduleBySingleContentRoot(((PsiDirectory)element).getVirtualFile());
1132           if (module != null) result.add(module);
1133         }
1134         else if (element instanceof VirtualFile) {
1135           Module module = moduleBySingleContentRoot((VirtualFile)element);
1136           if (module != null) result.add(module);
1137         }
1138       }
1139
1140       if (result.isEmpty()) {
1141         return null;
1142       }
1143       else {
1144         return result.toArray(new Module[result.size()]);
1145       }
1146     }
1147   }
1148
1149   /** Project view has the same node for module and its single content root 
1150    *   => MODULE_CONTEXT data key should return the module when its content root is selected
1151    *  When there are multiple content roots, they have different nodes under the module node
1152    *   => MODULE_CONTEXT should be only available for the module node
1153    *      otherwise VirtualFileArrayRule will return all module's content roots when just one of them is selected
1154    */
1155   @Nullable
1156   private Module moduleBySingleContentRoot(@NotNull VirtualFile file) {
1157     if (ProjectRootsUtil.isModuleContentRoot(file, myProject)) {
1158       Module module = ProjectRootManager.getInstance(myProject).getFileIndex().getModuleForFile(file);
1159       if (module != null && !module.isDisposed() && ModuleRootManager.getInstance(module).getContentRoots().length == 1) {
1160         return module;
1161       }
1162     }
1163
1164     return null;
1165   }
1166
1167   @NotNull
1168   private <T> List<T> getSelectedElements(@NotNull Class<T> klass) {
1169     List<T> result = new ArrayList<>();
1170     final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1171     if (viewPane == null) return result;
1172     final Object[] elements = viewPane.getSelectedElements();
1173     for (Object element : elements) {
1174       //element still valid
1175       if (element != null && klass.isAssignableFrom(element.getClass())) {
1176         result.add((T)element);
1177       }
1178     }
1179     return result;
1180   }
1181
1182   private final class MyIdeView implements IdeView {
1183     @Override
1184     public void selectElement(PsiElement element) {
1185       selectPsiElement(element, false);
1186       boolean requestFocus = true;
1187       if (element != null) {
1188         final boolean isDirectory = element instanceof PsiDirectory;
1189         if (!isDirectory) {
1190           FileEditor editor = EditorHelper.openInEditor(element, false);
1191           if (editor != null) {
1192             ToolWindowManager.getInstance(myProject).activateEditorComponent();
1193             requestFocus = false;
1194           }
1195         }
1196       }
1197
1198       if (requestFocus) {
1199         selectPsiElement(element, true);
1200       }
1201     }
1202
1203     @NotNull
1204     @Override
1205     public PsiDirectory[] getDirectories() {
1206       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1207       if (viewPane != null) {
1208         return viewPane.getSelectedDirectories();
1209       }
1210
1211       return PsiDirectory.EMPTY_ARRAY;
1212     }
1213
1214     @Override
1215     public PsiDirectory getOrChooseDirectory() {
1216       return DirectoryChooserUtil.getOrChooseDirectory(this);
1217     }
1218   }
1219
1220   @Override
1221   public void selectPsiElement(PsiElement element, boolean requestFocus) {
1222     if (element == null) return;
1223     VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element);
1224     select(element, virtualFile, requestFocus);
1225   }
1226
1227
1228   private static void readOption(Element node, @NotNull Map<String, Boolean> options) {
1229     if (node == null) return;
1230     for (Attribute attribute : node.getAttributes()) {
1231       options.put(attribute.getName(), Boolean.TRUE.toString().equals(attribute.getValue()) ? Boolean.TRUE : Boolean.FALSE);
1232     }
1233   }
1234
1235   private static void writeOption(@NotNull Element parentNode, @NotNull Map<String, Boolean> optionsForPanes, @NotNull String optionName) {
1236     Element e = new Element(optionName);
1237     for (Map.Entry<String, Boolean> entry : optionsForPanes.entrySet()) {
1238       final String key = entry.getKey();
1239       if (key != null) { //SCR48267
1240         e.setAttribute(key, Boolean.toString(entry.getValue().booleanValue()));
1241       }
1242     }
1243
1244     parentNode.addContent(e);
1245   }
1246
1247   @Override
1248   public void loadState(Element parentNode) {
1249     Element navigatorElement = parentNode.getChild(ELEMENT_NAVIGATOR);
1250     if (navigatorElement != null) {
1251       mySavedPaneId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_VIEW);
1252       mySavedPaneSubId = navigatorElement.getAttributeValue(ATTRIBUTE_CURRENT_SUBVIEW);
1253       if (mySavedPaneId == null) {
1254         mySavedPaneId = ProjectViewPane.ID;
1255         mySavedPaneSubId = null;
1256       }
1257       readOption(navigatorElement.getChild(ELEMENT_FLATTEN_PACKAGES), myFlattenPackages);
1258       readOption(navigatorElement.getChild(ELEMENT_SHOW_MEMBERS), myShowMembers);
1259       readOption(navigatorElement.getChild(ELEMENT_SHOW_MODULES), myShowModules);
1260       readOption(navigatorElement.getChild(ELEMENT_SHOW_LIBRARY_CONTENTS), myShowLibraryContents);
1261       readOption(navigatorElement.getChild(ELEMENT_HIDE_EMPTY_PACKAGES), myHideEmptyPackages);
1262       readOption(navigatorElement.getChild(ELEMENT_ABBREVIATE_PACKAGE_NAMES), myAbbreviatePackageNames);
1263       readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_TO_SOURCE), myAutoscrollToSource);
1264       readOption(navigatorElement.getChild(ELEMENT_AUTOSCROLL_FROM_SOURCE), myAutoscrollFromSource);
1265       readOption(navigatorElement.getChild(ELEMENT_SORT_BY_TYPE), mySortByType);
1266       readOption(navigatorElement.getChild(ELEMENT_MANUAL_ORDER), myManualOrder);
1267
1268       Element foldersElement = navigatorElement.getChild(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1269       if (foldersElement != null) myFoldersAlwaysOnTop = Boolean.valueOf(foldersElement.getAttributeValue("value"));
1270       
1271       try {
1272         splitterProportions.readExternal(navigatorElement);
1273       }
1274       catch (InvalidDataException e) {
1275         // ignore
1276       }
1277     }
1278     Element panesElement = parentNode.getChild(ELEMENT_PANES);
1279     if (panesElement != null) {
1280       readPaneState(panesElement);
1281     }
1282   }
1283
1284   private void readPaneState(@NotNull Element panesElement) {
1285     @SuppressWarnings({"unchecked"})
1286     final List<Element> paneElements = panesElement.getChildren(ELEMENT_PANE);
1287
1288     for (Element paneElement : paneElements) {
1289       String paneId = paneElement.getAttributeValue(ATTRIBUTE_ID);
1290       final AbstractProjectViewPane pane = myId2Pane.get(paneId);
1291       if (pane != null) {
1292         try {
1293           pane.readExternal(paneElement);
1294         }
1295         catch (InvalidDataException e) {
1296           // ignore
1297         }
1298       }
1299       else {
1300         myUninitializedPaneState.put(paneId, paneElement);
1301       }
1302     }
1303   }
1304
1305   @Override
1306   public Element getState() {
1307     Element parentNode = new Element("projectView");
1308     Element navigatorElement = new Element(ELEMENT_NAVIGATOR);
1309     AbstractProjectViewPane currentPane = getCurrentProjectViewPane();
1310     if (currentPane != null) {
1311       navigatorElement.setAttribute(ATTRIBUTE_CURRENT_VIEW, currentPane.getId());
1312       String subId = currentPane.getSubId();
1313       if (subId != null) {
1314         navigatorElement.setAttribute(ATTRIBUTE_CURRENT_SUBVIEW, subId);
1315       }
1316     }
1317     writeOption(navigatorElement, myFlattenPackages, ELEMENT_FLATTEN_PACKAGES);
1318     writeOption(navigatorElement, myShowMembers, ELEMENT_SHOW_MEMBERS);
1319     writeOption(navigatorElement, myShowModules, ELEMENT_SHOW_MODULES);
1320     writeOption(navigatorElement, myShowLibraryContents, ELEMENT_SHOW_LIBRARY_CONTENTS);
1321     writeOption(navigatorElement, myHideEmptyPackages, ELEMENT_HIDE_EMPTY_PACKAGES);
1322     writeOption(navigatorElement, myAbbreviatePackageNames, ELEMENT_ABBREVIATE_PACKAGE_NAMES);
1323     writeOption(navigatorElement, myAutoscrollToSource, ELEMENT_AUTOSCROLL_TO_SOURCE);
1324     writeOption(navigatorElement, myAutoscrollFromSource, ELEMENT_AUTOSCROLL_FROM_SOURCE);
1325     writeOption(navigatorElement, mySortByType, ELEMENT_SORT_BY_TYPE);
1326     writeOption(navigatorElement, myManualOrder, ELEMENT_MANUAL_ORDER);
1327     
1328     Element foldersElement = new Element(ELEMENT_FOLDERS_ALWAYS_ON_TOP);
1329     foldersElement.setAttribute("value", Boolean.toString(myFoldersAlwaysOnTop));
1330     navigatorElement.addContent(foldersElement);
1331
1332     splitterProportions.saveSplitterProportions(myPanel);
1333     try {
1334       splitterProportions.writeExternal(navigatorElement);
1335     }
1336     catch (WriteExternalException e) {
1337       // ignore
1338     }
1339     parentNode.addContent(navigatorElement);
1340
1341     Element panesElement = new Element(ELEMENT_PANES);
1342     writePaneState(panesElement);
1343     parentNode.addContent(panesElement);
1344     return parentNode;
1345   }
1346
1347   private void writePaneState(@NotNull Element panesElement) {
1348     for (AbstractProjectViewPane pane : myId2Pane.values()) {
1349       Element paneElement = new Element(ELEMENT_PANE);
1350       paneElement.setAttribute(ATTRIBUTE_ID, pane.getId());
1351       try {
1352         pane.writeExternal(paneElement);
1353       }
1354       catch (WriteExternalException e) {
1355         continue;
1356       }
1357       panesElement.addContent(paneElement);
1358     }
1359     for (Element element : myUninitializedPaneState.values()) {
1360       panesElement.addContent(element.clone());
1361     }
1362   }
1363
1364   boolean isGlobalOptions() {
1365     return Registry.is("ide.projectView.globalOptions");
1366   }
1367
1368   ProjectViewSharedSettings getGlobalOptions() {
1369     return ProjectViewSharedSettings.Companion.getInstance();
1370   }
1371
1372   @Override
1373   public boolean isAutoscrollToSource(String paneId) {
1374     if (isGlobalOptions()) {
1375       return getGlobalOptions().getAutoscrollToSource();
1376     }
1377
1378     return getPaneOptionValue(myAutoscrollToSource, paneId, UISettings.getInstance().DEFAULT_AUTOSCROLL_TO_SOURCE);
1379   }
1380
1381   public void setAutoscrollToSource(boolean autoscrollMode, String paneId) {
1382     if (isGlobalOptions()) {
1383       getGlobalOptions().setAutoscrollToSource(autoscrollMode);
1384     }
1385     myAutoscrollToSource.put(paneId, autoscrollMode);
1386   }
1387
1388   @Override
1389   public boolean isAutoscrollFromSource(String paneId) {
1390     if (isGlobalOptions()) {
1391       return getGlobalOptions().getAutoscrollFromSource();
1392     }
1393
1394     return getPaneOptionValue(myAutoscrollFromSource, paneId, ourAutoscrollFromSourceDefaults);
1395   }
1396
1397   public void setAutoscrollFromSource(boolean autoscrollMode, String paneId) {
1398     if (isGlobalOptions()) {
1399       getGlobalOptions().setAutoscrollFromSource(autoscrollMode);
1400     }
1401     setPaneOption(myAutoscrollFromSource, autoscrollMode, paneId, false);
1402   }
1403
1404   @Override
1405   public boolean isFlattenPackages(String paneId) {
1406     if (isGlobalOptions()) {
1407       return getGlobalOptions().getFlattenPackages();
1408     }
1409
1410     return getPaneOptionValue(myFlattenPackages, paneId, ourFlattenPackagesDefaults);
1411   }
1412
1413   public void setFlattenPackages(boolean flattenPackages, String paneId) {
1414     if (isGlobalOptions()) {
1415       getGlobalOptions().setFlattenPackages(flattenPackages);
1416       for (String pane : myFlattenPackages.keySet()) {
1417         setPaneOption(myFlattenPackages, flattenPackages, pane, true);
1418       }
1419     }
1420     setPaneOption(myFlattenPackages, flattenPackages, paneId, true);
1421   }
1422
1423   public boolean isFoldersAlwaysOnTop() {
1424     if (isGlobalOptions()) {
1425       return getGlobalOptions().getFoldersAlwaysOnTop();
1426     }
1427
1428     return myFoldersAlwaysOnTop;
1429   }
1430
1431   public void setFoldersAlwaysOnTop(boolean foldersAlwaysOnTop) {
1432     if (isGlobalOptions()) {
1433       getGlobalOptions().setFoldersAlwaysOnTop(foldersAlwaysOnTop);
1434     }
1435
1436     if (myFoldersAlwaysOnTop != foldersAlwaysOnTop) {
1437       myFoldersAlwaysOnTop = foldersAlwaysOnTop;
1438       for (AbstractProjectViewPane pane : myId2Pane.values()) {
1439         if (pane.getTree() != null) {
1440           pane.updateFromRoot(false);
1441         }
1442       }
1443     }
1444   }
1445
1446   @Override
1447   public boolean isShowMembers(String paneId) {
1448     if (isGlobalOptions()) {
1449       return getGlobalOptions().getShowMembers();
1450     }
1451
1452     return getPaneOptionValue(myShowMembers, paneId, ourShowMembersDefaults);
1453   }
1454
1455   public void setShowMembers(boolean showMembers, String paneId) {
1456     setPaneOption(myShowMembers, showMembers, paneId, true);
1457   }
1458
1459   @Override
1460   public boolean isHideEmptyMiddlePackages(String paneId) {
1461     if (isGlobalOptions()) {
1462       return getGlobalOptions().getHideEmptyPackages();
1463     }
1464
1465     return getPaneOptionValue(myHideEmptyPackages, paneId, ourHideEmptyPackagesDefaults);
1466   }
1467
1468   @Override
1469   public boolean isAbbreviatePackageNames(String paneId) {
1470     if (isGlobalOptions()) {
1471       return getGlobalOptions().getAbbreviatePackages();
1472     }
1473
1474     return getPaneOptionValue(myAbbreviatePackageNames, paneId, ourAbbreviatePackagesDefaults);
1475   }
1476
1477   @Override
1478   public boolean isShowLibraryContents(String paneId) {
1479     if (isGlobalOptions()) {
1480       return getGlobalOptions().getShowLibraryContents();
1481     }
1482
1483     return getPaneOptionValue(myShowLibraryContents, paneId, ourShowLibraryContentsDefaults);
1484   }
1485
1486   @Override
1487   public void setShowLibraryContents(boolean showLibraryContents, @NotNull String paneId) {
1488     if (isGlobalOptions()) {
1489       getGlobalOptions().setShowLibraryContents(showLibraryContents);
1490     }
1491     setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1492   }
1493
1494   @NotNull
1495   public ActionCallback setShowLibraryContentsCB(boolean showLibraryContents, String paneId) {
1496     return setPaneOption(myShowLibraryContents, showLibraryContents, paneId, true);
1497   }
1498
1499   @Override
1500   public boolean isShowModules(String paneId) {
1501     if (isGlobalOptions()) {
1502       return getGlobalOptions().getShowModules();
1503     }
1504
1505     return getPaneOptionValue(myShowModules, paneId, ourShowModulesDefaults);
1506   }
1507
1508   @Override
1509   public void setShowModules(boolean showModules, @NotNull String paneId) {
1510     if (isGlobalOptions()) {
1511       getGlobalOptions().setShowModules(showModules);
1512     }
1513     setPaneOption(myShowModules, showModules, paneId, true);
1514   }
1515
1516   @Override
1517   public void setHideEmptyPackages(boolean hideEmptyPackages, @NotNull String paneId) {
1518     if (isGlobalOptions()) {
1519       getGlobalOptions().setHideEmptyPackages(hideEmptyPackages);
1520       for (String pane : myHideEmptyPackages.keySet()) {
1521         setPaneOption(myHideEmptyPackages, hideEmptyPackages, pane, true);
1522       }
1523     }
1524     setPaneOption(myHideEmptyPackages, hideEmptyPackages, paneId, true);
1525   }
1526
1527   @Override
1528   public void setAbbreviatePackageNames(boolean abbreviatePackageNames, @NotNull String paneId) {
1529     if (isGlobalOptions()) {
1530       getGlobalOptions().setAbbreviatePackages(abbreviatePackageNames);
1531     }
1532     setPaneOption(myAbbreviatePackageNames, abbreviatePackageNames, paneId, true);
1533   }
1534
1535   @NotNull
1536   private ActionCallback setPaneOption(@NotNull Map<String, Boolean> optionsMap, boolean value, String paneId, final boolean updatePane) {
1537     if (paneId != null) {
1538       optionsMap.put(paneId, value);
1539       if (updatePane) {
1540         final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1541         if (pane != null) {
1542           return pane.updateFromRoot(false);
1543         }
1544       }
1545     }
1546     return ActionCallback.DONE;
1547   }
1548
1549   private static boolean getPaneOptionValue(@NotNull Map<String, Boolean> optionsMap, String paneId, boolean defaultValue) {
1550     final Boolean value = optionsMap.get(paneId);
1551     return value == null ? defaultValue : value.booleanValue();
1552   }
1553
1554   private class HideEmptyMiddlePackagesAction extends PaneOptionAction {
1555     private HideEmptyMiddlePackagesAction() {
1556       super(myHideEmptyPackages, "", "", null, ourHideEmptyPackagesDefaults);
1557     }
1558
1559     @Override
1560     public void setSelected(AnActionEvent event, boolean flag) {
1561       final AbstractProjectViewPane viewPane = getCurrentProjectViewPane();
1562       final SelectionInfo selectionInfo = SelectionInfo.create(viewPane);
1563
1564       if (isGlobalOptions()) {
1565         getGlobalOptions().setHideEmptyPackages(flag);
1566       }
1567       super.setSelected(event, flag);
1568
1569       selectionInfo.apply(viewPane);
1570     }
1571
1572     @Override
1573     public boolean isSelected(AnActionEvent event) {
1574       if (isGlobalOptions()) return getGlobalOptions().getHideEmptyPackages();
1575       return super.isSelected(event);
1576     }
1577
1578     @Override
1579     public void update(AnActionEvent e) {
1580       super.update(e);
1581       final Presentation presentation = e.getPresentation();
1582       if (isHideEmptyMiddlePackages(myCurrentViewId)) {
1583         presentation.setText(IdeBundle.message("action.hide.empty.middle.packages"));
1584         presentation.setDescription(IdeBundle.message("action.show.hide.empty.middle.packages"));
1585       }
1586       else {
1587         presentation.setText(IdeBundle.message("action.compact.empty.middle.packages"));
1588         presentation.setDescription(IdeBundle.message("action.show.compact.empty.middle.packages"));
1589       }
1590     }
1591   }
1592
1593   private static class SelectionInfo {
1594     private final Object[] myElements;
1595
1596     private SelectionInfo(@NotNull Object[] elements) {
1597       myElements = elements;
1598     }
1599
1600     public void apply(final AbstractProjectViewPane viewPane) {
1601       if (viewPane == null) {
1602         return;
1603       }
1604       AbstractTreeBuilder treeBuilder = viewPane.getTreeBuilder();
1605       JTree tree = viewPane.myTree;
1606       DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel();
1607       List<TreePath> paths = new ArrayList<>(myElements.length);
1608       for (final Object element : myElements) {
1609         DefaultMutableTreeNode node = treeBuilder.getNodeForElement(element);
1610         if (node == null) {
1611           treeBuilder.buildNodeForElement(element);
1612           node = treeBuilder.getNodeForElement(element);
1613         }
1614         if (node != null) {
1615           paths.add(new TreePath(treeModel.getPathToRoot(node)));
1616         }
1617       }
1618       if (!paths.isEmpty()) {
1619         tree.setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
1620       }
1621     }
1622
1623     @NotNull
1624     public static SelectionInfo create(final AbstractProjectViewPane viewPane) {
1625       List<Object> selectedElements = Collections.emptyList();
1626       if (viewPane != null) {
1627         final TreePath[] selectionPaths = viewPane.getSelectionPaths();
1628         if (selectionPaths != null) {
1629           selectedElements = new ArrayList<>();
1630           for (TreePath path : selectionPaths) {
1631             final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
1632             final Object userObject = node.getUserObject();
1633             if (userObject instanceof NodeDescriptor) {
1634               selectedElements.add(((NodeDescriptor)userObject).getElement());
1635             }
1636           }
1637         }
1638       }
1639       return new SelectionInfo(selectedElements.toArray());
1640     }
1641   }
1642
1643   private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
1644     private MyAutoScrollFromSourceHandler() {
1645       super(ProjectViewImpl.this.myProject, myViewContentPanel, ProjectViewImpl.this);
1646     }
1647
1648     @Override
1649     protected void selectElementFromEditor(@NotNull FileEditor fileEditor) {
1650       if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return;
1651       if (isAutoscrollFromSource(getCurrentViewId())) {
1652         if (fileEditor instanceof TextEditor) {
1653           Editor editor = ((TextEditor)fileEditor).getEditor();
1654           selectElementAtCaretNotLosingFocus(editor);
1655         }
1656         else {
1657           final VirtualFile file = FileEditorManagerEx.getInstanceEx(myProject).getFile(fileEditor);
1658           if (file != null) {
1659             final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file);
1660             if (psiFile != null) {
1661               final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1662               if (target != null) {
1663                 final MySelectInContext selectInContext = new MySelectInContext(psiFile, null) {
1664                   @Override
1665                   public Object getSelectorInFile() {
1666                     return psiFile;
1667                   }
1668                 };
1669
1670                 if (target.canSelect(selectInContext)) {
1671                   target.selectIn(selectInContext, false);
1672                 }
1673               }
1674             }
1675           }
1676         }
1677       }
1678     }
1679
1680     public void scrollFromSource() {
1681       final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
1682       final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor();
1683       if (selectedTextEditor != null) {
1684         selectElementAtCaret(selectedTextEditor);
1685         return;
1686       }
1687       final FileEditor[] editors = fileEditorManager.getSelectedEditors();
1688       for (FileEditor fileEditor : editors) {
1689         if (fileEditor instanceof TextEditor) {
1690           Editor editor = ((TextEditor)fileEditor).getEditor();
1691           selectElementAtCaret(editor);
1692           return;
1693         }
1694       }
1695       final VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles();
1696       if (selectedFiles.length > 0) {
1697         final PsiFile file = PsiManager.getInstance(myProject).findFile(selectedFiles[0]);
1698         if (file != null) {
1699           scrollFromFile(file, null);
1700         }
1701       }
1702     }
1703
1704     private void selectElementAtCaretNotLosingFocus(@NotNull Editor editor) {
1705       AbstractProjectViewPane pane = getCurrentProjectViewPane();
1706       if (pane != null && !IJSwingUtilities.hasFocus(pane.getComponentToFocus())) {
1707         selectElementAtCaret(editor);
1708       }
1709     }
1710
1711     private void selectElementAtCaret(@NotNull Editor editor) {
1712       final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
1713       if (file == null) return;
1714
1715       scrollFromFile(file, editor);
1716     }
1717
1718     private void scrollFromFile(@NotNull PsiFile file, @Nullable Editor editor) {
1719       PsiDocumentManager.getInstance(myProject).performWhenAllCommitted(() -> {
1720         final MySelectInContext selectInContext = new MySelectInContext(file, editor);
1721
1722         final SelectInTarget target = mySelectInTargets.get(getCurrentViewId());
1723         if (target != null && target.canSelect(selectInContext)) {
1724           target.selectIn(selectInContext, false);
1725         }
1726       });
1727     }
1728
1729     @Override
1730     protected boolean isAutoScrollEnabled() {
1731       return isAutoscrollFromSource(myCurrentViewId);
1732     }
1733
1734     @Override
1735     protected void setAutoScrollEnabled(boolean state) {
1736       setAutoscrollFromSource(state, myCurrentViewId);
1737       if (state) {
1738         final Editor editor = myFileEditorManager.getSelectedTextEditor();
1739         if (editor != null) {
1740           selectElementAtCaretNotLosingFocus(editor);
1741         }
1742       }
1743       createToolbarActions();
1744     }
1745
1746     private class MySelectInContext implements SelectInContext {
1747       @NotNull private final PsiFile myPsiFile;
1748       @Nullable private final Editor myEditor;
1749
1750       private MySelectInContext(@NotNull PsiFile psiFile, @Nullable Editor editor) {
1751         myPsiFile = psiFile;
1752         myEditor = editor;
1753       }
1754
1755       @Override
1756       @NotNull
1757       public Project getProject() {
1758         return myProject;
1759       }
1760
1761       @NotNull
1762       private PsiFile getPsiFile() {
1763         return myPsiFile;
1764       }
1765
1766       @Override
1767       @NotNull
1768       public FileEditorProvider getFileEditorProvider() {
1769         return new FileEditorProvider() {
1770           @Override
1771           public FileEditor openFileEditor() {
1772             return myFileEditorManager.openFile(myPsiFile.getContainingFile().getVirtualFile(), false)[0];
1773           }
1774         };
1775       }
1776
1777       @NotNull
1778       private PsiElement getPsiElement() {
1779         PsiElement e = null;
1780         if (myEditor != null) {
1781           final int offset = myEditor.getCaretModel().getOffset();
1782           if (PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) {
1783             PsiDocumentManager.getInstance(myProject).commitAllDocuments();
1784           }
1785           e = getPsiFile().findElementAt(offset);
1786         }
1787         if (e == null) {
1788           e = getPsiFile();
1789         }
1790         return e;
1791       }
1792
1793       @Override
1794       @NotNull
1795       public VirtualFile getVirtualFile() {
1796         return getPsiFile().getVirtualFile();
1797       }
1798
1799       @Override
1800       public Object getSelectorInFile() {
1801         return getPsiElement();
1802       }
1803     }
1804   }
1805
1806   @Override
1807   public boolean isManualOrder(String paneId) {
1808     return getPaneOptionValue(myManualOrder, paneId, ourManualOrderDefaults);
1809   }
1810
1811   @Override
1812   public void setManualOrder(@NotNull String paneId, final boolean enabled) {
1813     setPaneOption(myManualOrder, enabled, paneId, false);
1814     final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1815     pane.installComparator();
1816   }
1817
1818   @Override
1819   public boolean isSortByType(String paneId) {
1820     return getPaneOptionValue(mySortByType, paneId, ourSortByTypeDefaults);
1821   }
1822
1823   @Override
1824   public void setSortByType(@NotNull String paneId, final boolean sortByType) {
1825     setPaneOption(mySortByType, sortByType, paneId, false);
1826     final AbstractProjectViewPane pane = getProjectViewPaneById(paneId);
1827     pane.installComparator();
1828   }
1829
1830   private class ManualOrderAction extends ToggleAction implements DumbAware {
1831     private ManualOrderAction() {
1832       super(IdeBundle.message("action.manual.order"), IdeBundle.message("action.manual.order"), AllIcons.ObjectBrowser.Sorted);
1833     }
1834
1835     @Override
1836     public boolean isSelected(AnActionEvent event) {
1837       return isManualOrder(getCurrentViewId());
1838     }
1839
1840     @Override
1841     public void setSelected(AnActionEvent event, boolean flag) {
1842       setManualOrder(getCurrentViewId(), flag);
1843     }
1844
1845     @Override
1846     public void update(final AnActionEvent e) {
1847       super.update(e);
1848       final Presentation presentation = e.getPresentation();
1849       AbstractProjectViewPane pane = getCurrentProjectViewPane();
1850       presentation.setEnabledAndVisible(pane != null && pane.supportsManualOrder());
1851     }
1852   }
1853   
1854   private class SortByTypeAction extends ToggleAction implements DumbAware {
1855     private SortByTypeAction() {
1856       super(IdeBundle.message("action.sort.by.type"), IdeBundle.message("action.sort.by.type"), AllIcons.ObjectBrowser.SortByType);
1857     }
1858
1859     @Override
1860     public boolean isSelected(AnActionEvent event) {
1861       return isSortByType(getCurrentViewId());
1862     }
1863
1864     @Override
1865     public void setSelected(AnActionEvent event, boolean flag) {
1866       setSortByType(getCurrentViewId(), flag);
1867     }
1868
1869     @Override
1870     public void update(final AnActionEvent e) {
1871       super.update(e);
1872       final Presentation presentation = e.getPresentation();
1873       presentation.setVisible(getCurrentProjectViewPane() != null);
1874     }
1875   }
1876
1877   private class FoldersAlwaysOnTopAction extends ToggleAction implements DumbAware {
1878     private FoldersAlwaysOnTopAction() {
1879       super("Folders Always on Top");
1880     }
1881
1882     @Override
1883     public boolean isSelected(AnActionEvent event) {
1884       return isFoldersAlwaysOnTop();
1885     }
1886
1887     @Override
1888     public void setSelected(AnActionEvent event, boolean flag) {
1889       setFoldersAlwaysOnTop(flag);
1890     }
1891
1892     @Override
1893     public void update(final AnActionEvent e) {
1894       super.update(e);
1895       final Presentation presentation = e.getPresentation();
1896       presentation.setEnabledAndVisible(getCurrentProjectViewPane() != null);
1897     }
1898   }
1899
1900   private class ScrollFromSourceAction extends AnAction implements DumbAware {
1901     private ScrollFromSourceAction() {
1902       super("Scroll from Source", "Select the file open in the active editor", AllIcons.General.Locate);
1903       getTemplatePresentation().setHoveredIcon(AllIcons.General.LocateHover);
1904     }
1905
1906     @Override
1907     public void actionPerformed(AnActionEvent e) {
1908       myAutoScrollFromSourceHandler.scrollFromSource();
1909     }
1910   }
1911
1912   @NotNull
1913   @Override
1914   public Collection<String> getPaneIds() {
1915     return myId2Pane.keySet();
1916   }
1917
1918   @NotNull
1919   @Override
1920   public Collection<SelectInTarget> getSelectInTargets() {
1921     ensurePanesLoaded();
1922     return mySelectInTargets.values();
1923   }
1924
1925   @NotNull
1926   @Override
1927   public ActionCallback getReady(@NotNull Object requestor) {
1928     AbstractProjectViewPane pane = myId2Pane.get(myCurrentViewSubId);
1929     if (pane == null) {
1930       pane = myId2Pane.get(myCurrentViewId);
1931     }
1932     return pane != null ? pane.getReady(requestor) : ActionCallback.DONE;
1933   }
1934 }