PY-22324 Reverted new project UI to single combo box to make it more consistent with UI for existing project

Creation of virtualenvs is still lazy and allows creating virtualenvs
just after the project folder is created. But new virtualenv is not
the default choice at the moment. We'll come back to this idea later.
This commit is contained in:
Andrey Vlasovskikh
2017-10-10 05:49:49 +03:00
parent 7d1013d574
commit 9ba6a767a3
10 changed files with 97 additions and 122 deletions
@@ -22,7 +22,6 @@ import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase;
import com.intellij.ide.util.projectWizard.WebProjectTemplate;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.LabeledComponent;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.VerticalFlowLayout;
@@ -35,6 +34,7 @@ import com.intellij.util.Consumer;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.update.UiNotifyConnector;
import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
import com.jetbrains.python.newProject.PyFrameworkProjectGenerator;
@@ -42,28 +42,20 @@ import com.jetbrains.python.newProject.PythonProjectGenerator;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.sdk.PreferredSdkComparator;
import com.jetbrains.python.sdk.PyLazySdk;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.add.PyAddNewVirtualEnvPanel;
import com.jetbrains.python.sdk.add.PyAddSdkGroupPanel;
import com.jetbrains.python.sdk.add.PyAddSdkPanel;
import com.jetbrains.python.sdk.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> implements DumbAware {
private boolean myInstallFramework;
@Nullable private PyAddSdkGroupPanel myInterpreterPanel;
@Nullable private PyAddExistingSdkPanel myInterpreterPanel;
public ProjectSpecificSettingsStep(@NotNull final DirectoryProjectGenerator<T> projectGenerator,
@NotNull final AbstractNewProjectStep.AbstractCallback callback) {
@@ -105,25 +97,14 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
@Nullable
public Sdk getSdk() {
if (!(myProjectGenerator instanceof PythonProjectGenerator)) return null;
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
final PyAddExistingSdkPanel 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 interpreterPanel.getSdk();
}
@Nullable
private Sdk getInterpreterPanelSdk() {
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
final PyAddExistingSdkPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel == null) return null;
return interpreterPanel.getSdk();
}
@@ -140,7 +121,7 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
final String fileName = PathUtil.getFileName(getNewProjectPath());
((PythonProjectGenerator)myProjectGenerator).locationChanged(fileName);
});
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
final PyAddExistingSdkPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel != null) {
UiNotifyConnector.doWhenFirstShown(interpreterPanel, this::checkValid);
}
@@ -152,11 +133,9 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
*/
@Nullable
final String getRemotePath() {
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
final PyAddExistingSdkPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel == null) return null;
final PyAddExistingSdkPanel panel = ObjectUtils.tryCast(interpreterPanel.getSelectedPanel(), PyAddExistingSdkPanel.class);
if (panel == null) return null;
return panel.getRemotePath();
return interpreterPanel.getRemotePath();
}
@Override
@@ -175,7 +154,7 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
return false;
}
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
final PyAddExistingSdkPanel interpreterPanel = myInterpreterPanel;
if (interpreterPanel != null) {
final List<ValidationInfo> validationInfos = interpreterPanel.validateAll();
if (!validationInfos.isEmpty()) {
@@ -187,7 +166,7 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
final PythonProjectGenerator generator = ObjectUtils.tryCast(myProjectGenerator, PythonProjectGenerator.class);
final Sdk sdk = getInterpreterPanelSdk();
if (generator == null || sdk == null) return true;
if (generator == null || sdk == null || sdk instanceof PyLazySdk) return true;
try {
generator.checkProjectCanBeCreatedOnSdk(sdk, new File(myLocationField.getText()));
@@ -260,17 +239,9 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
@Override
protected JPanel createBasePanel() {
if (myProjectGenerator instanceof PythonProjectGenerator) {
final BorderLayout layout = new BorderLayout();
final JPanel locationPanel = new JPanel(layout);
final JPanel panel = new JPanel(new VerticalFlowLayout(0, 2));
final LabeledComponent<TextFieldWithBrowseButton> location = createLocationComponent();
locationPanel.add(location, BorderLayout.CENTER);
panel.add(locationPanel);
panel.add(createInterpretersPanel());
final JPanel interpretersPanel = createInterpretersPanel();
panel.add(interpretersPanel);
final JPanel basePanelExtension = ((PythonProjectGenerator)myProjectGenerator).extendBasePanel();
if (basePanelExtension != null) {
panel.add(basePanelExtension);
@@ -283,35 +254,22 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
@NotNull
private JPanel createInterpretersPanel() {
final JPanel container = new JPanel(new BorderLayout());
final JPanel decoratorPanel = new JPanel(new VerticalFlowLayout());
final List<Sdk> existingSdks = getValidPythonSdks();
final Sdk preferredSdk = getPreferredSdk(existingSdks);
final FormBuilder formBuilder = FormBuilder.createFormBuilder();
formBuilder.addLabeledComponent("Location:", createLocationComponent().getComponent());
final String newProjectPath = getNewProjectPath();
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 PyAddExistingSdkPanel existingSdkPanel = new PyAddExistingSdkPanel(null, existingSdks, newProjectPath, preferredSdk, formBuilder);
final List<PyAddSdkPanel> panels = Arrays.asList(newVirtualEnvPanel, existingSdkPanel);
myInterpreterPanel = new PyAddSdkGroupPanel("New project interpreter", getIcon(), panels, newVirtualEnvPanel);
myInterpreterPanel.addChangeListener(() -> decorator.setTitle(getProjectInterpreterTitle(myInterpreterPanel.getSelectedPanel())));
myInterpreterPanel = existingSdkPanel;
newVirtualEnvPanel.addChangeListener(this::checkValid);
existingSdkPanel.addChangeListener(this::checkValid);
myInterpreterPanel.addChangeListener(this::checkValid);
addLocationChangeListener(e -> myInterpreterPanel.setNewProjectPath(getNewProjectPath()));
addLocationChangeListener(event -> {
final String path = getNewProjectPath();
newVirtualEnvPanel.setNewProjectPath(path);
existingSdkPanel.setNewProjectPath(path);
});
container.add(myInterpreterPanel, BorderLayout.NORTH);
return decoratorPanel;
return existingSdkPanel;
}
@NotNull
@@ -332,11 +290,6 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
});
}
@NotNull
private static String getProjectInterpreterTitle(@NotNull PyAddSdkPanel panel) {
return "Project Interpreter: " + StringUtil.toTitleCase(panel.getPanelName());
}
@Nullable
private Sdk getPreferredSdk(@NotNull List<Sdk> sdks) {
final PyFrameworkProjectGenerator projectGenerator = ObjectUtils.tryCast(getProjectGenerator(), PyFrameworkProjectGenerator.class);
@@ -29,7 +29,8 @@ import java.awt.Component
class PyAddExistingSdkPanel(project: Project?,
existingSdks: List<Sdk>,
newProjectPath: String?,
preferredSdk: Sdk?) : PyAddSdkPanel() {
preferredSdk: Sdk?,
formBuilder: FormBuilder) : PyAddSdkPanel() {
override val panelName = "Existing interpreter"
@@ -56,7 +57,6 @@ class PyAddExistingSdkPanel(project: Project?,
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)
@@ -66,7 +66,7 @@ class PyAddExistingSdkPanel(project: Project?,
update()
}
}
val formPanel = FormBuilder.createFormBuilder()
val formPanel = formBuilder
.addLabeledComponent("Interpreter:", sdkChooserCombo)
.addComponent(remotePathField.mainPanel)
.panel
@@ -27,6 +27,7 @@ import com.intellij.ui.ComboboxSpeedSearch;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
import com.jetbrains.python.sdk.PyLazySdk;
import com.jetbrains.python.sdk.PySdkListCellRenderer;
import com.jetbrains.python.sdk.PythonSdkDetailsStep;
import org.jetbrains.annotations.NotNull;
@@ -35,6 +36,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
/**
@@ -85,20 +87,36 @@ public class PythonSdkChooserCombo extends ComboboxWithBrowseButton {
final Sdk oldSelectedSdk = (Sdk)comboBox.getSelectedItem();
PythonSdkDetailsStep.show(project, sdks, null, this, getButton().getLocationOnScreen(), myNewProjectPath, sdk -> {
if (sdk == null) return;
final ProjectSdksModel projectSdksModel = interpreterList.getModel();
if (projectSdksModel.findSdk(sdk) == null) {
projectSdksModel.addSdk(sdk);
try {
projectSdksModel.apply();
}
catch (ConfigurationException e) {
LOG.error("Error adding new python interpreter " + e.getMessage());
if (!(sdk instanceof PyLazySdk)) {
final ProjectSdksModel projectSdksModel = interpreterList.getModel();
if (projectSdksModel.findSdk(sdk) == null) {
projectSdksModel.addSdk(sdk);
try {
projectSdksModel.apply();
}
catch (ConfigurationException e) {
LOG.error("Error adding new python interpreter " + e.getMessage());
}
}
}
final List<Sdk> committedSdks = interpreterList.getAllPythonSdks();
final List<Sdk> allSdks = new ArrayList<>();
if (sdk instanceof PyLazySdk) {
allSdks.add(sdk);
}
// TODO: Filter out and sort the interpreters list
allSdks.addAll(interpreterList.getAllPythonSdks());
final Sdk copiedSdk = interpreterList.getModel().findSdk(sdk.getName());
comboBox.setModel(new CollectionComboBoxModel<>(committedSdks, oldSelectedSdk));
comboBox.setSelectedItem(copiedSdk);
final Sdk selectedSdk;
if (sdk instanceof PyLazySdk) {
selectedSdk = sdk;
}
else if (copiedSdk != null) {
selectedSdk = copiedSdk;
}
else {
selectedSdk = oldSelectedSdk;
}
comboBox.setModel(new CollectionComboBoxModel<>(allSdks, selectedSdk));
});
}
@@ -18,10 +18,13 @@ package com.jetbrains.python.sdk
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl
import com.intellij.openapi.util.NullableComputable
import javax.swing.Icon
/**
* @author vlan
*/
class PyLazySdk(name: String, private val create: NullableComputable<Sdk>) : ProjectJdkImpl(name, PythonSdkType.getInstance(), null, null) {
class PyLazySdk(name: String,
val icon: Icon,
private val create: NullableComputable<Sdk>) : ProjectJdkImpl(name, PythonSdkType.getInstance(), null, null) {
fun create(): Sdk? = create.compute()
}
@@ -74,11 +74,15 @@ open class PySdkListCellRenderer(private val sdkModifiers: Map<Sdk, SdkModificat
private fun customizeIcon(sdk: Sdk): Icon? {
val flavor = PythonSdkFlavor.getPlatformIndependentFlavor(sdk.homePath)
val icon = if (flavor != null) flavor.icon else (sdk.sdkType as? SdkType)?.icon ?: return null
val icon = when {
sdk is PyLazySdk -> sdk.icon
flavor != null -> flavor.icon
else -> (sdk.sdkType as? SdkType)?.icon ?: return null
}
return when {
PythonSdkType.isInvalid(sdk) || PythonSdkType.isIncompleteRemote(sdk) || PythonSdkType.hasInvalidRemoteCredentials(sdk) ->
wrapIconWithWarningDecorator(icon)
sdk is PyDetectedSdk ->
sdk is PyDetectedSdk || sdk is PyLazySdk ->
IconLoader.getTransparentIcon(icon)
else ->
icon
@@ -50,7 +50,7 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
private static final String LOCAL = PyBundle.message("sdk.details.step.add.local");
private static final String REMOTE = PyBundle.message("sdk.details.step.add.remote");
private static final String ALL = PyBundle.message("sdk.details.step.show.all");
@Nullable private String myNewProjectPath;
@Nullable private final String myNewProjectPath;
public static void show(@Nullable final Project project,
@NotNull final Sdk[] existingSdks,
@@ -60,23 +60,24 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
@Nullable String newProjectPath,
@NotNull final NullableConsumer<Sdk> sdkAddedCallback) {
final PythonSdkDetailsStep sdkHomesStep = new PythonSdkDetailsStep(project, showAllDialog, ownerComponent, existingSdks,
sdkAddedCallback);
sdkHomesStep.myNewProjectPath = newProjectPath;
sdkAddedCallback, newProjectPath);
final ListPopup popup = JBPopupFactory.getInstance().createListPopup(sdkHomesStep);
popup.showInScreenCoordinates(ownerComponent, popupPoint);
}
public PythonSdkDetailsStep(@Nullable final Project project,
@Nullable final DialogWrapper showAllDialog,
@NotNull final Component ownerComponent,
@NotNull final Sdk[] existingSdks,
@NotNull final NullableConsumer<Sdk> sdkAddedCallback) {
private PythonSdkDetailsStep(@Nullable final Project project,
@Nullable final DialogWrapper showAllDialog,
@NotNull final Component ownerComponent,
@NotNull final Sdk[] existingSdks,
@NotNull final NullableConsumer<Sdk> sdkAddedCallback,
@Nullable String newProjectPath) {
super(null, getAvailableOptions(showAllDialog != null));
myProject = project;
myShowAll = showAllDialog;
myOwnerComponent = ownerComponent;
myExistingSdks = existingSdks;
mySdkAddedCallback = sdkAddedCallback;
myNewProjectPath = newProjectPath;
}
private static List<String> getAvailableOptions(boolean showAll) {
@@ -114,7 +115,13 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
private void createLocalSdk() {
final Project project = myNewProjectPath != null ? null : myProject;
final PyAddSdkDialog dialog = new PyAddSdkDialog(project, Arrays.asList(myExistingSdks), myNewProjectPath);
final Sdk sdk = dialog.showAndGet() ? dialog.getOrCreateSdk() : null;
final Sdk sdk;
if (dialog.showAndGet()) {
sdk = dialog.getOrCreateSdk();
}
else {
sdk = null;
}
mySdkAddedCallback.consume(sdk);
}
@@ -224,7 +224,7 @@ public final class PythonSdkType extends SdkType {
}
public static boolean isInvalid(@NotNull Sdk sdk) {
if (isRemote(sdk)) {
if (isRemote(sdk) || sdk instanceof PyLazySdk) {
return false;
}
final VirtualFile interpreter = sdk.getHomeDirectory();
@@ -29,19 +29,17 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.NullableComputable
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBCheckBox
import com.intellij.util.PathUtil
import com.intellij.util.SystemProperties
import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.packaging.PyPackageManager
import com.jetbrains.python.packaging.PyPackageService
import com.jetbrains.python.sdk.associateWithProject
import com.jetbrains.python.sdk.createSdkByGenerateTask
import com.jetbrains.python.sdk.findBaseSdks
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
import icons.PythonIcons
import org.jetbrains.annotations.SystemDependent
@@ -50,24 +48,17 @@ import org.jetbrains.jps.model.serialization.PathMacroUtil
import java.awt.BorderLayout
import java.io.File
import javax.swing.Icon
import javax.swing.event.DocumentEvent
/**
* @author vlan
*/
class PyAddNewVirtualEnvPanel(private val project: Project?,
private val existingSdks: List<Sdk>,
newProjectPath: String?) : PyAddSdkPanel() {
private val newProjectPath: String?) : PyAddSdkPanel() {
companion object {
private const val VIRTUALENV_ROOT_DIR_MACRO_NAME = "VIRTUALENV_ROOT_DIR"
}
var newProjectPath: String? = newProjectPath
set(value) {
field = value
pathField.text = defaultBasePath
}
val path: String
get() = pathField.text.trim()
@@ -101,6 +92,10 @@ class PyAddNewVirtualEnvPanel(private val project: Project?,
.filterNotNull()
override fun getOrCreateSdk(): Sdk? {
return if (newProjectPath != null) sdk else createSdk()
}
private fun createSdk(): Sdk? {
val root = pathField.text
val task = object : Task.WithResult<String, ExecutionException>(project, "Creating Virtual Environment", false) {
override fun compute(indicator: ProgressIndicator): String {
@@ -121,14 +116,15 @@ class PyAddNewVirtualEnvPanel(private val project: Project?,
return sdk
}
fun addChangeListener(listener: Runnable) {
pathField.textField.document.addDocumentListener(object: DocumentAdapter() {
override fun textChanged(e: DocumentEvent?) {
listener.run()
override val sdk: Sdk?
get() {
val baseName = baseSdkField.selectedSdk?.homePath?.let { PythonSdkType.suggestBaseSdkName(it) }
val title = when {
baseName != null -> "New ${baseName} virtual environment"
else -> "New virtual environment"
}
})
baseSdkField.childComponent.addItemListener { listener.run() }
}
return PyLazySdk(title, PythonIcons.Python.Virtualenv, NullableComputable { createSdk() })
}
private fun excludeDirectoryFromProject(path: String, project: Project?) {
val currentProject = project ?: findProjectFromFocus() ?: return
@@ -24,7 +24,6 @@ import com.intellij.openapi.ui.popup.ListItemDescriptorAdapter
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.components.JBList
import com.intellij.ui.popup.list.GroupedItemsListRenderer
import com.intellij.util.PlatformUtils
import com.intellij.util.ui.JBUI
import com.jetbrains.python.sdk.PreferredSdkComparator
import com.jetbrains.python.sdk.PythonSdkType
@@ -101,18 +100,13 @@ class PyAddSdkDialog(private val project: Project?,
private fun createVirtualEnvPanel(project: Project?,
existingSdks: List<Sdk>,
newProjectPath: String?): PyAddSdkPanel {
val newVirtualEnvPanel = if (project != null || PlatformUtils.isPyCharmEducational())
PyAddNewVirtualEnvPanel(project, existingSdks, newProjectPath)
else
null
val newVirtualEnvPanel = PyAddNewVirtualEnvPanel(project, existingSdks, newProjectPath)
val existingVirtualEnvPanel = PyAddExistingVirtualEnvPanel(project, existingSdks, newProjectPath)
val panels = listOf(newVirtualEnvPanel,
existingVirtualEnvPanel)
.filterNotNull()
val defaultPanel = when {
detectVirtualEnvs(project, existingSdks).any { it.isAssociatedWithProject(project) } -> existingVirtualEnvPanel
newVirtualEnvPanel != null -> newVirtualEnvPanel
else -> existingVirtualEnvPanel
else -> newVirtualEnvPanel
}
return PyAddSdkGroupPanel("Virtual environment", PythonIcons.Python.Virtualenv, panels, defaultPanel)
}
@@ -124,7 +124,7 @@ public abstract class PythonSdkFlavor {
}
@Nullable
public static PythonSdkFlavor getFlavor(Sdk sdk) {
public static PythonSdkFlavor getFlavor(@NotNull Sdk sdk) {
final SdkAdditionalData data = sdk.getSdkAdditionalData();
if (data instanceof PythonSdkAdditionalData) {
PythonSdkFlavor flavor = ((PythonSdkAdditionalData)data).getFlavor();