diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java index 4a5dd2374711..ddcc0ad71e6a 100644 --- a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java @@ -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 extends ProjectSettingsStepBase 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 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 extends ProjectSettingsStepBase 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 extends ProjectSettingsStepBase 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 extends ProjectSettingsStepBase 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 extends ProjectSettingsStepBase 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 extends ProjectSettingsStepBase i return false; } - if (myNewVirtualEnvPanel != null) { - final List validationInfos = myNewVirtualEnvPanel.validateAll(); + final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel; + if (interpreterPanel != null) { + final List 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 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 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> 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 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, "
"); - setWarningText(warning); + warnings.add(warningResult.getErrorMessage()); } } } + + if (!warnings.isEmpty()) { + setWarningText(StringUtil.join(warnings, "
")); + } 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> validateFramework(@NotNull PyFrameworkProjectGenerator generator, @NotNull Sdk sdk) { + final List warnings = new ArrayList<>(); + boolean installFramework = false; + if (!generator.isFrameworkInstalled(sdk)) { + final String frameworkName = generator.getFrameworkTitle(); + if (PyPackageUtil.packageManagementEnabled(sdk)) { + installFramework = true; + final List 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 extends ProjectSettingsStepBase 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 extends ProjectSettingsStepBase 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 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 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 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 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 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, 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 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 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 getValidPythonSdks() { + final List 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; } } diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt new file mode 100644 index 000000000000..7e578eb27efa --- /dev/null +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt @@ -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, + 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 + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkGroupPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkGroupPanel.kt index db4c746c6024..4fe266b9b170 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkGroupPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkGroupPanel.kt @@ -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 = 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, 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 } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt index 84e1b2b3df61..8b3286b8a428 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkPanel.kt @@ -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 = 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? =