f83b5d918574ad2c2de5156a8108d2563664458c
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / ProjectConfigurable.java
1 // Copyright 2000-2018 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
3 package com.intellij.openapi.roots.ui.configuration;
4
5 import com.intellij.core.JavaPsiBundle;
6 import com.intellij.icons.AllIcons;
7 import com.intellij.ide.JavaUiBundle;
8 import com.intellij.ide.util.BrowseFilesListener;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
11 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
12 import com.intellij.openapi.fileChooser.FileChooserFactory;
13 import com.intellij.openapi.options.ConfigurationException;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.project.ProjectBundle;
16 import com.intellij.openapi.project.ex.ProjectEx;
17 import com.intellij.openapi.projectRoots.JavaSdk;
18 import com.intellij.openapi.projectRoots.JavaSdkVersion;
19 import com.intellij.openapi.projectRoots.Sdk;
20 import com.intellij.openapi.roots.CompilerProjectExtension;
21 import com.intellij.openapi.roots.LanguageLevelProjectExtension;
22 import com.intellij.openapi.roots.ModifiableRootModel;
23 import com.intellij.openapi.roots.impl.LanguageLevelProjectExtensionImpl;
24 import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
25 import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectStructureElementConfigurable;
26 import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
27 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureDaemonAnalyzer;
28 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
29 import com.intellij.openapi.ui.DetailsComponent;
30 import com.intellij.openapi.util.Comparing;
31 import com.intellij.openapi.util.EmptyRunnable;
32 import com.intellij.openapi.util.io.FileUtil;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.openapi.vfs.VfsUtilCore;
35 import com.intellij.pom.java.LanguageLevel;
36 import com.intellij.project.ProjectKt;
37 import com.intellij.ui.DocumentAdapter;
38 import com.intellij.ui.FieldPanel;
39 import com.intellij.ui.InsertPathAction;
40 import com.intellij.ui.components.fields.ExtendableTextField;
41 import com.intellij.util.ui.JBUI;
42 import org.jetbrains.annotations.NonNls;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
45
46 import javax.swing.*;
47 import javax.swing.border.EmptyBorder;
48 import javax.swing.event.DocumentEvent;
49 import java.awt.*;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.io.IOException;
53
54 /**
55  * @author Eugene Zhuravlev
56  */
57 public class ProjectConfigurable extends ProjectStructureElementConfigurable<Project> implements DetailsComponent.Facade {
58
59   private final Project myProject;
60
61   private LanguageLevelCombo myLanguageLevelCombo;
62   private ProjectJdkConfigurable myProjectJdkConfigurable;
63
64   private FieldPanel myProjectCompilerOutput;
65
66   private JTextField myProjectName;
67
68   private JPanel myPanel;
69
70   private final StructureConfigurableContext myContext;
71   private final ModulesConfigurator myModulesConfigurator;
72   private JPanel myWholePanel;
73
74   private boolean myFreeze = false;
75   private DetailsComponent myDetailsComponent;
76   private final GeneralProjectSettingsElement mySettingsElement;
77
78   public ProjectConfigurable(Project project,
79                              final StructureConfigurableContext context,
80                              ModulesConfigurator configurator,
81                              ProjectSdksModel model) {
82     myProject = project;
83     myContext = context;
84     myModulesConfigurator = configurator;
85     mySettingsElement = new GeneralProjectSettingsElement(context);
86     final ProjectStructureDaemonAnalyzer daemonAnalyzer = context.getDaemonAnalyzer();
87     myModulesConfigurator.addAllModuleChangeListener(new ModuleEditor.ChangeListener() {
88       @Override
89       public void moduleStateChanged(ModifiableRootModel moduleRootModel) {
90         daemonAnalyzer.queueUpdate(mySettingsElement);
91       }
92     });
93     init(model);
94   }
95
96   @Override
97   public ProjectStructureElement getProjectStructureElement() {
98     return mySettingsElement;
99   }
100
101   @Override
102   public DetailsComponent getDetailsComponent() {
103     return myDetailsComponent;
104   }
105
106   @Override
107   public JComponent createOptionsPanel() {
108     myDetailsComponent = new DetailsComponent(false, false);
109     myDetailsComponent.setContent(myPanel);
110     myDetailsComponent.setText(getBannerSlogan());
111
112     myProjectJdkConfigurable.createComponent(); //reload changed jdks
113
114     return myDetailsComponent.getComponent();
115   }
116
117   private void init(final ProjectSdksModel model) {
118     myPanel = new JPanel(new GridBagLayout());
119     myPanel.setPreferredSize(JBUI.size(700, 500));
120
121     if (ProjectKt.isDirectoryBased(myProject)) {
122       final JPanel namePanel = new JPanel(new BorderLayout());
123       final JLabel label = new JLabel(JavaUiBundle.message("settings.project.name"), SwingConstants.LEFT);
124       namePanel.add(label, BorderLayout.NORTH);
125
126       myProjectName = new JTextField();
127       label.setLabelFor(myProjectName);
128       myProjectName.setColumns(40);
129
130       final JPanel nameFieldPanel = new JPanel();
131       nameFieldPanel.setLayout(new BoxLayout(nameFieldPanel, BoxLayout.X_AXIS));
132       nameFieldPanel.add(Box.createHorizontalStrut(4));
133       nameFieldPanel.add(myProjectName);
134
135       namePanel.add(nameFieldPanel, BorderLayout.CENTER);
136       final JPanel wrapper = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
137       wrapper.add(namePanel);
138       wrapper.setAlignmentX(0);
139       myPanel.add(wrapper, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0,
140                                                   GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
141                                                   JBUI.insets(4, 0, 10, 0), 0, 0));
142     }
143
144     myProjectJdkConfigurable = new ProjectJdkConfigurable(myProject, model);
145     myPanel.add(myProjectJdkConfigurable.createComponent(), new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0,
146                                                                                    GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL,
147                                                                                    JBUI.insetsTop(4), 0, 0));
148
149     myPanel.add(myWholePanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST,
150                                                      GridBagConstraints.NONE, JBUI.insetsTop(4), 0, 0));
151
152     myPanel.setBorder(new EmptyBorder(0, 10, 0, 10));
153     myProjectCompilerOutput.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
154       @Override
155       protected void textChanged(@NotNull DocumentEvent e) {
156         if (myFreeze) return;
157         myModulesConfigurator.processModuleCompilerOutputChanged(getCompilerOutputUrl());
158       }
159     });
160     myProjectJdkConfigurable.addChangeListener(new ActionListener() {
161       @Override
162       public void actionPerformed(ActionEvent e) {
163         myLanguageLevelCombo.sdkUpdated(myProjectJdkConfigurable.getSelectedProjectJdk(), myProject.isDefault());
164         LanguageLevelProjectExtensionImpl.getInstanceImpl(myProject).setCurrentLevel(myLanguageLevelCombo.getSelectedLevel());
165       }
166     });
167     myLanguageLevelCombo.addActionListener(new ActionListener() {
168       @Override
169       public void actionPerformed(ActionEvent e) {
170         LanguageLevelProjectExtensionImpl.getInstanceImpl(myProject).setCurrentLevel(myLanguageLevelCombo.getSelectedLevel());
171       }
172     });
173     String accessibleName = StringUtil.removeHtmlTags(JavaUiBundle.message("project.language.level.name"));
174     String accessibleDescription = StringUtil.removeHtmlTags(JavaUiBundle.message("project.language.level.description"));
175     myLanguageLevelCombo.getAccessibleContext().setAccessibleName(accessibleName);
176     myLanguageLevelCombo.getAccessibleContext().setAccessibleDescription(accessibleDescription);
177   }
178
179   @Override
180   public void disposeUIResources() {
181     if (myProjectJdkConfigurable != null) {
182       myProjectJdkConfigurable.disposeUIResources();
183     }
184   }
185
186   @Override
187   public void reset() {
188     myFreeze = true;
189     try {
190       myProjectJdkConfigurable.reset();
191       final String compilerOutput = getOriginalCompilerOutputUrl();
192       if (compilerOutput != null) {
193         myProjectCompilerOutput.setText(FileUtil.toSystemDependentName(VfsUtilCore.urlToPath(compilerOutput)));
194       }
195       myLanguageLevelCombo.reset(myProject);
196
197       if (myProjectName != null) {
198         myProjectName.setText(myProject.getName());
199       }
200     }
201     finally {
202       myFreeze = false;
203     }
204
205     myContext.getDaemonAnalyzer().queueUpdate(mySettingsElement);
206   }
207
208
209   @Override
210   public void apply() throws ConfigurationException {
211     final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(myProject);
212     assert compilerProjectExtension != null : myProject;
213
214     if (myProjectName != null && StringUtil.isEmptyOrSpaces(myProjectName.getText())) {
215       throw new ConfigurationException("Please, specify project name!");
216     }
217
218     ApplicationManager.getApplication().runWriteAction(() -> {
219       // set the output path first so that handlers of RootsChanged event sent after JDK is set
220       // would see the updated path
221       String canonicalPath = myProjectCompilerOutput.getText();
222       if (canonicalPath != null && canonicalPath.length() > 0) {
223         try {
224           canonicalPath = FileUtil.resolveShortWindowsName(canonicalPath);
225         }
226         catch (IOException e) {
227           //file doesn't exist yet
228         }
229         canonicalPath = FileUtil.toSystemIndependentName(canonicalPath);
230         compilerProjectExtension.setCompilerOutputUrl(VfsUtilCore.pathToUrl(canonicalPath));
231       }
232       else {
233         compilerProjectExtension.setCompilerOutputPointer(null);
234       }
235
236       LanguageLevelProjectExtension extension = LanguageLevelProjectExtension.getInstance(myProject);
237       LanguageLevel level = myLanguageLevelCombo.getSelectedLevel();
238       if (level != null) {
239         extension.setLanguageLevel(level);
240       }
241       extension.setDefault(myLanguageLevelCombo.isDefault());
242       myProjectJdkConfigurable.apply();
243
244       if (myProjectName != null) {
245         ((ProjectEx)myProject).setProjectName(getProjectName());
246         if (myDetailsComponent != null) myDetailsComponent.setText(getBannerSlogan());
247       }
248     });
249   }
250
251
252   @Override
253   public void setDisplayName(final String name) {
254     //do nothing
255   }
256
257   @Override
258   public Project getEditableObject() {
259     return myProject;
260   }
261
262   @Override
263   public String getBannerSlogan() {
264     return JavaUiBundle.message("project.roots.project.banner.text", myProject.getName());
265   }
266
267   @Override
268   public String getDisplayName() {
269     return ProjectBundle.message("project.roots.project.display.name");
270   }
271
272   @Override
273   public Icon getIcon(boolean open) {
274     return AllIcons.Nodes.Project;
275   }
276
277   @Override
278   @Nullable
279   @NonNls
280   public String getHelpTopic() {
281     return "reference.settingsdialog.project.structure.general";
282   }
283
284
285   @Override
286   public boolean isModified() {
287     LanguageLevelProjectExtension extension = LanguageLevelProjectExtension.getInstance(myProject);
288     if (extension.isDefault() != myLanguageLevelCombo.isDefault() ||
289         !extension.isDefault() && !extension.getLanguageLevel().equals(myLanguageLevelCombo.getSelectedLevel())) {
290       return true;
291     }
292     final String compilerOutput = getOriginalCompilerOutputUrl();
293     if (!Comparing.strEqual(FileUtil.toSystemIndependentName(VfsUtilCore.urlToPath(compilerOutput)),
294                             FileUtil.toSystemIndependentName(myProjectCompilerOutput.getText()))) return true;
295     if (myProjectJdkConfigurable.isModified()) return true;
296     if (!getProjectName().equals(myProject.getName())) return true;
297
298     return false;
299   }
300
301   @NotNull
302   public String getProjectName() {
303     return myProjectName != null ? myProjectName.getText().trim() : myProject.getName();
304   }
305
306   @Nullable
307   private String getOriginalCompilerOutputUrl() {
308     final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(myProject);
309     return extension != null ? extension.getCompilerOutputUrl() : null;
310   }
311
312   private void createUIComponents() {
313     myLanguageLevelCombo = new LanguageLevelCombo(JavaPsiBundle.message("default.language.level.description")) {
314       @Override
315       protected LanguageLevel getDefaultLevel() {
316         Sdk sdk = myProjectJdkConfigurable.getSelectedProjectJdk();
317         if (sdk == null) return null;
318         JavaSdkVersion version = JavaSdk.getInstance().getVersion(sdk);
319         return version == null ? null : version.getMaxLanguageLevel();
320       }
321     };
322     final JTextField textField = new ExtendableTextField();
323     String accessibleName = StringUtil.removeHtmlTags(JavaUiBundle.message("project.compiler.output.name"));
324     String accessibleDescription = StringUtil.removeHtmlTags(JavaUiBundle.message("project.compiler.output.description"));
325     textField.getAccessibleContext().setAccessibleName(accessibleName);
326     textField.getAccessibleContext().setAccessibleDescription(accessibleDescription);
327     final FileChooserDescriptor outputPathsChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
328     InsertPathAction.addTo(textField, outputPathsChooserDescriptor);
329     outputPathsChooserDescriptor.setHideIgnored(false);
330     BrowseFilesListener listener = new BrowseFilesListener(textField, accessibleName,
331                                                            JavaUiBundle.message("project.compiler.output.description"),
332                                                            outputPathsChooserDescriptor);
333     myProjectCompilerOutput = new FieldPanel(textField, null, null, listener, EmptyRunnable.getInstance());
334     FileChooserFactory.getInstance().installFileCompletion(myProjectCompilerOutput.getTextField(), outputPathsChooserDescriptor, true, null);
335   }
336
337   public String getCompilerOutputUrl() {
338     return VfsUtilCore.pathToUrl(myProjectCompilerOutput.getText().trim());
339   }
340 }