blob: 213b0e1e69d5609b7964d82cc27be1074099beb9 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.configuration;
import com.google.common.collect.Sets;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModel;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.remote.RemoteSdkAdditionalData;
import com.intellij.ui.*;
import com.intellij.ui.components.JBList;
import com.intellij.util.NullableConsumer;
import com.intellij.util.NullableFunction;
import com.intellij.util.containers.FactoryMap;
import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import com.jetbrains.python.sdk.*;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PythonSdkDetailsDialog extends DialogWrapper {
private JPanel myMainPanel;
private JList mySdkList;
private boolean mySdkListChanged = false;
private final PyConfigurableInterpreterList myInterpreterList;
private final ProjectSdksModel myProjectSdksModel;
private Map<Sdk, SdkModificator> myModificators = new FactoryMap<Sdk, SdkModificator>() {
@Override
protected SdkModificator create(Sdk sdk) {
return sdk.getSdkModificator();
}
};
private Set<SdkModificator> myModifiedModificators = new HashSet<SdkModificator>();
private final Project myProject;
private boolean myShowOtherProjectVirtualenvs = true;
private final Module myModule;
private NullableConsumer<Sdk> myShowMoreCallback;
private SdkModel.Listener myListener;
public PythonSdkDetailsDialog(Project project, NullableConsumer<Sdk> showMoreCallback) {
super(project, true);
myModule = null;
setTitle("Project Interpreters");
myShowMoreCallback = showMoreCallback;
myProject = project;
myInterpreterList = PyConfigurableInterpreterList.getInstance(myProject);
myProjectSdksModel = myInterpreterList.getModel();
init();
updateOkButton();
}
@Override
protected void dispose() {
myProjectSdksModel.removeListener(myListener);
super.dispose();
}
public PythonSdkDetailsDialog(Module module, NullableConsumer<Sdk> showMoreCallback) {
super(module.getProject());
myModule = module;
setTitle("Project Interpreters");
myShowMoreCallback = showMoreCallback;
myProject = module.getProject();
myInterpreterList = PyConfigurableInterpreterList.getInstance(myProject);
myProjectSdksModel = myInterpreterList.getModel();
init();
updateOkButton();
}
@Nullable
@Override
protected JComponent createCenterPanel() {
mySdkList = new JBList();
//noinspection unchecked
mySdkList.setCellRenderer(new PySdkListCellRenderer("", myModificators));
mySdkList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
ToolbarDecorator decorator = ToolbarDecorator.createDecorator(mySdkList).disableUpDownActions()
.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
addSdk(button);
updateOkButton();
}
})
.setEditAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
editSdk();
updateOkButton();
}
})
.setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
removeSdk();
updateOkButton();
}
})
.addExtraAction(new ToggleVirtualEnvFilterButton())
.addExtraAction(new ShowPathButton());
decorator.setPreferredSize(new Dimension(600, 500));
myMainPanel = decorator.createPanel();
refreshSdkList();
addListeners();
return myMainPanel;
}
private void addListeners() {
myListener = new SdkModel.Listener() {
@Override
public void sdkAdded(Sdk sdk) {
}
@Override
public void beforeSdkRemove(Sdk sdk) {
}
@Override
public void sdkChanged(Sdk sdk, String previousName) {
refreshSdkList();
}
@Override
public void sdkHomeSelected(Sdk sdk, String newSdkHome) {
}
};
myProjectSdksModel.addListener(myListener);
mySdkList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
updateOkButton();
updateUI(getSelectedSdk());
}
});
}
private void updateUI(final Sdk selectedSdk) {
myProjectSdksModel.setProjectSdk(selectedSdk);
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return mySdkList;
}
public boolean isModified() {
Sdk projectSdk = getSdk();
if (projectSdk != null) {
projectSdk = myProjectSdksModel.findSdk(projectSdk.getName());
}
return getSelectedSdk() != projectSdk || mySdkListChanged ||
myProjectSdksModel.isModified() ||
!myModifiedModificators.isEmpty();
}
protected void updateOkButton() {
super.setOKActionEnabled(isModified());
}
@Override
protected void doOKAction() {
try {
apply();
}
catch (ConfigurationException ignored) {
}
super.doOKAction();
}
public void apply() throws ConfigurationException {
for (SdkModificator modificator : myModifiedModificators) {
modificator.commitChanges();
}
myModificators.clear();
myModifiedModificators.clear();
myProjectSdksModel.apply();
mySdkListChanged = false;
myShowMoreCallback.consume(getSelectedSdk());
Disposer.dispose(getDisposable());
}
@Nullable
public Sdk getSelectedSdk() {
return (Sdk)mySdkList.getSelectedValue();
}
private void refreshSdkList() {
final List<Sdk> pythonSdks = myInterpreterList.getAllPythonSdks(myProject);
Sdk projectSdk = getSdk();
if (!myShowOtherProjectVirtualenvs) {
VirtualEnvProjectFilter.removeNotMatching(myProject, pythonSdks);
}
//noinspection unchecked
mySdkList.setModel(new CollectionListModel<Sdk>(pythonSdks));
mySdkListChanged = false;
if (projectSdk != null) {
projectSdk = myProjectSdksModel.findSdk(projectSdk.getName());
mySdkList.clearSelection();
mySdkList.setSelectedValue(projectSdk, true);
mySdkList.updateUI();
}
}
@Nullable
private Sdk getSdk() {
if (myModule == null) {
return ProjectRootManager.getInstance(myProject).getProjectSdk();
}
final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule);
return rootManager.getSdk();
}
private void addSdk(AnActionButton button) {
PythonSdkDetailsStep
.show(myProject, myProjectSdksModel.getSdks(), null, myMainPanel, button.getPreferredPopupPoint().getScreenPoint(),
new NullableConsumer<Sdk>() {
@Override
public void consume(Sdk sdk) {
addCreatedSdk(sdk, true);
}
});
}
private void addCreatedSdk(@Nullable final Sdk sdk, boolean newVirtualEnv) {
if (sdk != null) {
final PySdkService sdkService = PySdkService.getInstance();
sdkService.restoreSdk(sdk);
boolean isVirtualEnv = PythonSdkType.isVirtualEnv(sdk);
if (isVirtualEnv && !newVirtualEnv) {
AddVEnvOptionsDialog dialog = new AddVEnvOptionsDialog(myMainPanel);
dialog.show();
if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
return;
}
SdkModificator modificator = myModificators.get(sdk);
setSdkAssociated(modificator, !dialog.makeAvailableToAll());
myModifiedModificators.add(modificator);
}
final Sdk oldSdk = myProjectSdksModel.findSdk(sdk);
if (oldSdk == null) {
myProjectSdksModel.addSdk(sdk);
}
refreshSdkList();
mySdkList.setSelectedValue(sdk, true);
mySdkListChanged = true;
}
}
private void editSdk() {
final Sdk currentSdk = getSelectedSdk();
if (currentSdk != null) {
if (currentSdk.getSdkAdditionalData() instanceof RemoteSdkAdditionalData) {
editRemoteSdk(currentSdk);
}
else {
editSdk(currentSdk);
}
updateUI(currentSdk);
}
}
private void editRemoteSdk(Sdk currentSdk) {
PythonRemoteInterpreterManager remoteInterpreterManager = PythonRemoteInterpreterManager.getInstance();
if (remoteInterpreterManager != null) {
final SdkModificator modificator = myModificators.get(currentSdk);
Set<Sdk> existingSdks = Sets.newHashSet(myProjectSdksModel.getSdks());
existingSdks.remove(currentSdk);
if (remoteInterpreterManager.editSdk(myProject, modificator, existingSdks)) {
myModifiedModificators.add(modificator);
}
}
}
private void editSdk(final Sdk currentSdk) {
final SdkModificator modificator = myModificators.get(currentSdk);
final EditSdkDialog dialog = new EditSdkDialog(myProject, modificator, new NullableFunction<String, String>() {
@Override
public String fun(String s) {
if (isDuplicateSdkName(s, currentSdk)) {
return "Please specify a unique name for the interpreter";
}
return null;
}
});
dialog.show();
if (dialog.isOK()) {
final boolean pathChanged = !Comparing.equal(currentSdk.getHomePath(), dialog.getHomePath());
if (!currentSdk.getName().equals(dialog.getName()) || pathChanged || dialog.isAssociateChanged()) {
myModifiedModificators.add(modificator);
modificator.setName(dialog.getName());
modificator.setHomePath(dialog.getHomePath());
if (dialog.isAssociateChanged()) {
setSdkAssociated(modificator, dialog.associateWithProject());
}
if (pathChanged) {
reloadSdk(currentSdk);
}
}
}
}
private void setSdkAssociated(SdkModificator modificator, boolean isAssociated) {
PythonSdkAdditionalData additionalData = (PythonSdkAdditionalData)modificator.getSdkAdditionalData();
if (additionalData == null) {
additionalData = new PythonSdkAdditionalData(PythonSdkFlavor.getFlavor(modificator.getHomePath()));
modificator.setSdkAdditionalData(additionalData);
}
if (isAssociated) {
additionalData.associateWithProject(myProject);
}
else {
additionalData.setAssociatedProjectPath(null);
}
}
private boolean isDuplicateSdkName(String s, Sdk sdk) {
for (Sdk existingSdk : myProjectSdksModel.getSdks()) {
if (existingSdk == sdk) {
continue;
}
String existingName;
if (myModificators.containsKey(existingSdk)) {
existingName = myModificators.get(existingSdk).getName();
}
else {
existingName = existingSdk.getName();
}
if (existingName.equals(s)) {
return true;
}
}
return false;
}
private void removeSdk() {
final Sdk currentSdk = getSelectedSdk();
if (currentSdk != null) {
final PySdkService sdkService = PySdkService.getInstance();
sdkService.removeSdk(currentSdk);
myProjectSdksModel.removeSdk(currentSdk);
if (myModificators.containsKey(currentSdk)) {
SdkModificator modificator = myModificators.get(currentSdk);
myModifiedModificators.remove(modificator);
myModificators.remove(currentSdk);
}
refreshSdkList();
mySdkListChanged = true;
// TODO select initially selected SDK
if (mySdkList.getSelectedIndex() < 0) {
mySdkList.setSelectedIndex(0);
}
}
}
private void reloadSdk() {
final Sdk currentSdk = getSelectedSdk();
if (currentSdk != null) {
myModifiedModificators.add(myModificators.get(currentSdk));
reloadSdk(currentSdk);
}
}
private void reloadSdk(Sdk currentSdk) {
PythonSdkType.setupSdkPaths(myProject, null, currentSdk, myModificators.get(currentSdk)); // or must it be a RunWriteAction?
}
private class ToggleVirtualEnvFilterButton extends ToggleActionButton implements DumbAware {
public ToggleVirtualEnvFilterButton() {
super("Show virtual environments associated with other projects", AllIcons.General.Filter);
}
@Override
public boolean isSelected(AnActionEvent e) {
return myShowOtherProjectVirtualenvs;
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
myShowOtherProjectVirtualenvs = state;
refreshSdkList();
updateOkButton();
}
}
private class ShowPathButton extends AnActionButton implements DumbAware {
public ShowPathButton() {
super("Show paths for the selected interpreter", AllIcons.Actions.ShowAsTree);
}
@Override
public boolean isEnabled() {
return getSelectedSdk() != null;
}
@Override
public void actionPerformed(AnActionEvent e) {
Sdk sdk = getSelectedSdk();
if (sdk instanceof PyDetectedSdk) {
final String sdkName = sdk.getName();
VirtualFile sdkHome = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
@Override
public VirtualFile compute() {
return LocalFileSystem.getInstance().refreshAndFindFileByPath(sdkName);
}
});
sdk =
SdkConfigurationUtil.setupSdk(ProjectJdkTable.getInstance().getAllJdks(), sdkHome, PythonSdkType.getInstance(), true, null, null);
}
final PythonPathEditor pathEditor = createPathEditor(sdk);
final SdkModificator sdkModificator = myModificators.get(sdk);
PythonPathDialog dialog = new PythonPathDialog(myProject, pathEditor);
pathEditor.reset(sdk != null ? sdkModificator : null);
dialog.show();
if (dialog.isOK()) {
if (pathEditor.isModified()) {
pathEditor.apply(sdkModificator);
myModifiedModificators.add(sdkModificator);
}
}
updateOkButton();
}
}
private PythonPathEditor createPathEditor(final Sdk sdk) {
if (PySdkUtil.isRemote(sdk)) {
return new PythonPathEditor("Classes", OrderRootType.CLASSES, FileChooserDescriptorFactory.createAllButJarContentsDescriptor()) {
private final PyRemoteSdkAdditionalDataBase myRemoteSdkData = (PyRemoteSdkAdditionalDataBase) sdk.getSdkAdditionalData();
@Override
protected void onReloadButtonClicked() {
reloadSdk();
}
@Override
protected String getPresentablePath(VirtualFile value) {
String path = value.getPath();
return myRemoteSdkData.getPathMappings().convertToRemote(path);
}
@Override
protected void addToolbarButtons(ToolbarDecorator toolbarDecorator) {
toolbarDecorator.setAddActionUpdater(new AnActionButtonUpdater() {
@Override
public boolean isEnabled(AnActionEvent e) {
return false; //TODO: implement adding remote path
}
});
}
};
}
else {
return new PythonPathEditor("Classes", OrderRootType.CLASSES, FileChooserDescriptorFactory.createAllButJarContentsDescriptor()) {
@Override
protected void onReloadButtonClicked() {
reloadSdk();
}
};
}
}
}