PY-26372 Refactored new project form validation to take into account new project interpreter fields

This commit is contained in:
Andrey Vlasovskikh
2017-10-10 05:49:49 +03:00
parent b616d2d01b
commit 91c0f3840f
4 changed files with 307 additions and 324 deletions
@@ -16,90 +16,59 @@
package com.jetbrains.python.newProject.steps;
import com.google.common.collect.Iterables;
import com.intellij.execution.ExecutionException;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase;
import com.intellij.ide.util.projectWizard.WebProjectTemplate;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.LabeledComponent;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.HideableDecorator;
import com.intellij.ui.TextAccessor;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PathUtil;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.update.UiNotifyConnector;
import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
import com.jetbrains.python.configuration.VirtualEnvProjectFilter;
import com.jetbrains.python.newProject.PyFrameworkProjectGenerator;
import com.jetbrains.python.newProject.PythonProjectGenerator;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyPackageUtil;
import com.jetbrains.python.remote.PyProjectSynchronizer;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.sdk.PreferredSdkComparator;
import com.jetbrains.python.sdk.PyLazySdk;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.add.PyAddNewVirtualEnvPanel;
import icons.PythonIcons;
import com.jetbrains.python.sdk.add.PyAddSdkGroupPanel;
import com.jetbrains.python.sdk.add.PyAddSdkPanel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> implements DumbAware {
private static final Logger LOGGER = Logger.getInstance(ProjectSpecificSettingsStep.class);
private PythonSdkChooserCombo mySdkCombo;
private boolean myInstallFramework;
private Sdk mySdk;
@Nullable private JPanel mySelectedInterpretersPanel;
@Nullable private PyAddNewVirtualEnvPanel myNewVirtualEnvPanel;
/**
* For remote projects path for project on remote side
*/
private PyRemotePathField myRemotePathField;
/**
* If remote path required for project creation or not
*/
private boolean myRemotePathRequired;
@Nullable private PyAddSdkGroupPanel myInterpreterPanel;
public ProjectSpecificSettingsStep(@NotNull final DirectoryProjectGenerator<T> projectGenerator,
@NotNull final AbstractNewProjectStep.AbstractCallback callback) {
super(projectGenerator, callback);
}
private static void acceptsSdk(@NotNull final DirectoryProjectGenerator<?> generator,
@NotNull final Sdk sdk,
@NotNull final File projectDirectory) throws PythonProjectGenerator.PyNoProjectAllowedOnSdkException {
if (generator instanceof PythonProjectGenerator) {
((PythonProjectGenerator<?>)generator).checkProjectCanBeCreatedOnSdk(sdk, projectDirectory);
}
}
@Override
protected JPanel createAndFillContentPanel() {
if (myProjectGenerator instanceof PythonProjectGenerator) {
@@ -122,10 +91,8 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
if (advancedSettings != null) {
final JPanel jPanel = new JPanel(new VerticalFlowLayout());
final HideableDecorator deco = new HideableDecorator(jPanel, "Mor&e Settings", false);
boolean isValid = checkValid();
deco.setOn(!isValid);
if (myProjectGenerator instanceof PythonProjectGenerator && !deco.isExpanded()) {
final ValidationResult result = ((PythonProjectGenerator)myProjectGenerator).warningValidation(getSdk());
if (myProjectGenerator instanceof PythonProjectGenerator) {
final ValidationResult result = ((PythonProjectGenerator)myProjectGenerator).warningValidation(getInterpreterPanelSdk());
deco.setOn(!result.isOk());
}
deco.setContentComponent(advancedSettings);
@@ -137,16 +104,27 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
@Nullable
public Sdk getSdk() {
if (!(myProjectGenerator instanceof PythonProjectGenerator)) return null;
if (mySdk != null) return mySdk;
if (mySelectedInterpretersPanel != null && mySelectedInterpretersPanel == myNewVirtualEnvPanel) {
return new PyLazySdk("Uninitialized virtual environment at " + myNewVirtualEnvPanel.getPath(),
() -> myNewVirtualEnvPanel != null ? myNewVirtualEnvPanel.getOrCreateSdk() : null);
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel == null) return null;
final PyAddSdkPanel panel = interpreterPanel.getSelectedPanel();
if (panel instanceof PyAddNewVirtualEnvPanel) {
final PyAddNewVirtualEnvPanel virtualEnvPanel = (PyAddNewVirtualEnvPanel)panel;
return new PyLazySdk("Uninitialized virtual environment at " + virtualEnvPanel.getPath(),
virtualEnvPanel::getOrCreateSdk);
}
else if (panel instanceof PyAddExistingSdkPanel) {
return panel.getSdk();
}
else {
return null;
}
return (Sdk)mySdkCombo.getComboBox().getSelectedItem();
}
public void setSdk(final Sdk sdk) {
mySdk = sdk;
@Nullable
private Sdk getInterpreterPanelSdk() {
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel == null) return null;
return interpreterPanel.getSdk();
}
public boolean installFramework() {
@@ -164,40 +142,10 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
((PythonProjectGenerator)myProjectGenerator).locationChanged(PathUtil.getFileName(path));
}
});
mySdkCombo.getComboBox().addItemListener(e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
final Runnable checkValidOnSwing = () -> ApplicationManager.getApplication().invokeLater(this::checkValid);
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
try {
// Refresh before validation to make sure no stale data
final Sdk sdk = getSdk();
if (sdk == null) {
return;
}
final boolean noPackages = PyPackageManager.getInstance(sdk).refreshAndGetPackages(true).isEmpty();
if (noPackages) {
LOGGER.warn(String.format("No packages on %s", sdk.getHomePath()));
}
checkValidOnSwing.run();
}
catch (final ExecutionException exception) {
LOGGER.warn(exception);
checkValidOnSwing.run();
}
}, "Refreshing List of Packages, Please Wait", false, null);
}
});
if (myRemotePathField != null) {
myRemotePathField.addTextChangeListener(this::checkValid);
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel != null) {
UiNotifyConnector.doWhenFirstShown(interpreterPanel, this::checkValid);
}
if (myNewVirtualEnvPanel != null) {
myNewVirtualEnvPanel.addChangeListener(this::checkValid);
}
UiNotifyConnector.doWhenFirstShown(mySdkCombo, this::checkValid);
}
}
@@ -206,8 +154,11 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
*/
@Nullable
final String getRemotePath() {
final PyRemotePathField field = myRemotePathField;
return (field != null ? field.getTextField().getText() : null);
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel == null) return null;
final PyAddExistingSdkPanel panel = ObjectUtils.tryCast(interpreterPanel.getSelectedPanel(), PyAddExistingSdkPanel.class);
if (panel == null) return null;
return panel.getRemotePath();
}
@Override
@@ -226,85 +177,86 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
return false;
}
if (myNewVirtualEnvPanel != null) {
final List<ValidationInfo> validationInfos = myNewVirtualEnvPanel.validateAll();
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel != null) {
final List<ValidationInfo> validationInfos = interpreterPanel.validateAll();
if (!validationInfos.isEmpty()) {
setErrorText(StringUtil.join(validationInfos, info -> info.message, "\n"));
return false;
}
}
if (myProjectGenerator instanceof PythonProjectGenerator) {
final Sdk sdk = getSdk();
if (sdk == null) {
setErrorText("No Python interpreter selected");
return false;
}
if (!(sdk instanceof PyLazySdk) && PythonSdkType.isInvalid(sdk)) {
setErrorText("Choose valid python interpreter");
return false;
}
final List<String> warningList = new ArrayList<>();
final boolean isPy3k = PythonSdkType.getLanguageLevelForSdk(sdk).isPy3K();
try {
acceptsSdk(myProjectGenerator, sdk, new File(myLocationField.getText()));
}
catch (final PythonProjectGenerator.PyNoProjectAllowedOnSdkException e) {
setErrorText(e.getMessage());
return false;
}
if (myRemotePathRequired && StringUtil.isEmpty(myRemotePathField.getTextField().getText())) {
setErrorText("Remote path not provided");
final PythonProjectGenerator generator = ObjectUtils.tryCast(myProjectGenerator, PythonProjectGenerator.class);
final Sdk sdk = getInterpreterPanelSdk();
if (generator == null || sdk == null) return true;
try {
generator.checkProjectCanBeCreatedOnSdk(sdk, new File(myLocationField.getText()));
}
catch (final PythonProjectGenerator.PyNoProjectAllowedOnSdkException e) {
setErrorText(e.getMessage());
return false;
}
final List<String> warnings = new ArrayList<>();
final PyFrameworkProjectGenerator frameworkGenerator = ObjectUtils.tryCast(myProjectGenerator, PyFrameworkProjectGenerator.class);
if (frameworkGenerator != null) {
final String python3Error = validateFrameworkSupportsPython3(frameworkGenerator, sdk);
if (python3Error != null) {
setErrorText(python3Error);
return false;
}
if (myProjectGenerator instanceof PyFrameworkProjectGenerator) {
PyFrameworkProjectGenerator frameworkProjectGenerator = (PyFrameworkProjectGenerator)myProjectGenerator;
String frameworkName = frameworkProjectGenerator.getFrameworkTitle();
// Framework package check may be heavy in case of remote sdk and should not be called on AWT, pretend everything is OK for
// remote and check for packages later
if (!PythonSdkType.isRemote(sdk)) {
final Pair<Boolean, List<String>> validationInfo = validateFramework(frameworkGenerator, sdk);
myInstallFramework = validationInfo.first;
warnings.addAll(validationInfo.second);
if (isPy3k && !((PyFrameworkProjectGenerator)myProjectGenerator).supportsPython3()) {
setErrorText(frameworkName + " is not supported for the selected interpreter");
return false;
}
if (PythonSdkType.isRemote(sdk)) {
return true;
}
// All code beyond this line may be heavy in case of remote sdk and should not be called on AWT
// pretend everything is ok for remote and check package later
if (!isFrameworkInstalled(sdk)) {
if (PyPackageUtil.packageManagementEnabled(sdk)) {
myInstallFramework = true;
final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
if (!PyPackageUtil.hasManagement(packages)) {
warningList.add("Python packaging tools and " + frameworkName + " will be installed on the selected interpreter");
}
else {
warningList.add(frameworkName + " will be installed on the selected interpreter");
}
}
else {
warningList.add(frameworkName + " is not installed on the selected interpreter");
}
}
final ValidationResult warningResult = ((PythonProjectGenerator)myProjectGenerator).warningValidation(sdk);
if (!warningResult.isOk()) {
warningList.add(warningResult.getErrorMessage());
}
if (!warningList.isEmpty()) {
final String warning = StringUtil.join(warningList, "<br/>");
setWarningText(warning);
warnings.add(warningResult.getErrorMessage());
}
}
}
if (!warnings.isEmpty()) {
setWarningText(StringUtil.join(warnings, "<br/>"));
}
return true;
}
private boolean isFrameworkInstalled(Sdk sdk) {
PyFrameworkProjectGenerator projectGenerator = (PyFrameworkProjectGenerator)getProjectGenerator();
return projectGenerator != null && projectGenerator.isFrameworkInstalled(sdk);
private static String validateFrameworkSupportsPython3(@NotNull PyFrameworkProjectGenerator generator, @NotNull Sdk sdk) {
final String frameworkName = generator.getFrameworkTitle();
final boolean isPy3k = PythonSdkType.getLanguageLevelForSdk(sdk).isPy3K();
return isPy3k && !generator.supportsPython3() ? frameworkName + " is not supported for the selected interpreter" : null;
}
@NotNull
private static Pair<Boolean, List<String>> validateFramework(@NotNull PyFrameworkProjectGenerator generator, @NotNull Sdk sdk) {
final List<String> warnings = new ArrayList<>();
boolean installFramework = false;
if (!generator.isFrameworkInstalled(sdk)) {
final String frameworkName = generator.getFrameworkTitle();
if (PyPackageUtil.packageManagementEnabled(sdk)) {
installFramework = true;
final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
if (!PyPackageUtil.hasManagement(packages)) {
warnings.add("Python packaging tools and " + frameworkName + " will be installed on the selected interpreter");
}
else {
warnings.add(frameworkName + " will be installed on the selected interpreter");
}
}
else {
warnings.add(frameworkName + " is not installed on the selected interpreter");
}
}
return Pair.create(installFramework, warnings);
}
@Override
@@ -321,14 +273,6 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
panel.add(locationPanel);
panel.add(createInterpretersPanel());
final PythonRemoteInterpreterManager remoteInterpreterManager = PythonRemoteInterpreterManager.getInstance();
final Sdk sdk = getSdk();
if (remoteInterpreterManager != null && sdk != null) {
createRemotePathField(panel, remoteInterpreterManager);
}
final JPanel basePanelExtension = ((PythonProjectGenerator)myProjectGenerator).extendBasePanel();
if (basePanelExtension != null) {
panel.add(basePanelExtension);
@@ -343,175 +287,59 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
private JPanel createInterpretersPanel() {
final JPanel container = new JPanel(new BorderLayout());
final JPanel decoratorPanel = new JPanel(new VerticalFlowLayout());
final HideableDecorator decorator = new HideableDecorator(decoratorPanel, "Project Interpreter", false);
final List<Sdk> existingSdks = getValidPythonSdks();
final Sdk preferredSdk = getPreferredSdk(existingSdks);
final String newProjectPath = myLocationField.getText().trim();
final PyAddNewVirtualEnvPanel newVirtualEnvPanel = new PyAddNewVirtualEnvPanel(null, existingSdks, newProjectPath);
final PyAddExistingSdkPanel existingSdkPanel = new PyAddExistingSdkPanel(null, existingSdks, newProjectPath, preferredSdk);
final HideableDecorator decorator = new HideableDecorator(decoratorPanel, getProjectInterpreterTitle(newVirtualEnvPanel), false);
decorator.setContentComponent(container);
final Project defaultProject = ProjectManager.getInstance().getDefaultProject();
final List<Sdk> pythonSdks = PyConfigurableInterpreterList.getInstance(defaultProject).getAllPythonSdks();
Iterables.removeIf(pythonSdks, sdk -> !(sdk.getSdkType() instanceof PythonSdkType) || PythonSdkType.isInvalid(sdk));
Collections.sort(pythonSdks, new PreferredSdkComparator());
myNewVirtualEnvPanel = new PyAddNewVirtualEnvPanel(null, pythonSdks, myLocationField.getText().trim());
myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
myNewVirtualEnvPanel.setNewProjectPath(myLocationField.getText().trim());
final List<PyAddSdkPanel> panels = Arrays.asList(newVirtualEnvPanel, existingSdkPanel);
myInterpreterPanel = new PyAddSdkGroupPanel("New project interpreter", getIcon(), panels, newVirtualEnvPanel);
myInterpreterPanel.addChangeListener(() -> {
PyAddSdkGroupPanel panel = myInterpreterPanel;
if (panel != null) {
decorator.setTitle(getProjectInterpreterTitle(panel.getSelectedPanel()));
}
});
final Map<JBRadioButton, JPanel> radioButtons = new LinkedHashMap<>();
final JBRadioButton newVirtualEnvButton = new JBRadioButton(myNewVirtualEnvPanel.getPanelName());
radioButtons.put(newVirtualEnvButton, myNewVirtualEnvPanel);
radioButtons.put(new JBRadioButton("Existing interpreter"), createInterpreterCombo());
final ButtonGroup buttonGroup = new ButtonGroup();
for (JBRadioButton button : radioButtons.keySet()) {
buttonGroup.add(button);
}
final FormBuilder formBuilder = FormBuilder.createFormBuilder();
for (Map.Entry<JBRadioButton, JPanel> entry : radioButtons.entrySet()) {
final JBRadioButton button = entry.getKey();
final JPanel panel = entry.getValue();
panel.setBorder(JBUI.Borders.emptyLeft(30));
formBuilder.addComponent(button);
formBuilder.addComponent(panel);
button.addItemListener(e -> {
mySelectedInterpretersPanel = panel;
for (JPanel p : radioButtons.values()) {
final boolean isCurrentPanel = p.equals(panel);
UIUtil.setEnabled(p, isCurrentPanel, true);
if (isCurrentPanel) {
decorator.setTitle("Project Interpreter: " + StringUtil.toTitleCase(button.getText()));
}
}
});
}
mySelectedInterpretersPanel = myNewVirtualEnvPanel;
newVirtualEnvButton.setSelected(true);
container.add(formBuilder.getPanel(), BorderLayout.NORTH);
newVirtualEnvPanel.addChangeListener(this::checkValid);
existingSdkPanel.addChangeListener(this::checkValid);
myInterpreterPanel.addChangeListener(this::checkValid);
container.add(myInterpreterPanel, BorderLayout.NORTH);
return decoratorPanel;
}
private void createRemotePathField(@NotNull final JPanel panelToAddField,
@NotNull final PythonRemoteInterpreterManager remoteInterpreterManager) {
myRemotePathField = new PyRemotePathField();
myRemotePathField.addActionListener(e -> {
final Sdk currentSdk = getSdk();
if (!PySdkUtil.isRemote(currentSdk)) {
return;
}
// If chosen SDK is remote then display
final Pair<Supplier<String>, JPanel> browserForm;
try {
browserForm = remoteInterpreterManager.createServerBrowserForm(currentSdk);
}
catch (final ExecutionException | InterruptedException ex) {
Logger.getInstance(ProjectSpecificSettingsStep.class).warn("Failed to create server browse button", ex);
JBPopupFactory.getInstance().createMessage("Failed to browse remote server. Make sure you have permissions. ").show(panelToAddField);
return;
}
if (browserForm != null) {
browserForm.second.setVisible(true);
final DialogWrapper wrapper = new MyRemoteServerBrowserDialog(browserForm.second);
if (wrapper.showAndGet()) {
myRemotePathField.getTextField().setText(browserForm.first.get());
}
}
});
mySdkCombo.addChangedListener(e -> configureMappingField(remoteInterpreterManager));
panelToAddField.add(myRemotePathField.getMainPanel());
configureMappingField(remoteInterpreterManager);
}
/**
* Enables or disables "remote path" based on interpreter.
*/
private void configureMappingField(@NotNull final PythonRemoteInterpreterManager remoteInterpreterManager) {
if (myRemotePathField == null) {
return;
}
final JPanel mainPanel = myRemotePathField.getMainPanel();
final PyProjectSynchronizer synchronizer = getSynchronizer(remoteInterpreterManager);
if (synchronizer != null) {
final String defaultRemotePath = synchronizer.getDefaultRemotePath();
final boolean mappingRequired = defaultRemotePath != null;
mainPanel.setVisible(mappingRequired);
final TextAccessor textField = myRemotePathField.getTextField();
if (mappingRequired && StringUtil.isEmpty(textField.getText())) {
textField.setText(defaultRemotePath);
}
myRemotePathRequired = mappingRequired;
}
else {
mainPanel.setVisible(false);
myRemotePathRequired = false;
}
@NotNull
private static String getProjectInterpreterTitle(@NotNull PyAddSdkPanel panel) {
return "Project Interpreter: " + StringUtil.toTitleCase(panel.getPanelName());
}
@Nullable
private PyProjectSynchronizer getSynchronizer(@NotNull final PythonRemoteInterpreterManager manager) {
final Sdk sdk = getSdk();
if (sdk == null) {
return null;
private Sdk getPreferredSdk(@NotNull List<Sdk> sdks) {
final PyFrameworkProjectGenerator projectGenerator = ObjectUtils.tryCast(getProjectGenerator(), PyFrameworkProjectGenerator.class);
final boolean onlyPython2 = projectGenerator != null && !projectGenerator.supportsPython3();
final Sdk preferred = ContainerUtil.getFirstItem(sdks);
if (preferred == null) return null;
if (onlyPython2 && PythonSdkType.getLanguageLevelForSdk(preferred).isAtLeast(LanguageLevel.PYTHON30)) {
final Sdk python2Sdk = PythonSdkType.findPython2Sdk(sdks);
return python2Sdk != null ? python2Sdk : preferred;
}
return manager.getSynchronizer(sdk);
return preferred;
}
@NotNull
private PythonSdkChooserCombo createInterpreterCombo() {
final Project project = ProjectManager.getInstance().getDefaultProject();
final List<Sdk> sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks();
VirtualEnvProjectFilter.removeAllAssociated(sdks);
Iterables.removeIf(sdks, sdk -> sdk != null && PythonSdkType.isInvalid(sdk));
Sdk compatibleSdk = sdks.isEmpty() ? null : sdks.iterator().next();
DirectoryProjectGenerator generator = getProjectGenerator();
if (generator instanceof PyFrameworkProjectGenerator && !((PyFrameworkProjectGenerator)generator).supportsPython3()) {
if (compatibleSdk != null && PythonSdkType.getLanguageLevelForSdk(compatibleSdk).isPy3K()) {
Sdk python2Sdk = PythonSdkType.findPython2Sdk(sdks);
if (python2Sdk != null) {
compatibleSdk = python2Sdk;
}
}
}
final Sdk preferred = compatibleSdk;
mySdkCombo = new PythonSdkChooserCombo(project, sdks, myLocationField.getText().trim(), sdk -> sdk == preferred);
if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) {
mySdkCombo.putClientProperty("JButton.buttonType", null);
}
mySdkCombo.setButtonIcon(PythonIcons.Python.InterpreterGear);
myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
mySdkCombo.setNewProjectPath(myLocationField.getText().trim());
}
});
return mySdkCombo;
}
/**
* Dialog to display remote server browser
*/
private static class MyRemoteServerBrowserDialog extends DialogWrapper {
private final JPanel myBrowserForm;
MyRemoteServerBrowserDialog(@NotNull final JPanel browserForm) {
super(true);
myBrowserForm = browserForm;
init();
}
@Nullable
@Override
protected JComponent createCenterPanel() {
return myBrowserForm;
}
private static List<Sdk> getValidPythonSdks() {
final List<Sdk> pythonSdks = PyConfigurableInterpreterList.getInstance(null).getAllPythonSdks();
Iterables.removeIf(pythonSdks, sdk -> !(sdk.getSdkType() instanceof PythonSdkType) ||
PythonSdkType.isInvalid(sdk) ||
PySdkExtKt.getAssociatedProjectPath(sdk) != null);
Collections.sort(pythonSdks, new PreferredSdkComparator());
return pythonSdks;
}
}
@@ -0,0 +1,140 @@
// Copyright 2000-2017 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.
package com.jetbrains.python.newProject.steps
import com.intellij.execution.ExecutionException
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.component1
import com.intellij.openapi.util.component2
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.ui.FormBuilder
import com.intellij.util.ui.UIUtil
import com.jetbrains.python.remote.PyProjectSynchronizer
import com.jetbrains.python.remote.PythonRemoteInterpreterManager
import com.jetbrains.python.sdk.PySdkUtil
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.add.PyAddSdkPanel
import icons.PythonIcons
import java.awt.BorderLayout
import java.awt.Component
/**
* @author vlan
*/
class PyAddExistingSdkPanel(project: Project?,
existingSdks: List<Sdk>,
newProjectPath: String?,
preferredSdk: Sdk?) : PyAddSdkPanel() {
override val panelName = "Existing interpreter"
override val sdk: Sdk?
get() = sdkChooserCombo.comboBox.selectedItem as? Sdk
val remotePath: String?
get() = if (remotePathField.mainPanel.isVisible) remotePathField.textField.text else null
private val sdkChooserCombo: PythonSdkChooserCombo
private val remotePathField = PyRemotePathField().apply {
addActionListener {
val currentSdk = sdk ?: return@addActionListener
if (!PySdkUtil.isRemote(currentSdk)) return@addActionListener
textField.text = currentSdk.chooseRemotePath(parent) ?: return@addActionListener
}
}
init {
layout = BorderLayout()
// TODO: Set new project path on external change
sdkChooserCombo = PythonSdkChooserCombo(project, existingSdks, newProjectPath, { it != null && it == preferredSdk }).apply {
if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) {
putClientProperty("JButton.buttonType", null)
}
setButtonIcon(PythonIcons.Python.InterpreterGear)
addChangedListener {
update()
}
}
val formPanel = FormBuilder.createFormBuilder()
.addLabeledComponent("Interpreter:", sdkChooserCombo)
.addComponent(remotePathField.mainPanel)
.panel
add(formPanel, BorderLayout.NORTH)
update()
}
override fun validateAll() =
listOf(validateSdkChooserField(),
validateRemotePathField())
.filterNotNull()
fun addChangeListener(listener: Runnable) {
sdkChooserCombo.addChangedListener { listener.run() }
remotePathField.addTextChangeListener { listener.run() }
}
private fun validateSdkChooserField(): ValidationInfo? {
val selectedSdk = sdk
val message = when {
selectedSdk == null -> "No Python interpreter selected"
PythonSdkType.isInvalid(selectedSdk) -> "Choose valid Python interpreter"
else -> return null
}
return ValidationInfo(message, sdkChooserCombo)
}
private fun validateRemotePathField(): ValidationInfo? {
val path = remotePath
return when {
path != null && path.isBlank() -> ValidationInfo("Remote path not provided")
else -> null
}
}
private fun update() {
val synchronizer = sdk?.projectSynchronizer
remotePathField.mainPanel.isVisible = synchronizer != null
if (synchronizer != null) {
val defaultRemotePath = synchronizer.getDefaultRemotePath()
val textField = remotePathField.textField
if (defaultRemotePath != null && StringUtil.isEmpty(textField.text)) {
textField.text = defaultRemotePath
}
}
}
companion object {
private val Sdk.projectSynchronizer: PyProjectSynchronizer?
get() = PythonRemoteInterpreterManager.getInstance()?.getSynchronizer(this)
private fun Sdk.chooseRemotePath(owner: Component): String? {
val remoteManager = PythonRemoteInterpreterManager.getInstance() ?: return null
val (supplier, panel) = try {
remoteManager.createServerBrowserForm(this) ?: return null
}
catch (e: Exception) {
when (e) {
is ExecutionException, is InterruptedException -> {
Logger.getInstance(PyAddExistingSdkPanel::class.java).warn("Failed to create server browse button", e)
JBPopupFactory.getInstance()
.createMessage("Failed to browse remote server. Make sure you have permissions.")
.show(owner)
return null
}
else -> throw e
}
}
panel.isVisible = true
val wrapper = object : DialogWrapper(true) {
init { init() }
override fun createCenterPanel() = panel
}
return if (wrapper.showAndGet()) supplier.get() else null
}
}
}
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.sdk.add
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.ui.components.JBRadioButton
import com.intellij.util.ui.FormBuilder
import com.intellij.util.ui.JBUI
@@ -33,7 +34,8 @@ class PyAddSdkGroupPanel(name: String,
defaultPanel: PyAddSdkPanel) : PyAddSdkPanel() {
override val panelName = name
override val icon = panelIcon
private var selectedPanel: PyAddSdkPanel? = null
var selectedPanel: PyAddSdkPanel? = null
private val changeListeners: MutableList<Runnable> = mutableListOf()
init {
layout = BorderLayout()
@@ -46,29 +48,41 @@ class PyAddSdkGroupPanel(name: String,
override fun validateAll() = panels.filter { it.isEnabled }.flatMap { it.validateAll() }
override val sdk: Sdk?
get() = selectedPanel?.sdk
override fun getOrCreateSdk() = selectedPanel?.getOrCreateSdk()
fun addChangeListener(listener: Runnable) {
changeListeners += listener
}
private fun createRadioButtonPanel(panels: List<PyAddSdkPanel>, defaultPanel: PyAddSdkPanel): JPanel? {
val radioButtons = panels.map { JBRadioButton(it.panelName) to it }.toMap(linkedMapOf())
val buttonMap = panels.map { JBRadioButton(it.panelName) to it }.toMap(linkedMapOf())
ButtonGroup().apply {
for (button in radioButtons.keys) {
for (button in buttonMap.keys) {
add(button)
}
}
val formBuilder = FormBuilder.createFormBuilder()
for ((button, panel) in radioButtons) {
for ((button, panel) in buttonMap) {
panel.border = JBUI.Borders.emptyLeft(30)
formBuilder.addComponent(button)
formBuilder.addComponent(panel)
button.addItemListener {
selectedPanel = panel
for (c in panels) {
UIUtil.setEnabled(c, c == panel, true)
}
if (button.isSelected) {
selectedPanel = panel
for (listener in changeListeners) {
listener.run()
}
}
}
}
selectedPanel = defaultPanel
radioButtons.filterValues { it == defaultPanel }.keys.first().isSelected = true
buttonMap.filterValues { it == defaultPanel }.keys.first().isSelected = true
return formBuilder.panel
}
}
@@ -33,8 +33,9 @@ import javax.swing.JPanel
abstract class PyAddSdkPanel : JPanel() {
abstract val panelName: String
open val icon: Icon = PythonIcons.Python.Python
open val sdk: Sdk? = null
open fun getOrCreateSdk(): Sdk? = null
open fun getOrCreateSdk(): Sdk? = sdk
open fun validateAll(): List<ValidationInfo> = emptyList()
@@ -47,9 +48,9 @@ abstract class PyAddSdkPanel : JPanel() {
StringUtil.isEmptyOrSpaces(text) -> "Location field is empty"
file.exists() && !file.isDirectory -> "Location field path is not a directory"
file.isNotEmptyDirectory -> "Location directory is not empty"
else -> null
else -> return null
}
return message?.let { ValidationInfo(it, field) }
return ValidationInfo(message, field)
}
@JvmStatic protected fun validateAnacondaPresense(component: JComponent?): ValidationInfo? =