CPP-13710 - commit only current document if there are no processors that can access...
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / ProjectStructureConfigurable.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.roots.ui.configuration;
3
4 import com.intellij.compiler.server.BuildManager;
5 import com.intellij.facet.Facet;
6 import com.intellij.ide.JavaUiBundle;
7 import com.intellij.ide.util.PropertiesComponent;
8 import com.intellij.openapi.actionSystem.*;
9 import com.intellij.openapi.application.AccessToken;
10 import com.intellij.openapi.components.ServiceManager;
11 import com.intellij.openapi.module.Module;
12 import com.intellij.openapi.module.ModuleManager;
13 import com.intellij.openapi.options.Configurable;
14 import com.intellij.openapi.options.ConfigurationException;
15 import com.intellij.openapi.options.SearchableConfigurable;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.project.ProjectManager;
18 import com.intellij.openapi.projectRoots.Sdk;
19 import com.intellij.openapi.roots.LibraryOrderEntry;
20 import com.intellij.openapi.roots.OrderEntry;
21 import com.intellij.openapi.roots.libraries.Library;
22 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
23 import com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactsStructureConfigurable;
24 import com.intellij.openapi.roots.ui.configuration.projectRoot.*;
25 import com.intellij.openapi.ui.DetailsComponent;
26 import com.intellij.openapi.ui.MasterDetailsComponent;
27 import com.intellij.openapi.util.ActionCallback;
28 import com.intellij.openapi.util.Ref;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
31 import com.intellij.packaging.artifacts.Artifact;
32 import com.intellij.ui.JBSplitter;
33 import com.intellij.ui.OnePixelSplitter;
34 import com.intellij.ui.components.panels.Wrapper;
35 import com.intellij.ui.navigation.BackAction;
36 import com.intellij.ui.navigation.ForwardAction;
37 import com.intellij.ui.navigation.History;
38 import com.intellij.ui.navigation.Place;
39 import com.intellij.util.io.storage.HeavyProcessLatch;
40 import com.intellij.util.ui.JBUI;
41 import com.intellij.util.ui.UIUtil;
42 import org.jetbrains.annotations.Nls;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import javax.swing.*;
48 import java.awt.*;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.List;
52
53 import static com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurableFilter.ConfigurableId;
54
55 public class ProjectStructureConfigurable implements SearchableConfigurable, Place.Navigator,
56                                                                               Configurable.NoMargin, Configurable.NoScroll {
57   public static final DataKey<ProjectStructureConfigurable> KEY = DataKey.create("ProjectStructureConfiguration");
58
59   protected final UIState myUiState = new UIState();
60   private JBSplitter mySplitter;
61   private JComponent myToolbarComponent;
62   @NonNls public static final String CATEGORY = "category";
63   private JComponent myToFocus;
64
65   public static class UIState {
66     public float proportion;
67     public float sideProportion;
68
69     public String lastEditedConfigurable;
70   }
71
72   private final Project myProject;
73   private final FacetStructureConfigurable myFacetStructureConfigurable;
74   private final ArtifactsStructureConfigurable myArtifactsStructureConfigurable;
75
76   private History myHistory = new History(this);
77   private SidePanel mySidePanel;
78
79   private JPanel myComponent;
80   private final Wrapper myDetails = new Wrapper();
81
82   private Configurable mySelectedConfigurable;
83
84   private final ProjectSdksModel myProjectJdksModel = new ProjectSdksModel();
85
86   private ProjectConfigurable myProjectConfig;
87   private final ProjectLibrariesConfigurable myProjectLibrariesConfig;
88   private final GlobalLibrariesConfigurable myGlobalLibrariesConfig;
89   private ModuleStructureConfigurable myModulesConfig;
90
91   private boolean myUiInitialized;
92
93   private final List<Configurable> myName2Config = new ArrayList<>();
94   private final StructureConfigurableContext myContext;
95   private final ModulesConfigurator myModuleConfigurator;
96   private JdkListConfigurable myJdkListConfig;
97
98   private final JLabel myEmptySelection = new JLabel(
99     JavaUiBundle.message("project.structure.empty.text"),
100     SwingConstants.CENTER);
101
102   private final ObsoleteLibraryFilesRemover myObsoleteLibraryFilesRemover;
103
104   public ProjectStructureConfigurable(@NotNull Project project) {
105     myProject = project;
106     myFacetStructureConfigurable = FacetStructureConfigurable.getInstance(project);
107     myArtifactsStructureConfigurable = project.getService(ArtifactsStructureConfigurable.class);
108
109     myModuleConfigurator = new ModulesConfigurator(myProject);
110     myContext = new StructureConfigurableContext(myProject, myModuleConfigurator);
111     myModuleConfigurator.setContext(myContext);
112
113     myProjectLibrariesConfig = ProjectLibrariesConfigurable.getInstance(project);
114     myGlobalLibrariesConfig = GlobalLibrariesConfigurable.getInstance(project);
115     myModulesConfig = ModuleStructureConfigurable.getInstance(project);
116
117     myProjectLibrariesConfig.init(myContext);
118     myGlobalLibrariesConfig.init(myContext);
119     myModulesConfig.init(myContext);
120     myFacetStructureConfigurable.init(myContext);
121     if (!project.isDefault()) {
122       myArtifactsStructureConfigurable.init(myContext, myModulesConfig, myProjectLibrariesConfig, myGlobalLibrariesConfig);
123     }
124
125     final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject);
126     myUiState.lastEditedConfigurable = propertiesComponent.getValue("project.structure.last.edited");
127     final String proportion = propertiesComponent.getValue("project.structure.proportion");
128     myUiState.proportion = proportion != null ? Float.parseFloat(proportion) : 0;
129     final String sideProportion = propertiesComponent.getValue("project.structure.side.proportion");
130     myUiState.sideProportion = sideProportion != null ? Float.parseFloat(sideProportion) : 0;
131     myObsoleteLibraryFilesRemover = new ObsoleteLibraryFilesRemover(project);
132   }
133
134   @Override
135   @NotNull
136   @NonNls
137   public String getId() {
138     return "project.structure";
139   }
140
141   @Override
142   @Nls
143   public String getDisplayName() {
144     return JavaUiBundle.message("project.settings.display.name");
145   }
146
147   @Override
148   @Nullable
149   @NonNls
150   public String getHelpTopic() {
151     return mySelectedConfigurable != null ? mySelectedConfigurable.getHelpTopic() : "";
152   }
153
154   @Override
155   public JComponent createComponent() {
156     myComponent = new MyPanel();
157
158     mySplitter = new OnePixelSplitter(false, .15f);
159     mySplitter.setSplitterProportionKey("ProjectStructure.TopLevelElements");
160     mySplitter.setHonorComponentsMinimumSize(true);
161
162     initSidePanel();
163
164     final JPanel left = new JPanel(new BorderLayout()) {
165       @Override
166       public Dimension getMinimumSize() {
167         final Dimension original = super.getMinimumSize();
168         return new Dimension(Math.max(original.width, 100), original.height);
169       }
170     };
171
172     final DefaultActionGroup toolbarGroup = new DefaultActionGroup();
173     toolbarGroup.add(new BackAction(myComponent, myContext));
174     toolbarGroup.add(new ForwardAction(myComponent, myContext));
175     final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar("ProjectStructure", toolbarGroup, true);
176     toolbar.setTargetComponent(myComponent);
177     myToolbarComponent = toolbar.getComponent();
178     left.setBackground(UIUtil.SIDE_PANEL_BACKGROUND);
179     myToolbarComponent.setBackground(UIUtil.SIDE_PANEL_BACKGROUND);
180     left.add(myToolbarComponent, BorderLayout.NORTH);
181     left.add(mySidePanel, BorderLayout.CENTER);
182
183     mySplitter.setFirstComponent(left);
184     mySplitter.setSecondComponent(myDetails);
185
186     myComponent.add(mySplitter, BorderLayout.CENTER);
187
188     myUiInitialized = true;
189
190     return myComponent;
191   }
192
193   private void initSidePanel() {
194     boolean isDefaultProject = myProject == ProjectManager.getInstance().getDefaultProject();
195
196     mySidePanel = new SidePanel(this);
197     mySidePanel.addSeparator(JavaUiBundle.message("project.settings.title"));
198     addProjectConfig();
199     if (!isDefaultProject) {
200       addModulesConfig();
201     }
202     addProjectLibrariesConfig();
203
204     if (!isDefaultProject) {
205       addFacetsConfig();
206       addArtifactsConfig();
207     }
208
209     mySidePanel.addSeparator(JavaUiBundle.message("project.structure.platform.title"));
210     addJdkListConfig();
211     addGlobalLibrariesConfig();
212
213     mySidePanel.addSeparator("--");
214     addErrorPane();
215   }
216
217   private void addArtifactsConfig() {
218     addConfigurable(myArtifactsStructureConfigurable, ConfigurableId.ARTIFACTS);
219   }
220
221   private void addConfigurable(final Configurable configurable, final ConfigurableId configurableId) {
222     addConfigurable(configurable, isAvailable(configurableId));
223   }
224
225   private boolean isAvailable(ConfigurableId id) {
226     for (ProjectStructureConfigurableFilter filter : ProjectStructureConfigurableFilter.EP_NAME.getExtensions()) {
227       if (!filter.isAvailable(id, myProject)) {
228         return false;
229       }
230     }
231     return true;
232   }
233
234   public ArtifactsStructureConfigurable getArtifactsStructureConfigurable() {
235     return myArtifactsStructureConfigurable;
236   }
237
238   private void addFacetsConfig() {
239     if (myFacetStructureConfigurable.isVisible()) {
240       addConfigurable(myFacetStructureConfigurable, ConfigurableId.FACETS);
241     }
242   }
243
244   private void addJdkListConfig() {
245     if (myJdkListConfig == null) {
246       myJdkListConfig = JdkListConfigurable.getInstance(myProject);
247       myJdkListConfig.init(myContext);
248     }
249     addConfigurable(myJdkListConfig, ConfigurableId.JDK_LIST);
250   }
251
252   private void addProjectConfig() {
253     myProjectConfig = new ProjectConfigurable(myProject, myContext, myModuleConfigurator, myProjectJdksModel);
254     addConfigurable(myProjectConfig, ConfigurableId.PROJECT);
255   }
256
257   private void addProjectLibrariesConfig() {
258     addConfigurable(myProjectLibrariesConfig, ConfigurableId.PROJECT_LIBRARIES);
259   }
260
261   private void addErrorPane() {
262     addConfigurable(new ErrorPaneConfigurable(myProject, myContext, () -> mySidePanel.getList().repaint()), true);
263   }
264
265   private void addGlobalLibrariesConfig() {
266     addConfigurable(myGlobalLibrariesConfig, ConfigurableId.GLOBAL_LIBRARIES);
267   }
268
269   private void addModulesConfig() {
270     myModulesConfig = ModuleStructureConfigurable.getInstance(myProject);
271     addConfigurable(myModulesConfig, ConfigurableId.MODULES);
272   }
273
274   @Override
275   public boolean isModified() {
276     for (Configurable each : myName2Config) {
277       if (each.isModified()) return true;
278     }
279
280     return false;
281   }
282
283   @Override
284   public void apply() throws ConfigurationException {
285     for (Configurable each : myName2Config) {
286       if (each instanceof BaseStructureConfigurable && each.isModified()) {
287         ((BaseStructureConfigurable)each).checkCanApply();
288       }
289     }
290     final Ref<ConfigurationException> exceptionRef = Ref.create();
291     try {
292       for (Configurable each : myName2Config) {
293         if (each.isModified()) {
294           each.apply();
295         }
296       }
297     }
298     catch (ConfigurationException e) {
299       exceptionRef.set(e);
300     }
301
302     if (!exceptionRef.isNull()) {
303       throw exceptionRef.get();
304     }
305
306     myObsoleteLibraryFilesRemover.deleteFiles();
307     myContext.getDaemonAnalyzer().clearCaches();
308     BuildManager.getInstance().scheduleAutoMake();
309   }
310
311   @Override
312   public void reset() {
313     // need this to ensure VFS operations will not block because of storage flushing
314     // and other maintenance IO tasks run in background
315     AccessToken token = HeavyProcessLatch.INSTANCE.processStarted(JavaUiBundle.message("project.structure.configurable.reset.text"));
316
317     try {
318
319       myContext.reset();
320
321       myProjectJdksModel.reset(myProject);
322
323       Configurable toSelect = null;
324       for (Configurable each : myName2Config) {
325         if (myUiState.lastEditedConfigurable != null && myUiState.lastEditedConfigurable.equals(each.getDisplayName())) {
326           toSelect = each;
327         }
328         if (each instanceof MasterDetailsComponent) {
329           ((MasterDetailsComponent)each).setHistory(myHistory);
330         }
331         each.reset();
332       }
333
334       myHistory.clear();
335
336       if (toSelect == null && myName2Config.size() > 0) {
337         toSelect = myName2Config.iterator().next();
338       }
339
340       removeSelected();
341
342       navigateTo(toSelect != null ? createPlaceFor(toSelect) : null, false);
343
344       if (myUiState.proportion > 0) {
345         mySplitter.setProportion(myUiState.proportion);
346       }
347     }
348     finally {
349       token.finish();
350     }
351   }
352
353   @Override
354   public void disposeUIResources() {
355     if (!myUiInitialized) return;
356     final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(myProject);
357     propertiesComponent.setValue("project.structure.last.edited", myUiState.lastEditedConfigurable);
358     propertiesComponent.setValue("project.structure.proportion", String.valueOf(myUiState.proportion));
359     propertiesComponent.setValue("project.structure.side.proportion", String.valueOf(myUiState.sideProportion));
360
361     myUiState.proportion = mySplitter.getProportion();
362     saveSideProportion();
363     myContext.getDaemonAnalyzer().stop();
364     for (Configurable each : myName2Config) {
365       each.disposeUIResources();
366     }
367     myContext.clear();
368     myName2Config.clear();
369
370     myModuleConfigurator.getFacetsConfigurator().clearMaps();
371
372     myUiInitialized = false;
373   }
374
375   public boolean isUiInitialized() {
376     return myUiInitialized;
377   }
378
379   public History getHistory() {
380     return myHistory;
381   }
382
383   @Override
384   public void setHistory(final History history) {
385     myHistory = history;
386   }
387
388   @Override
389   public void queryPlace(@NotNull final Place place) {
390     place.putPath(CATEGORY, mySelectedConfigurable);
391     Place.queryFurther(mySelectedConfigurable, place);
392   }
393
394   public ActionCallback selectProjectGeneralSettings(final boolean requestFocus) {
395     return navigateTo(createProjectConfigurablePlace(), requestFocus);
396   }
397
398   public Place createProjectConfigurablePlace() {
399     return createPlaceFor(myProjectConfig);
400   }
401
402   public ActionCallback select(@Nullable final String moduleToSelect, @Nullable String editorNameToSelect, final boolean requestFocus) {
403     Place place = createModulesPlace();
404     if (moduleToSelect != null) {
405       final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleToSelect);
406       assert module != null;
407       place = place.putPath(MasterDetailsComponent.TREE_OBJECT, module).putPath(ModuleEditor.SELECTED_EDITOR_NAME, editorNameToSelect);
408     }
409     return navigateTo(place, requestFocus);
410   }
411
412   public Place createModulesPlace() {
413     return createPlaceFor(myModulesConfig);
414   }
415
416   public Place createModulePlace(@NotNull Module module) {
417     return createModulesPlace().putPath(MasterDetailsComponent.TREE_OBJECT, module);
418   }
419
420   public ActionCallback select(@Nullable final Facet facetToSelect, final boolean requestFocus) {
421     Place place = createModulesPlace();
422     if (facetToSelect != null) {
423       place = place.putPath(MasterDetailsComponent.TREE_OBJECT, facetToSelect);
424     }
425     return navigateTo(place, requestFocus);
426   }
427
428   public ActionCallback select(@NotNull Sdk sdk, final boolean requestFocus) {
429     Place place = createPlaceFor(myJdkListConfig);
430     place.putPath(MasterDetailsComponent.TREE_NAME, sdk.getName());
431     return navigateTo(place, requestFocus);
432   }
433
434   public ActionCallback selectGlobalLibraries(final boolean requestFocus) {
435     Place place = createPlaceFor(myGlobalLibrariesConfig);
436     return navigateTo(place, requestFocus);
437   }
438
439   public ActionCallback selectProjectOrGlobalLibrary(@NotNull Library library, boolean requestFocus) {
440     Place place = createProjectOrGlobalLibraryPlace(library);
441     return navigateTo(place, requestFocus);
442   }
443
444   public Place createProjectOrGlobalLibraryPlace(Library library) {
445     Place place = createPlaceFor(getConfigurableFor(library));
446     place.putPath(MasterDetailsComponent.TREE_NAME, library.getName());
447     return place;
448   }
449
450   public ActionCallback select(@Nullable Artifact artifact, boolean requestFocus) {
451     Place place = createArtifactPlace(artifact);
452     return navigateTo(place, requestFocus);
453   }
454
455   public Place createArtifactPlace(Artifact artifact) {
456     Place place = createPlaceFor(myArtifactsStructureConfigurable);
457     if (artifact != null) {
458       place.putPath(MasterDetailsComponent.TREE_NAME, artifact.getName());
459     }
460     return place;
461   }
462
463   public ActionCallback select(@NotNull LibraryOrderEntry libraryOrderEntry, final boolean requestFocus) {
464     final Library lib = libraryOrderEntry.getLibrary();
465     if (lib == null || lib.getTable() == null) {
466       return selectOrderEntry(libraryOrderEntry.getOwnerModule(), libraryOrderEntry);
467     }
468     Place place = createPlaceFor(getConfigurableFor(lib));
469     place.putPath(MasterDetailsComponent.TREE_NAME, libraryOrderEntry.getLibraryName());
470     return navigateTo(place, requestFocus);
471   }
472
473   public ActionCallback selectOrderEntry(@NotNull final Module module, @Nullable final OrderEntry orderEntry) {
474     return ModuleStructureConfigurable.getInstance(myProject).selectOrderEntry(module, orderEntry);
475   }
476
477   @Override
478   public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) {
479     final Configurable toSelect = (Configurable)place.getPath(CATEGORY);
480
481     JComponent detailsContent = myDetails.getTargetComponent();
482
483     if (mySelectedConfigurable != toSelect) {
484       if (mySelectedConfigurable instanceof BaseStructureConfigurable) {
485         ((BaseStructureConfigurable)mySelectedConfigurable).onStructureUnselected();
486       }
487       saveSideProportion();
488       removeSelected();
489
490       if (toSelect != null) {
491         detailsContent = toSelect.createComponent();
492         myDetails.setContent(detailsContent);
493       }
494
495       mySelectedConfigurable = toSelect;
496       if (mySelectedConfigurable != null) {
497         myUiState.lastEditedConfigurable = mySelectedConfigurable.getDisplayName();
498       }
499
500       if (toSelect instanceof MasterDetailsComponent) {
501         final MasterDetailsComponent masterDetails = (MasterDetailsComponent)toSelect;
502         if (myUiState.sideProportion > 0) {
503           masterDetails.getSplitter().setProportion(myUiState.sideProportion);
504         }
505         masterDetails.setHistory(myHistory);
506       }
507
508       if (toSelect instanceof DetailsComponent.Facade) {
509         ((DetailsComponent.Facade)toSelect).getDetailsComponent().setBannerMinHeight(myToolbarComponent.getPreferredSize().height);
510       }
511
512       if (toSelect instanceof BaseStructureConfigurable) {
513         ((BaseStructureConfigurable)toSelect).onStructureSelected();
514       }
515     }
516
517
518
519     if (detailsContent != null) {
520       JComponent toFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(detailsContent);
521       if (toFocus == null) {
522         toFocus = detailsContent;
523       }
524       if (requestFocus) {
525         myToFocus = toFocus;
526         UIUtil.requestFocus(toFocus);
527       }
528     }
529
530     final ActionCallback result = new ActionCallback();
531     Place.goFurther(toSelect, place, requestFocus).notifyWhenDone(result);
532
533     myDetails.revalidate();
534     myDetails.repaint();
535
536     if (toSelect != null) {
537       mySidePanel.select(createPlaceFor(toSelect));
538     }
539
540     if (!myHistory.isNavigatingNow() && mySelectedConfigurable != null) {
541       myHistory.pushQueryPlace();
542     }
543
544     return result;
545   }
546
547   private void saveSideProportion() {
548     if (mySelectedConfigurable instanceof MasterDetailsComponent) {
549       myUiState.sideProportion = ((MasterDetailsComponent)mySelectedConfigurable).getSplitter().getProportion();
550     }
551   }
552
553   private void removeSelected() {
554     myDetails.removeAll();
555     mySelectedConfigurable = null;
556     myUiState.lastEditedConfigurable = null;
557
558     myDetails.add(myEmptySelection, BorderLayout.CENTER);
559   }
560
561   public static ProjectStructureConfigurable getInstance(@NotNull final Project project) {
562     return ServiceManager.getService(project, ProjectStructureConfigurable.class);
563   }
564
565   @NotNull
566   public ProjectSdksModel getProjectJdksModel() {
567     return myProjectJdksModel;
568   }
569
570   public JdkListConfigurable getJdkConfig() {
571     return myJdkListConfig;
572   }
573
574   public ModuleStructureConfigurable getModulesConfig() {
575     return myModulesConfig;
576   }
577
578   public ProjectConfigurable getProjectConfig() {
579     return myProjectConfig;
580   }
581
582   public void registerObsoleteLibraryRoots(@NotNull Collection<? extends VirtualFile> roots) {
583     myObsoleteLibraryFilesRemover.registerObsoleteLibraryRoots(roots);
584   }
585
586   private void addConfigurable(Configurable configurable, boolean addToSidePanel) {
587     myName2Config.add(configurable);
588
589     if (addToSidePanel) {
590       mySidePanel.addPlace(createPlaceFor(configurable), new Presentation(configurable.getDisplayName()));
591     }
592   }
593
594   private static Place createPlaceFor(final Configurable configurable) {
595     return new Place().putPath(CATEGORY, configurable);
596   }
597
598
599   public StructureConfigurableContext getContext() {
600     return myContext;
601   }
602
603   private class MyPanel extends JPanel implements DataProvider {
604     MyPanel() {
605       super(new BorderLayout());
606     }
607
608     @Override
609     @Nullable
610     public Object getData(@NotNull @NonNls final String dataId) {
611       if (KEY.is(dataId)) {
612         return ProjectStructureConfigurable.this;
613       } else if (History.KEY.is(dataId)) {
614         return getHistory();
615       } else {
616         return null;
617       }
618     }
619
620     @Override
621     public Dimension getPreferredSize() {
622       return JBUI.size(1024, 768);
623     }
624   }
625
626   public BaseLibrariesConfigurable getConfigurableFor(final Library library) {
627     if (LibraryTablesRegistrar.PROJECT_LEVEL.equals(library.getTable().getTableLevel())) {
628       return myProjectLibrariesConfig;
629     } else {
630       return myGlobalLibrariesConfig;
631     }
632   }
633
634   @Override
635   public JComponent getPreferredFocusedComponent() {
636     return myToFocus;
637   }
638 }