diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagesToolWindowModuleAttachListener.kt b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagesToolWindowModuleAttachListener.kt index 9112383270c0..6c8d8e712ea7 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagesToolWindowModuleAttachListener.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagesToolWindowModuleAttachListener.kt @@ -12,4 +12,6 @@ class PyPackagesToolWindowModuleAttachListener : ModuleAttachListener { module.project.service().moduleAttached() } } + + } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt index 6b93fbd2103c..fd00cef0205a 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowPanel.kt @@ -9,52 +9,42 @@ import com.intellij.openapi.actionSystem.DataSink import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory -import com.intellij.openapi.fileEditor.FileEditorManagerEvent -import com.intellij.openapi.fileEditor.FileEditorManagerListener -import com.intellij.openapi.module.Module -import com.intellij.openapi.module.ModuleManager -import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.project.modules import com.intellij.openapi.ui.SimpleToolWindowPanel -import com.intellij.openapi.ui.TextFieldWithBrowseButton import com.intellij.openapi.util.Disposer import com.intellij.openapi.wm.ToolWindowManager import com.intellij.openapi.wm.ex.ToolWindowManagerListener import com.intellij.ui.OnePixelSplitter -import com.intellij.ui.ScrollPaneFactory import com.intellij.ui.SearchTextField import com.intellij.ui.SideBorder -import com.intellij.ui.components.* -import com.intellij.ui.dsl.builder.* -import com.intellij.util.ui.JBUI +import com.intellij.ui.components.JBPanelWithEmptyText import com.intellij.util.ui.NamedColorUtil import com.jetbrains.python.PyBundle.message -import com.jetbrains.python.icons.PythonIcons -import com.jetbrains.python.packaging.common.PythonLocalPackageSpecification -import com.jetbrains.python.packaging.common.PythonVcsPackageSpecification import com.jetbrains.python.packaging.toolwindow.details.PyPackageDescriptionController import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage import com.jetbrains.python.packaging.toolwindow.model.PyPackagesViewData +import com.jetbrains.python.packaging.toolwindow.modules.PyPackagesModuleController import com.jetbrains.python.packaging.toolwindow.packages.PyPackageSearchTextField import com.jetbrains.python.packaging.toolwindow.packages.PyPackagesListController +import com.jetbrains.python.packaging.toolwindow.ui.PyPackageCustomInstallComponents import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents import com.jetbrains.python.packaging.utils.PyPackageCoroutine -import com.jetbrains.python.sdk.pythonSdk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.awt.BorderLayout import java.awt.Dimension -import java.awt.FlowLayout -import javax.swing.* -import javax.swing.event.ListSelectionListener +import javax.swing.BorderFactory +import javax.swing.JComponent +import javax.swing.JPanel class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindowPanel(false, true), Disposable { + private val moduleController = PyPackagesModuleController(project) + private val descriptionController = PyPackageDescriptionController(project).also { Disposer.register(this, it) } @@ -64,7 +54,7 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo } - internal val packagingScope = PyPackageCoroutine.getIoScope(project) + private val packagingScope = PyPackageCoroutine.getIoScope(project) private val searchTextField: SearchTextField = PyPackageSearchTextField(project) @@ -83,12 +73,6 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo } - private val fromVcsText: String - get() = message("python.toolwindow.packages.add.package.from.vcs") - private val fromDiscText: String - get() = message("python.toolwindow.packages.add.package.from.disc") - - init { val service = project.service() Disposer.register(service, this) @@ -130,19 +114,6 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo actionToolbar.targetComponent = this - val installFromLocationLink = DropDownLink(message("python.toolwindow.packages.add.package.action"), - listOf(fromVcsText, fromDiscText)) { - val specification = when (it) { - fromDiscText -> showInstallFromDiscDialog(service) - fromVcsText -> showInstallFromVcsDialog(service) - else -> throw IllegalStateException("Unknown operation") - } - if (specification != null) { - packagingScope.launch { - service.installPackage(specification) - } - } - } mainPanel = PyPackagesUiComponents.borderPanel { val topToolbar = PyPackagesUiComponents.boxPanel { @@ -153,7 +124,7 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo add(searchTextField) actionToolbar.component.maximumSize = Dimension(70, actionToolbar.component.maximumSize.height) add(actionToolbar.component) - add(installFromLocationLink) + add(PyPackageCustomInstallComponents.createInstallFromLocationLink(project)) } add(topToolbar, BorderLayout.NORTH) add(splitter!!, BorderLayout.CENTER) @@ -162,121 +133,20 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo } private fun createLeftPanel(service: PyPackagingToolWindowService): JComponent { - if (project.modules.size == 1) return packageListController.component + if (project.modules.size == 1) + return packageListController.component val left = JPanel(BorderLayout()).apply { border = BorderFactory.createEmptyBorder() } - val modulePanel = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)).apply { - border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.RIGHT) - maximumSize = Dimension(80, maximumSize.height) - minimumSize = Dimension(50, minimumSize.height) - } - - val moduleList = JBList(ModuleManager.getInstance(project).modules.toList().sortedBy { it.name }).apply { - selectionMode = ListSelectionModel.SINGLE_SELECTION - border = JBUI.Borders.empty() - - val itemRenderer = JBLabel("", PythonIcons.Python.PythonClosed, JLabel.LEFT).apply { - border = JBUI.Borders.empty(0, 10) - } - cellRenderer = ListCellRenderer { _, value, _, _, _ -> itemRenderer.text = value.name; itemRenderer } - - addListSelectionListener(ListSelectionListener { e -> - if (e.valueIsAdjusting) return@ListSelectionListener - val selectedModule = this@apply.selectedValue - val sdk = selectedModule.pythonSdk ?: return@ListSelectionListener - packagingScope.launch { - service.initForSdk(sdk) - } - }) - } - - val fileListener = object : FileEditorManagerListener { - override fun selectionChanged(event: FileEditorManagerEvent) { - if (project.modules.size > 1) { - val newFile = event.newFile ?: return - val module = ModuleUtilCore.findModuleForFile(newFile, project) - packagingScope.launch { - val index = (moduleList.model as DefaultListModel).indexOf(module) - moduleList.selectionModel.setSelectionInterval(index, index) - } - } - } - } - service.project.messageBus - .connect(service) - .subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, fileListener) - - modulePanel.add(moduleList) - left.add(ScrollPaneFactory.createScrollPane(modulePanel, true), BorderLayout.WEST) + left.add(moduleController.component, BorderLayout.WEST) left.add(packageListController.component, BorderLayout.CENTER) return left } - private fun showInstallFromVcsDialog(service: PyPackagingToolWindowService): PythonVcsPackageSpecification? { - var editable = false - var link = "" - val systems = listOf(message("python.toolwindow.packages.add.package.vcs.git"), - message("python.toolwindow.packages.add.package.vcs.svn"), - message("python.toolwindow.packages.add.package.vcs.hg"), - message("python.toolwindow.packages.add.package.vcs.bzr")) - var vcs = systems.first() - val panel = panel { - row { - comboBox(systems) - .bindItem({ vcs }, { vcs = it!! }) - textField() - .columns(COLUMNS_MEDIUM) - .bindText({ link }, { link = it }) - .align(AlignX.FILL) - } - row { - checkBox(message("python.toolwindow.packages.add.package.as.editable")) - .bindSelected({ editable }, { editable = it }) - } - } - - val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet() - if (shouldInstall) { - val prefix = when (vcs) { - message("python.toolwindow.packages.add.package.vcs.git") -> "git+" - message("python.toolwindow.packages.add.package.vcs.svn") -> "svn+" - message("python.toolwindow.packages.add.package.vcs.hg") -> "hg+" - message("python.toolwindow.packages.add.package.vcs.bzr") -> "bzr+" - else -> throw IllegalStateException("Unknown VCS") - } - - return PythonVcsPackageSpecification(link, link, prefix, editable) - } - return null - } - - private fun showInstallFromDiscDialog(service: PyPackagingToolWindowService): PythonLocalPackageSpecification? { - var editable = false - - val textField = TextFieldWithBrowseButton().apply { - addBrowseFolderListener(message("python.toolwindow.packages.add.package.path.selector"), "", service.project, - FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor()) - } - val panel = panel { - row(message("python.toolwindow.packages.add.package.path")) { - cell(textField) - .columns(COLUMNS_MEDIUM) - .align(AlignX.FILL) - } - row { - checkBox(message("python.toolwindow.packages.add.package.as.editable")) - .bindSelected({ editable }, { editable = it }) - } - } - - val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet() - return if (shouldInstall) PythonLocalPackageSpecification(textField.text, textField.text, editable) else null - } private fun trackOrientation(service: PyPackagingToolWindowService) { service.project.messageBus.connect(service).subscribe(ToolWindowManagerListener.TOPIC, object : ToolWindowManagerListener { @@ -347,9 +217,6 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo companion object { private const val HORIZONTAL_SPLITTER_KEY = "Python.PackagingToolWindow.Horizontal" private const val VERTICAL_SPLITTER_KEY = "Python.PackagingToolWindow.Vertical" - - val NO_DESCRIPTION: String - get() = message("python.toolwindow.packages.no.description.placeholder") } } diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDetailsHtmlRender.kt b/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDetailsHtmlRender.kt index d971d1089817..382c78e27a58 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDetailsHtmlRender.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackageDetailsHtmlRender.kt @@ -15,7 +15,6 @@ import com.jetbrains.python.PythonHelper import com.jetbrains.python.PythonHelpersLocator import com.jetbrains.python.packaging.PyPIPackageRanking import com.jetbrains.python.packaging.common.PythonPackageDetails -import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowPanel.Companion.NO_DESCRIPTION import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory import com.jetbrains.python.run.applyHelperPackageToPythonPath import com.jetbrains.python.run.buildTargetedCommandLine @@ -108,4 +107,8 @@ class PyPackageDetailsHtmlRender(val project: Project, val currentSdk: Sdk?) { } private fun wrapHtml(html: String): String = "

$html

" + + private val NO_DESCRIPTION: String + get() = message("python.toolwindow.packages.no.description.placeholder") + } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesModuleController.kt b/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesModuleController.kt new file mode 100644 index 000000000000..32a784f8cb0e --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/toolwindow/modules/PyPackagesModuleController.kt @@ -0,0 +1,81 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.packaging.toolwindow.modules + +import com.intellij.openapi.components.service +import com.intellij.openapi.fileEditor.FileEditorManagerEvent +import com.intellij.openapi.fileEditor.FileEditorManagerListener +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.modules +import com.intellij.ui.ScrollPaneFactory +import com.intellij.ui.SideBorder +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBList +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.NamedColorUtil +import com.jetbrains.python.icons.PythonIcons +import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService +import com.jetbrains.python.packaging.utils.PyPackageCoroutine +import com.jetbrains.python.sdk.pythonSdk +import kotlinx.coroutines.launch +import java.awt.Dimension +import java.awt.FlowLayout +import javax.swing.* +import javax.swing.event.ListSelectionListener + +class PyPackagesModuleController(val project: Project) { + val packagingScope = PyPackageCoroutine.getIoScope(project) + val service + get() = project.service() + + val moduleList = JBList(ModuleManager.getInstance(project).modules.toList().sortedBy { it.name }).apply { + selectionMode = ListSelectionModel.SINGLE_SELECTION + border = JBUI.Borders.empty() + + val itemRenderer = JBLabel("", PythonIcons.Python.PythonClosed, JLabel.LEFT).apply { + border = JBUI.Borders.empty(0, 10) + } + cellRenderer = ListCellRenderer { _, value, _, _, _ -> itemRenderer.text = value.name; itemRenderer } + + addListSelectionListener(ListSelectionListener { e -> + if (e.valueIsAdjusting) return@ListSelectionListener + val selectedModule = this@apply.selectedValue + val sdk = selectedModule.pythonSdk ?: return@ListSelectionListener + packagingScope.launch { + service.initForSdk(sdk) + } + }) + } + + + private val modulePanel = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)).apply { + border = SideBorder(NamedColorUtil.getBoundsColor(), SideBorder.RIGHT) + maximumSize = Dimension(80, maximumSize.height) + minimumSize = Dimension(50, minimumSize.height) + }.also { + it.add(moduleList) + } + + val component = ScrollPaneFactory.createScrollPane(modulePanel, true) + + val fileListener = object : FileEditorManagerListener { + override fun selectionChanged(event: FileEditorManagerEvent) { + if (project.modules.size > 1) { + val newFile = event.newFile ?: return + val module = ModuleUtilCore.findModuleForFile(newFile, project) + packagingScope.launch { + val index = (moduleList.model as DefaultListModel).indexOf(module) + moduleList.selectionModel.setSelectionInterval(index, index) + } + } + } + } + + init { + project.messageBus + .connect(service) + .subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, fileListener) + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/ui/PyPackageCustomInstallComponents.kt b/python/src/com/jetbrains/python/packaging/toolwindow/ui/PyPackageCustomInstallComponents.kt new file mode 100644 index 000000000000..c44121a49e71 --- /dev/null +++ b/python/src/com/jetbrains/python/packaging/toolwindow/ui/PyPackageCustomInstallComponents.kt @@ -0,0 +1,104 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.packaging.toolwindow.ui + +import com.intellij.openapi.components.service +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.TextFieldWithBrowseButton +import com.intellij.ui.components.DropDownLink +import com.intellij.ui.components.dialog +import com.intellij.ui.dsl.builder.* +import com.jetbrains.python.PyBundle.message +import com.jetbrains.python.packaging.common.PythonLocalPackageSpecification +import com.jetbrains.python.packaging.common.PythonVcsPackageSpecification +import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService +import com.jetbrains.python.packaging.utils.PyPackageCoroutine +import org.jetbrains.annotations.Nls + +object PyPackageCustomInstallComponents { + private val fromVcsText: String + get() = message("python.toolwindow.packages.add.package.from.vcs") + private val fromDiscText: String + get() = message("python.toolwindow.packages.add.package.from.disc") + + fun createInstallFromLocationLink(project: Project): DropDownLink<@Nls String> { + return DropDownLink(message("python.toolwindow.packages.add.package.action"), + listOf(fromVcsText, fromDiscText)) { + val service = project.service() + + val specification = when (it) { + fromDiscText -> showInstallFromDiscDialog(service) + fromVcsText -> showInstallFromVcsDialog(service) + else -> throw IllegalStateException("Unknown operation") + } + if (specification != null) { + PyPackageCoroutine.launch(project) { + service.installPackage(specification) + } + } + } + + } + + private fun showInstallFromVcsDialog(service: PyPackagingToolWindowService): PythonVcsPackageSpecification? { + var editable = false + var link = "" + val systems = listOf(message("python.toolwindow.packages.add.package.vcs.git"), + message("python.toolwindow.packages.add.package.vcs.svn"), + message("python.toolwindow.packages.add.package.vcs.hg"), + message("python.toolwindow.packages.add.package.vcs.bzr")) + var vcs = systems.first() + + val panel = panel { + row { + comboBox(systems) + .bindItem({ vcs }, { vcs = it!! }) + textField() + .columns(COLUMNS_MEDIUM) + .bindText({ link }, { link = it }) + .align(AlignX.FILL) + } + row { + checkBox(message("python.toolwindow.packages.add.package.as.editable")) + .bindSelected({ editable }, { editable = it }) + } + } + + val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet() + if (shouldInstall) { + val prefix = when (vcs) { + message("python.toolwindow.packages.add.package.vcs.git") -> "git+" + message("python.toolwindow.packages.add.package.vcs.svn") -> "svn+" + message("python.toolwindow.packages.add.package.vcs.hg") -> "hg+" + message("python.toolwindow.packages.add.package.vcs.bzr") -> "bzr+" + else -> throw IllegalStateException("Unknown VCS") + } + + return PythonVcsPackageSpecification(link, link, prefix, editable) + } + return null + } + + private fun showInstallFromDiscDialog(service: PyPackagingToolWindowService): PythonLocalPackageSpecification? { + var editable = false + + val textField = TextFieldWithBrowseButton().apply { + addBrowseFolderListener(message("python.toolwindow.packages.add.package.path.selector"), "", service.project, + FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor()) + } + val panel = panel { + row(message("python.toolwindow.packages.add.package.path")) { + cell(textField) + .columns(COLUMNS_MEDIUM) + .align(AlignX.FILL) + } + row { + checkBox(message("python.toolwindow.packages.add.package.as.editable")) + .bindSelected({ editable }, { editable = it }) + } + } + + val shouldInstall = dialog(message("python.toolwindow.packages.add.package.dialog.title"), panel, project = service.project, resizable = true).showAndGet() + return if (shouldInstall) PythonLocalPackageSpecification(textField.text, textField.text, editable) else null + } +} \ No newline at end of file