Merge branch 'master' into traff/zip_helpers
[idea/community.git] / python / ide / src / com / jetbrains / python / PyIdeCommonOptionsForm.java
1 /*
2  * Copyright 2000-2015 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 package com.jetbrains.python;
17
18 import com.intellij.application.options.ModulesComboBox;
19 import com.intellij.execution.configuration.EnvironmentVariablesComponent;
20 import com.intellij.execution.util.PathMappingsComponent;
21 import com.intellij.ide.util.PropertiesComponent;
22 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
23 import com.intellij.openapi.module.Module;
24 import com.intellij.openapi.module.ModuleManager;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.projectRoots.Sdk;
27 import com.intellij.openapi.projectRoots.SdkModel;
28 import com.intellij.openapi.projectRoots.impl.SdkListCellRenderer;
29 import com.intellij.openapi.roots.ModuleRootManager;
30 import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
31 import com.intellij.openapi.ui.ComboBox;
32 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
33 import com.intellij.openapi.util.io.FileUtil;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.ui.CollectionComboBoxModel;
36 import com.intellij.ui.HideableDecorator;
37 import com.intellij.ui.RawCommandLineEditor;
38 import com.intellij.ui.components.JBCheckBox;
39 import com.intellij.ui.components.JBLabel;
40 import com.intellij.util.PathMappingSettings;
41 import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
42 import com.jetbrains.python.configuration.PyConfigureInterpretersLinkPanel;
43 import com.jetbrains.python.run.AbstractPyCommonOptionsForm;
44 import com.jetbrains.python.run.PyCommonOptionsFormData;
45 import com.jetbrains.python.sdk.PySdkUtil;
46 import com.jetbrains.python.sdk.PythonSdkType;
47 import org.jetbrains.annotations.Nullable;
48
49 import javax.swing.*;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.Map;
55
56 /**
57  * @author yole
58  */
59 public class PyIdeCommonOptionsForm implements AbstractPyCommonOptionsForm {
60   private JPanel myMainPanel;
61   private EnvironmentVariablesComponent myEnvsComponent;
62   private RawCommandLineEditor myInterpreterOptionsTextField;
63   private TextFieldWithBrowseButton myWorkingDirectoryTextField;
64   private JComboBox myInterpreterComboBox;
65   private JBLabel myPythonInterpreterJBLabel;
66   private JLabel myProjectLabel;
67   private ModulesComboBox myModuleCombo;
68   private JPanel myConfigureInterpretersPanel;
69   private String mySelectedSdkHome = null;
70   private PathMappingsComponent myPathMappingsComponent;
71   private JPanel myHideablePanel;
72   private JBCheckBox myAddContentRootsCheckbox;
73   private JBCheckBox myAddSourceRootsCheckbox;
74
75   private JComponent labelAnchor;
76   private final Project myProject;
77   private List<Sdk> myPythonSdks;
78   private boolean myInterpreterRemote;
79   private final HideableDecorator myDecorator;
80
81   public PyIdeCommonOptionsForm(PyCommonOptionsFormData data) {
82     myProject = data.getProject();
83     myWorkingDirectoryTextField.addBrowseFolderListener("Select Working Directory", "", data.getProject(),
84                                                         FileChooserDescriptorFactory.createSingleFolderDescriptor());
85
86     myPythonSdks = new ArrayList<Sdk>(PythonSdkType.getAllSdks());
87     myPythonSdks.add(0, null);
88
89     myInterpreterComboBox.setModel(new CollectionComboBoxModel(myPythonSdks, null));
90
91     final Module[] modules = ModuleManager.getInstance(data.getProject()).getModules();
92     if (modules.length == 1) {
93       setModule(modules[0]);
94       myProjectLabel.setVisible(false);
95       myModuleCombo.setVisible(false);
96     }
97     else {
98       final List<Module> validModules = data.getValidModules();
99       Module selection = validModules.size() > 0 ? validModules.get(0) : null;
100       myModuleCombo.setModules(validModules);
101       myModuleCombo.setSelectedModule(selection);
102       myModuleCombo.addActionListener(new ActionListener() {
103         @Override
104         public void actionPerformed(ActionEvent e) {
105           updateDefaultInterpreter(myModuleCombo.getSelectedModule());
106         }
107       });
108       updateDefaultInterpreter(myModuleCombo.getSelectedModule());
109     }
110
111     setAnchor(myEnvsComponent.getLabel());
112     myPathMappingsComponent.setAnchor(myEnvsComponent.getLabel());
113
114     if (data.showConfigureInterpretersLink()) {
115       myConfigureInterpretersPanel.add(new PyConfigureInterpretersLinkPanel(myMainPanel));
116     }
117
118     addInterpreterComboBoxActionListener(new ActionListener() {
119       @Override
120       public void actionPerformed(ActionEvent event) {
121         updateRemoteInterpreterMode();
122       }
123     }
124     );
125
126     updateRemoteInterpreterMode();
127
128     myDecorator = new HideableDecorator(myHideablePanel, "Environment", false) {
129       @Override
130       protected void on() {
131         super.on();
132         storeState();
133       }
134
135       @Override
136       protected void off() {
137         super.off();
138         storeState();
139       }
140       private void storeState() {
141         PropertiesComponent.getInstance().setValue(EXPAND_PROPERTY_KEY, String.valueOf(isExpanded()), "true");
142       }
143     };
144     myDecorator.setOn(PropertiesComponent.getInstance().getBoolean(EXPAND_PROPERTY_KEY, true));
145     myDecorator.setContentComponent(myMainPanel);
146   }
147
148   @Override
149   public JComponent getMainPanel() {
150     return myHideablePanel;
151   }
152
153   @Override
154   public void subscribe() {
155     PyConfigurableInterpreterList myInterpreterList = PyConfigurableInterpreterList.getInstance(myProject);
156     ProjectSdksModel myProjectSdksModel = myInterpreterList.getModel();
157     myProjectSdksModel.addListener(new MyListener(this, myInterpreterList));
158     updateSdkList(true, myInterpreterList);
159   }
160
161   @Override
162   public JComponent getAnchor() {
163     return labelAnchor;
164   }
165
166   @Override
167   public void setAnchor(JComponent anchor) {
168     labelAnchor = anchor;
169     myEnvsComponent.setAnchor(anchor);
170     myPythonInterpreterJBLabel.setAnchor(anchor);
171   }
172
173   public String getInterpreterOptions() {
174     return myInterpreterOptionsTextField.getText().trim();
175   }
176
177   public void setInterpreterOptions(String interpreterOptions) {
178     myInterpreterOptionsTextField.setText(interpreterOptions);
179   }
180
181   public String getWorkingDirectory() {
182     return FileUtil.toSystemIndependentName(myWorkingDirectoryTextField.getText().trim());
183   }
184
185   public void setWorkingDirectory(String workingDirectory) {
186     myWorkingDirectoryTextField.setText(workingDirectory == null ? "" : FileUtil.toSystemDependentName(workingDirectory));
187   }
188
189   @Override
190   public String getSdkHome() {
191     final Sdk selectedSdk = (Sdk)myInterpreterComboBox.getSelectedItem();
192     return selectedSdk == null ? null : selectedSdk.getHomePath();
193   }
194
195   @Override
196   public void setSdkHome(String sdkHome) {
197     mySelectedSdkHome = sdkHome;
198   }
199
200   @Nullable
201   @Override
202   public Module getModule() {
203     final Module selectedItem = myModuleCombo.getSelectedModule();
204     if (selectedItem != null) {
205       return selectedItem;
206     }
207     final Module[] modules = ModuleManager.getInstance(myProject).getModules();
208     if (modules.length == 1) {
209       return modules[0];
210     }
211     return null;
212   }
213
214   @Override
215   public void setModule(Module module) {
216     myModuleCombo.setSelectedModule(module);
217     updateDefaultInterpreter(module);
218   }
219
220   private void updateDefaultInterpreter(Module module) {
221     final Sdk sdk = module == null ? null : ModuleRootManager.getInstance(module).getSdk();
222     String projectSdkName = sdk == null ? "none" : sdk.getName();
223     myInterpreterComboBox.setRenderer(new SdkListCellRenderer("Project Default (" + projectSdkName + ")"));
224   }
225
226   public void updateSdkList(boolean preserveSelection, PyConfigurableInterpreterList myInterpreterList) {
227     myPythonSdks = myInterpreterList.getAllPythonSdks(myProject);
228     Sdk selection = preserveSelection ? (Sdk)myInterpreterComboBox.getSelectedItem() : null;
229     if (!myPythonSdks.contains(selection)) {
230       selection = null;
231     }
232     myPythonSdks.add(0, null);
233     myInterpreterComboBox.setModel(new CollectionComboBoxModel(myPythonSdks, selection));
234   }
235
236   @Override
237   public boolean isUseModuleSdk() {
238     return myInterpreterComboBox.getSelectedItem() == null;
239   }
240
241   @Override
242   public void setUseModuleSdk(boolean useModuleSdk) {
243     myInterpreterComboBox.setSelectedItem(useModuleSdk ? null : PythonSdkType.findSdkByPath(myPythonSdks, mySelectedSdkHome));
244   }
245
246   public boolean isPassParentEnvs() {
247     return myEnvsComponent.isPassParentEnvs();
248   }
249
250   public void setPassParentEnvs(boolean passParentEnvs) {
251     myEnvsComponent.setPassParentEnvs(passParentEnvs);
252   }
253
254   public Map<String, String> getEnvs() {
255     return myEnvsComponent.getEnvs();
256   }
257
258   public void setEnvs(Map<String, String> envs) {
259     myEnvsComponent.setEnvs(envs);
260   }
261
262   @Override
263   @Nullable
264   public PathMappingSettings getMappingSettings() {
265     if (myInterpreterRemote) {
266       return myPathMappingsComponent.getMappingSettings();
267     }
268     else {
269       return new PathMappingSettings();
270     }
271   }
272
273   @Override
274   public void setMappingSettings(@Nullable PathMappingSettings mappingSettings) {
275     myPathMappingsComponent.setMappingSettings(mappingSettings);
276   }
277
278   @Override
279   public boolean shouldAddContentRoots() {
280     return myAddContentRootsCheckbox.isSelected();
281   }
282
283   @Override
284   public boolean shouldAddSourceRoots() {
285     return myAddSourceRootsCheckbox.isSelected();
286   }
287
288   @Override
289   public void setAddContentRoots(boolean flag) {
290     myAddContentRootsCheckbox.setSelected(flag);
291   }
292
293   @Override
294   public void setAddSourceRoots(boolean flag) {
295     myAddSourceRootsCheckbox.setSelected(flag);
296   }
297
298   private void createUIComponents() {
299     myInterpreterComboBox = new ComboBox(100);
300   }
301
302   private void setRemoteInterpreterMode(boolean isInterpreterRemote) {
303     myInterpreterRemote = isInterpreterRemote;
304     myPathMappingsComponent.setVisible(isInterpreterRemote);
305   }
306
307   private void updateRemoteInterpreterMode() {
308     setRemoteInterpreterMode(PySdkUtil.isRemote(getSdkSelected()));
309   }
310
311   @Nullable
312   private Sdk getSdkSelected() {
313     String sdkHome = getSdkHome();
314     if (StringUtil.isEmptyOrSpaces(sdkHome)) {
315       final Sdk projectJdk = PythonSdkType.findPythonSdk(getModule());
316       if (projectJdk != null) {
317         sdkHome = projectJdk.getHomePath();
318       }
319     }
320
321     return PythonSdkType.findSdkByPath(sdkHome);
322   }
323
324   @Override
325   public void addInterpreterComboBoxActionListener(ActionListener listener) {
326     myInterpreterComboBox.addActionListener(listener);
327   }
328
329   @Override
330   public void removeInterpreterComboBoxActionListener(ActionListener listener) {
331     myInterpreterComboBox.removeActionListener(listener);
332   }
333
334   private static class MyListener implements SdkModel.Listener {
335     private final PyIdeCommonOptionsForm myForm;
336     private PyConfigurableInterpreterList myInterpreterList;
337
338     public MyListener(PyIdeCommonOptionsForm form, PyConfigurableInterpreterList interpreterList) {
339       myForm = form;
340       myInterpreterList = interpreterList;
341     }
342
343
344     private void update() {
345       myForm.updateSdkList(true, myInterpreterList);
346     }
347
348     @Override
349     public void sdkAdded(Sdk sdk) {
350       update();
351     }
352
353     @Override
354     public void beforeSdkRemove(Sdk sdk) {
355       update();
356     }
357
358     @Override
359     public void sdkChanged(Sdk sdk, String previousName) {
360       update();
361     }
362
363     @Override
364     public void sdkHomeSelected(Sdk sdk, String newSdkHome) {
365     }
366   }
367
368   @Override
369   public String getModuleName() {
370     Module module = getModule();
371     return module != null? module.getName() : null;
372   }
373 }