2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.projectRoots.ui;
18 import com.google.common.collect.Lists;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.SdkEditorAdditionalOptionsProvider;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.options.Configurable;
24 import com.intellij.openapi.options.ConfigurationException;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.project.ProjectBundle;
27 import com.intellij.openapi.projectRoots.*;
28 import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
29 import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
30 import com.intellij.openapi.roots.OrderRootType;
31 import com.intellij.openapi.roots.ui.OrderRootTypeUIFactory;
32 import com.intellij.openapi.ui.Messages;
33 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
34 import com.intellij.openapi.util.ActionCallback;
35 import com.intellij.openapi.util.Comparing;
36 import com.intellij.openapi.util.Disposer;
37 import com.intellij.openapi.util.io.FileUtil;
38 import com.intellij.openapi.vfs.VirtualFile;
39 import com.intellij.ui.TabbedPaneWrapper;
40 import com.intellij.ui.navigation.History;
41 import com.intellij.ui.navigation.Place;
42 import com.intellij.util.containers.ContainerUtil;
43 import com.intellij.util.ui.JBUI;
44 import com.intellij.util.ui.UIUtil;
45 import org.jetbrains.annotations.NonNls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
50 import javax.swing.event.ChangeEvent;
51 import javax.swing.event.ChangeListener;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
57 import java.util.List;
63 public class SdkEditor implements Configurable, Place.Navigator {
64 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.ui.SdkEditor");
65 @NonNls private static final String SDK_TAB = "sdkTab";
68 private final Map<OrderRootType, SdkPathEditor> myPathEditors = new HashMap<>();
70 private TextFieldWithBrowseButton myHomeComponent;
71 private final Map<SdkType, List<AdditionalDataConfigurable>> myAdditionalDataConfigurables = new HashMap<>();
72 private final Map<AdditionalDataConfigurable, JComponent> myAdditionalDataComponents = new HashMap<>();
73 private JPanel myAdditionalDataPanel;
74 private final SdkModificator myEditedSdkModificator = new EditedSdkModificator();
77 private JPanel myMainPanel;
78 private TabbedPaneWrapper myTabbedPane;
79 private Project myProject;
80 private final SdkModel mySdkModel;
81 private JLabel myHomeFieldLabel;
82 private String myVersionString;
84 private String myInitialName;
85 private String myInitialPath;
86 private final History myHistory;
88 private final Disposable myDisposable = Disposer.newDisposable();
90 public SdkEditor(Project project, SdkModel sdkModel, History history, final ProjectJdkImpl sdk) {
92 mySdkModel = sdkModel;
99 private void initSdk(Sdk sdk) {
102 myInitialName = mySdk.getName();
103 myInitialPath = mySdk.getHomePath();
109 for (final AdditionalDataConfigurable additionalDataConfigurable : getAdditionalDataConfigurable()) {
110 additionalDataConfigurable.setSdk(sdk);
112 if (myMainPanel != null) {
118 public String getDisplayName() {
119 return ProjectBundle.message("sdk.configure.editor.title");
123 public String getHelpTopic() {
128 public JComponent createComponent() {
132 private void createMainPanel() {
133 myMainPanel = new JPanel(new GridBagLayout());
135 myTabbedPane = new TabbedPaneWrapper(myDisposable);
136 for (OrderRootType type : OrderRootType.getAllTypes()) {
137 if (mySdk == null || showTabForType(type)) {
138 final SdkPathEditor pathEditor = OrderRootTypeUIFactory.FACTORY.getByKey(type).createPathEditor(mySdk);
139 if (pathEditor != null) {
140 pathEditor.setAddBaseDir(mySdk.getHomeDirectory());
141 myTabbedPane.addTab(pathEditor.getDisplayName(), pathEditor.createComponent());
142 myPathEditors.put(type, pathEditor);
147 myTabbedPane.addChangeListener(new ChangeListener() {
149 public void stateChanged(final ChangeEvent e) {
150 myHistory.pushQueryPlace();
154 myHomeComponent = createHomeComponent();
155 myHomeComponent.getTextField().setEditable(false);
157 myHomeFieldLabel = new JLabel(getHomeFieldLabelValue());
158 final int leftInset = 10;
159 final int rightInset = 10;
160 myMainPanel.add(myHomeFieldLabel,
161 new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE,
162 JBUI.insets(2, leftInset, 2, 2), 0, 0));
163 myMainPanel.add(myHomeComponent, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
164 GridBagConstraints.HORIZONTAL,
165 JBUI.insets(2, 2, 2, rightInset), 0, 0));
167 myAdditionalDataPanel = new JPanel(new BorderLayout());
168 myMainPanel.add(myAdditionalDataPanel, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.CENTER,
169 GridBagConstraints.BOTH,
170 JBUI.insets(2, leftInset, 0, rightInset), 0, 0));
172 myMainPanel.add(myTabbedPane.getComponent(),
173 new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER,
174 GridBagConstraints.BOTH,
175 JBUI.insetsTop(2), 0, 0));
178 protected TextFieldWithBrowseButton createHomeComponent() {
179 return new TextFieldWithBrowseButton(new ActionListener() {
181 public void actionPerformed(ActionEvent e) {
187 protected boolean showTabForType(@NotNull OrderRootType type) {
188 return ((SdkType)mySdk.getSdkType()).isRootTypeApplicable(type);
191 private String getHomeFieldLabelValue() {
193 return ((SdkType)mySdk.getSdkType()).getHomeFieldLabel();
195 return ProjectBundle.message("sdk.configure.general.home.path");
199 public boolean isModified() {
200 boolean isModified = !Comparing.equal(mySdk == null ? null : mySdk.getName(), myInitialName);
202 isModified || !Comparing.equal(FileUtil.toSystemIndependentName(getHomeValue()), FileUtil.toSystemIndependentName(myInitialPath));
203 for (PathEditor pathEditor : myPathEditors.values()) {
204 isModified = isModified || pathEditor.isModified();
206 for (final AdditionalDataConfigurable configurable : getAdditionalDataConfigurable()) {
207 isModified = isModified || configurable.isModified();
213 public void apply() throws ConfigurationException {
214 if (!Comparing.equal(myInitialName, mySdk == null ? "" : mySdk.getName())) {
215 if (mySdk == null || mySdk.getName().isEmpty()) {
216 throw new ConfigurationException(ProjectBundle.message("sdk.list.name.required.error"));
220 myInitialName = mySdk.getName();
221 myInitialPath = mySdk.getHomePath();
222 final SdkModificator sdkModificator = mySdk.getSdkModificator();
223 sdkModificator.setHomePath(getHomeValue().replace(File.separatorChar, '/'));
224 for (SdkPathEditor pathEditor : myPathEditors.values()) {
225 pathEditor.apply(sdkModificator);
227 ApplicationManager.getApplication().runWriteAction(() -> sdkModificator.commitChanges());
228 for (final AdditionalDataConfigurable configurable : getAdditionalDataConfigurable()) {
229 if (configurable != null) {
230 configurable.apply();
237 public void reset() {
239 setHomePathValue("");
240 for (SdkPathEditor pathEditor : myPathEditors.values()) {
241 pathEditor.reset(null);
245 final SdkModificator sdkModificator = mySdk.getSdkModificator();
246 for (OrderRootType type : myPathEditors.keySet()) {
247 myPathEditors.get(type).reset(sdkModificator);
249 sdkModificator.commitChanges();
250 setHomePathValue(mySdk.getHomePath().replace('/', File.separatorChar));
252 myVersionString = null;
253 myHomeFieldLabel.setText(getHomeFieldLabelValue());
254 updateAdditionalDataComponent();
256 for (final AdditionalDataConfigurable configurable : getAdditionalDataConfigurable()) {
257 configurable.reset();
260 myHomeComponent.setEnabled(mySdk != null);
262 for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
263 myTabbedPane.setEnabledAt(i, mySdk != null);
268 public void disposeUIResources() {
269 for (final SdkType sdkType : myAdditionalDataConfigurables.keySet()) {
270 for (final AdditionalDataConfigurable configurable : myAdditionalDataConfigurables.get(sdkType)) {
271 configurable.disposeUIResources();
274 myAdditionalDataConfigurables.clear();
275 myAdditionalDataComponents.clear();
277 Disposer.dispose(myDisposable);
280 private String getHomeValue() {
281 return myHomeComponent.getText().trim();
284 private void clearAllPaths() {
285 for (PathEditor editor : myPathEditors.values()) {
290 private void setHomePathValue(String absolutePath) {
291 myHomeComponent.setText(absolutePath);
293 if (absolutePath != null && !absolutePath.isEmpty()) {
294 final File homeDir = new File(absolutePath);
295 boolean homeMustBeDirectory = mySdk == null || ((SdkType)mySdk.getSdkType()).getHomeChooserDescriptor().isChooseFolders();
296 fg = homeDir.exists() && homeDir.isDirectory() == homeMustBeDirectory
297 ? UIUtil.getFieldForegroundColor()
298 : PathEditor.INVALID_COLOR;
301 fg = UIUtil.getFieldForegroundColor();
303 myHomeComponent.getTextField().setForeground(fg);
306 private void doSelectHomePath() {
307 final SdkType sdkType = (SdkType)mySdk.getSdkType();
308 SdkConfigurationUtil.selectSdkHome(sdkType, path -> doSetHomePath(path, sdkType));
311 private void doSetHomePath(final String homePath, final SdkType sdkType) {
312 if (homePath == null) {
315 setHomePathValue(homePath.replace('/', File.separatorChar));
317 final String newSdkName = suggestSdkName(homePath);
318 ((ProjectJdkImpl)mySdk).setName(newSdkName);
321 final Sdk dummySdk = (Sdk)mySdk.clone();
322 SdkModificator sdkModificator = dummySdk.getSdkModificator();
323 sdkModificator.setHomePath(homePath);
324 sdkModificator.removeAllRoots();
325 sdkModificator.commitChanges();
327 sdkType.setupSdkPaths(dummySdk, mySdkModel);
330 myVersionString = dummySdk.getVersionString();
331 if (myVersionString == null) {
332 Messages.showMessageDialog(ProjectBundle.message("sdk.java.corrupt.error", homePath),
333 ProjectBundle.message("sdk.java.corrupt.title"), Messages.getErrorIcon());
335 sdkModificator = dummySdk.getSdkModificator();
336 for (OrderRootType type : myPathEditors.keySet()) {
337 SdkPathEditor pathEditor = myPathEditors.get(type);
338 pathEditor.setAddBaseDir(dummySdk.getHomeDirectory());
339 pathEditor.addPaths(sdkModificator.getRoots(type));
341 mySdkModel.getMulticaster().sdkHomeSelected(dummySdk, homePath);
343 catch (CloneNotSupportedException e) {
344 LOG.error(e); // should not happen in normal program
348 private String suggestSdkName(final String homePath) {
349 final String currentName = mySdk.getName();
350 final String suggestedName = ((SdkType)mySdk.getSdkType()).suggestSdkName(currentName, homePath);
351 if (Comparing.equal(currentName, suggestedName)) return currentName;
352 String newSdkName = suggestedName;
353 final Set<String> allNames = new HashSet<>();
354 Sdk[] sdks = mySdkModel.getSdks();
355 for (Sdk sdk : sdks) {
356 allNames.add(sdk.getName());
359 while (allNames.contains(newSdkName)) {
360 newSdkName = suggestedName + " (" + ++i + ")";
365 private void updateAdditionalDataComponent() {
366 myAdditionalDataPanel.removeAll();
367 for (AdditionalDataConfigurable configurable : getAdditionalDataConfigurable()) {
368 JComponent component = myAdditionalDataComponents.get(configurable);
369 if (component == null) {
370 component = configurable.createComponent();
371 myAdditionalDataComponents.put(configurable, component);
373 if (component != null) {
374 if (configurable.getTabName() != null) {
375 myTabbedPane.addTab(configurable.getTabName(), component);
378 myAdditionalDataPanel.add(component, BorderLayout.CENTER);
385 private List<AdditionalDataConfigurable> getAdditionalDataConfigurable() {
387 return ContainerUtil.emptyList();
389 return initAdditionalDataConfigurable(mySdk);
393 private List<AdditionalDataConfigurable> initAdditionalDataConfigurable(Sdk sdk) {
394 final SdkType sdkType = (SdkType)sdk.getSdkType();
395 List<AdditionalDataConfigurable> configurables = myAdditionalDataConfigurables.get(sdkType);
396 if (configurables == null) {
397 configurables = Lists.newArrayList();
398 myAdditionalDataConfigurables.put(sdkType, configurables);
401 AdditionalDataConfigurable sdkConfigurable = sdkType.createAdditionalDataConfigurable(mySdkModel, myEditedSdkModificator);
402 if (sdkConfigurable != null) {
403 configurables.add(sdkConfigurable);
406 for (SdkEditorAdditionalOptionsProvider factory : SdkEditorAdditionalOptionsProvider.getSdkOptionsFactory(mySdk.getSdkType())) {
407 AdditionalDataConfigurable options = factory.createOptions(myProject, mySdk);
408 if (options != null) {
409 configurables.add(options);
414 return configurables;
417 private class EditedSdkModificator implements SdkModificator {
419 public String getName() {
420 return mySdk.getName();
424 public void setName(String name) {
425 ((ProjectJdkImpl)mySdk).setName(name);
429 public String getHomePath() {
430 return getHomeValue();
434 public void setHomePath(String path) {
435 doSetHomePath(path, (SdkType)mySdk.getSdkType());
439 public String getVersionString() {
440 return myVersionString != null ? myVersionString : mySdk.getVersionString();
444 public void setVersionString(String versionString) {
445 throw new UnsupportedOperationException(); // not supported for this editor
449 public SdkAdditionalData getSdkAdditionalData() {
450 return mySdk.getSdkAdditionalData();
454 public void setSdkAdditionalData(SdkAdditionalData data) {
455 throw new UnsupportedOperationException(); // not supported for this editor
459 public VirtualFile[] getRoots(OrderRootType rootType) {
460 final PathEditor editor = myPathEditors.get(rootType);
461 if (editor == null) {
462 throw new IllegalStateException("no editor for root type " + rootType);
464 return editor.getRoots();
468 public void addRoot(VirtualFile root, OrderRootType rootType) {
469 myPathEditors.get(rootType).addPaths(root);
473 public void removeRoot(VirtualFile root, OrderRootType rootType) {
474 myPathEditors.get(rootType).removePaths(root);
478 public void removeRoots(OrderRootType rootType) {
479 myPathEditors.get(rootType).clearList();
483 public void removeAllRoots() {
484 for (PathEditor editor : myPathEditors.values()) {
490 public void commitChanges() {
494 public boolean isWritable() {
500 public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) {
501 if (place == null) return ActionCallback.DONE;
502 myTabbedPane.setSelectedTitle((String)place.getPath(SDK_TAB));
503 return ActionCallback.DONE;
507 public void queryPlace(@NotNull final Place place) {
508 place.putPath(SDK_TAB, myTabbedPane.getSelectedTitle());
512 public void setHistory(final History history) {