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