PY-49929 Packages(fix): Allow selecting multiple packages simultaneously for a quick uninstall \ upgrade

GitOrigin-RevId: df8b02713955006061411b92cb89f40dc25046be
This commit is contained in:
Nikita.Ashihmin
2024-08-24 20:36:33 +04:00
committed by intellij-monorepo-bot
parent 46871c21ac
commit e66c52bd22
16 changed files with 190 additions and 123 deletions

View File

@@ -865,9 +865,10 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
<group id="PyPackageToolwindowContext">
<action id="PyInstallPackage" class="com.jetbrains.python.packaging.toolwindow.actions.InstallPackageAction"/>
<action id="PyInstallWithOptionPackage" class="com.jetbrains.python.packaging.toolwindow.actions.InstallWithOptionsPackageAction"/>
<action id="PyDeletePackage" class="com.jetbrains.python.packaging.toolwindow.actions.DeletePackageAction"/>
<action id="PyChangeVersionPackage" class="com.jetbrains.python.packaging.toolwindow.actions.ChangeVersionPackageAction"/>
<action id="PyUpdateToLatestPackage" class="com.jetbrains.python.packaging.toolwindow.actions.UpdatePackageToLatestAction"/>
<separator/>
<action id="PyDeletePackage" class="com.jetbrains.python.packaging.toolwindow.actions.DeletePackageAction"/>
</group>
</actions>

View File

@@ -1529,7 +1529,7 @@ advertiser.code.cells.supported.by.pro=Code cells are supported by PyCharm Profe
action.PyInstallPackage.text=Install
action.PyDeletePackage.text=Uninstall
action.PyUpdateToLatestPackage.text=Update to Latest
action.PyChangeVersionPackage.text=Install Specific Version
action.PyChangeVersionPackage.text=Change Version
action.PyInstallWithOptionPackage.text=Install With Options
python.toolwindow.packages.deleting.text=Uninstalling\u2026
progress.text.installing=Installing\u2026

View File

@@ -0,0 +1,6 @@
// 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.repository
import com.jetbrains.python.PyBundle
class InstalledPyPackagedRepository : PyPackageRepository(PyBundle.message("python.toolwindow.packages.installed.label"), "", "") {}

View File

@@ -5,12 +5,12 @@ import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.ui.JBColor
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.packaging.repository.InstalledPyPackagedRepository
import com.jetbrains.python.packaging.repository.PyPackageRepository
import com.jetbrains.python.packaging.toolwindow.model.*
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTable
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTableModel
import com.jetbrains.python.packaging.toolwindow.packages.PyPackagingTableGroup
import com.jetbrains.python.packaging.toolwindow.packages.table.PyPackagesTable
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagingTableGroup
import java.awt.Rectangle
import javax.swing.JLabel
import javax.swing.JPanel
@@ -19,11 +19,12 @@ import javax.swing.JTable
class PyPackagingTablesView(private val project: Project,
private val container: JPanel,
private val controller: PyPackagingToolWindowPanel) {
private val repositories: MutableList<PyPackagingTableGroup<DisplayablePackage>> = mutableListOf()
private val repositories: MutableList<PyPackagingTableGroup> = mutableListOf()
private val installedPackages = PyPackagingTableGroup(
object : PyPackageRepository(message("python.toolwindow.packages.installed.label"), "", "") {},
PyPackagesTable(project, PyPackagesTableModel(), this, controller))
InstalledPyPackagedRepository(),
PyPackagesTable(project, this, controller))
private val invalidRepositories: MutableMap<String, JPanel> = mutableMapOf()
init {
installedPackages.addTo(container)
installedPackages.expand()
@@ -31,8 +32,10 @@ class PyPackagingTablesView(private val project: Project,
fun showSearchResult(installed: List<InstalledPackage>, repoData: List<PyPackagesViewData>) {
updatePackages(installed, repoData)
installedPackages.expand()
installedPackages.updateHeaderText(installed.size)
val tableToData = repositories.map { repo -> repo to repoData.find { it.repository.name == repo.name }!! }
tableToData.forEach { (table, data) ->
table.updateHeaderText(data.packages.size + data.moreItems)
@@ -47,17 +50,21 @@ class PyPackagingTablesView(private val project: Project,
fun resetSearch(installed: List<InstalledPackage>, repoData: List<PyPackagesViewData>) {
updatePackages(installed, repoData)
installedPackages.expand()
installedPackages.updateHeaderText(null)
repositories.forEach {
it.collapse()
it.updateHeaderText(null)
}
container.scrollRectToVisible(Rectangle(0, 0))
}
private fun updatePackages(installed: List<InstalledPackage>, repoData: List<PyPackagesViewData>) {
installedPackages.table.items = installed
val (validRepoData, invalid) = repoData.partition { it !is PyInvalidRepositoryViewData }
for (data in validRepoData) {
@@ -73,7 +80,7 @@ class PyPackagingTablesView(private val project: Project,
selectedItem?.let { existingRepo.table.selectPackage(it) }
}
else {
val newTable = PyPackagesTable(project, PyPackagesTableModel(), this, controller)
val newTable = PyPackagesTable(project, this, controller)
newTable.items = withExpander
val newTableGroup = PyPackagingTableGroup(data.repository, newTable)
@@ -129,7 +136,7 @@ class PyPackagingTablesView(private val project: Project,
tableWithMatch.setRowSelectionInterval(exactMatch, exactMatch)
}
fun requestSelection(table: JTable) {
fun removeSelectionNotFormTable(table: JTable) {
if (table != installedPackages.table) installedPackages.table.clearSelection()
repositories.asSequence()
.filter { it.table != table }
@@ -178,4 +185,9 @@ class PyPackagingTablesView(private val project: Project,
}
}
}
fun getSelectedPackages(): List<DisplayablePackage> {
val repos = listOf(installedPackages) + repositories
return repos.flatMap { it.table.selectedItems() }
}
}

View File

@@ -137,6 +137,7 @@ class PyPackagingToolWindowPanel(private val project: Project) : SimpleToolWindo
override fun uiDataSnapshot(sink: DataSink) {
sink[PyPackagesUiComponents.SELECTED_PACKAGE_DATA_CONTEXT] = descriptionController.selectedPackage.get()
sink[PyPackagesUiComponents.SELECTED_PACKAGES_DATA_CONTEXT] = this.packageListController.getSelectedPackages()
super.uiDataSnapshot(sink)
}

View File

@@ -10,6 +10,7 @@ import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackages
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -31,8 +32,7 @@ internal class ChangeVersionPackageAction : DumbAwareAction() {
}
override fun update(e: AnActionEvent) {
val pkg = e.selectedPackage as? InstalledPackage
e.presentation.isEnabledAndVisible = pkg != null
e.presentation.isEnabledAndVisible = e.selectedPackage as? InstalledPackage != null && e.selectedPackages.size == 1
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT

View File

@@ -8,12 +8,23 @@ import com.intellij.openapi.project.DumbAwareAction
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackages
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
internal class DeletePackageAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val selectedPackages = e.selectedPackages.filterIsInstance<InstalledPackage>()
if (selectedPackages.size > 1) {
PyPackageCoroutine.launch(project, Dispatchers.IO) {
selectedPackages.forEach { pkg ->
project.service<PyPackagingToolWindowService>().deletePackage(pkg)
}
}
return
}
val pkg = e.selectedPackage as? InstalledPackage ?: return
val service = project.service<PyPackagingToolWindowService>()
@@ -23,7 +34,7 @@ internal class DeletePackageAction : DumbAwareAction() {
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.selectedPackage is InstalledPackage
e.presentation.isEnabledAndVisible = e.selectedPackages.all { it is InstalledPackage }
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT

View File

@@ -3,12 +3,14 @@ package com.jetbrains.python.packaging.toolwindow.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.ui.awt.RelativePoint
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackages
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -17,9 +19,20 @@ import java.awt.event.MouseEvent
internal class InstallPackageAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val selectedPackages = e.selectedPackages.filterIsInstance<InstallablePackage>()
if (selectedPackages.size > 1) {
PyPackageCoroutine.launch(project, Dispatchers.IO) {
selectedPackages.forEach { pkg ->
val specification = pkg.repository.createPackageSpecification(pkg.name, null)
project.service<PyPackagingToolWindowService>().installPackage(specification)
}
}
return
}
val pkg = e.selectedPackage as? InstallablePackage ?: return
PyPackageCoroutine.launch(project, Dispatchers.IO) {
val service = PyPackagingToolWindowService.getInstance(project)
val details = service.detailsForPackage(pkg)
@@ -32,7 +45,7 @@ internal class InstallPackageAction : DumbAwareAction() {
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.selectedPackage is InstallablePackage
e.presentation.isEnabledAndVisible = e.selectedPackages.all { it is InstallablePackage }
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT

View File

@@ -13,6 +13,7 @@ import com.jetbrains.python.packaging.common.PythonPackageDetails
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents.selectedPackages
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -32,7 +33,7 @@ internal class InstallWithOptionsPackageAction : DumbAwareAction() {
}
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.selectedPackage is InstallablePackage
e.presentation.isEnabledAndVisible = e.selectedPackage as? InstallablePackage != null && e.selectedPackages.size == 1
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT

View File

@@ -8,6 +8,7 @@ import com.intellij.ui.ScrollPaneFactory
import com.intellij.util.ui.UIUtil
import com.jetbrains.python.packaging.toolwindow.PyPackagingTablesView
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowPanel
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 javax.swing.BoxLayout
@@ -37,4 +38,8 @@ class PyPackagesListController(val project: Project, val controller: PyPackaging
fun selectPackage(name: String) {
tablesView.selectPackage(name)
}
fun getSelectedPackages(): List<DisplayablePackage> {
return tablesView.getSelectedPackages()
}
}

View File

@@ -1,15 +1,17 @@
// 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
package com.jetbrains.python.packaging.toolwindow.packages
import com.intellij.icons.AllIcons
import com.intellij.openapi.util.NlsSafe
import com.jetbrains.python.PyBundle
import com.jetbrains.python.packaging.repository.PyPackageRepository
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
import com.jetbrains.python.packaging.toolwindow.packages.table.PyPackagesTable
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import javax.swing.JLabel
import javax.swing.JPanel
internal class PyPackagingTableGroup<T : DisplayablePackage>(val repository: PyPackageRepository, val table: PyPackagesTable<T>) {
internal class PyPackagingTableGroup(val repository: PyPackageRepository, val table: PyPackagesTable) {
@NlsSafe
val name: String = repository.name!!
@@ -19,7 +21,7 @@ internal class PyPackagingTableGroup<T : DisplayablePackage>(val repository: PyP
private var itemsCount: Int? = null
internal var items: List<T>
internal var items: List<DisplayablePackage>
get() = table.items
set(value) {
table.items = value
@@ -47,11 +49,6 @@ internal class PyPackagingTableGroup<T : DisplayablePackage>(val repository: PyP
panel.add(table)
}
fun replace(row: Int, pkg: T) {
table.removeRow(row)
table.insertRow(row, pkg)
}
fun removeFrom(panel: JPanel) {
panel.remove(header)
panel.remove(table)

View File

@@ -1,5 +1,5 @@
// 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
package com.jetbrains.python.packaging.toolwindow.packages
import com.intellij.openapi.util.NlsSafe
import com.intellij.ui.hover.TableHoverListener
@@ -10,6 +10,8 @@ import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
import com.jetbrains.python.packaging.toolwindow.model.ExpandResultNode
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
import com.jetbrains.python.packaging.toolwindow.packages.table.PyPackagesTable
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import java.awt.Component
import java.awt.font.TextAttribute
import javax.swing.BoxLayout
@@ -20,7 +22,9 @@ import javax.swing.table.DefaultTableCellRenderer
internal class PyPaginationAwareRenderer : DefaultTableCellRenderer() {
private val nameLabel = JLabel().apply { border = JBUI.Borders.empty(0, 12) }
private val versionLabel = JLabel().apply { border = JBUI.Borders.emptyRight(12) }
private val linkLabel = JLabel(PyBundle.message("action.python.packages.install.text")).apply {
border = JBUI.Borders.emptyRight(12)
foreground = JBUI.CurrentTheme.Link.Foreground.ENABLED
@@ -45,7 +49,7 @@ internal class PyPaginationAwareRenderer : DefaultTableCellRenderer() {
row: Int,
column: Int,
): Component {
val rowSelected = table.selectedRow == row
val rowSelected = row in table.selectedRows
val tableFocused = table.hasFocus()
if (value is ExpandResultNode) {
@@ -101,7 +105,7 @@ internal class PyPaginationAwareRenderer : DefaultTableCellRenderer() {
@Suppress("UNCHECKED_CAST")
private fun JLabel.updateUnderline(table: JTable, currentRow: Int) {
val hoveredRow = TableHoverListener.getHoveredRow(table)
val hoveredColumn = (table as PyPackagesTable<*>).hoveredColumn
val hoveredColumn = (table as PyPackagesTable).hoveredColumn
val underline = if (hoveredRow == currentRow && hoveredColumn == 1) TextAttribute.UNDERLINE_ON else -1
val attributes = font.attributes as MutableMap<TextAttribute, Any>

View File

@@ -0,0 +1,45 @@
// 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.packages.table
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.hover.TableHoverListener
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesUiComponents
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
internal class PyPackageTableMouseAdapter(private val table: PyPackagesTable) : MouseAdapter() {
val project: Project = table.project
val service
get() = project.service<PyPackagingToolWindowService>()
override fun mouseClicked(e: MouseEvent) {
if (e.clickCount != 1 || table.columnAtPoint(e.point) != 1) return // double click or click on package name column, nothing to be done
val hoveredRow = TableHoverListener.getHoveredRow(table)
val selectedPackage = table.items[hoveredRow]
if (selectedPackage is InstallablePackage) {
PyPackageCoroutine.launch(project, Dispatchers.IO) {
val details = service.detailsForPackage(selectedPackage)
withContext(Dispatchers.Main) {
PyPackagesUiComponents.createAvailableVersionsPopup(selectedPackage, details, project).show(RelativePoint(e))
}
}
}
else if (selectedPackage is InstalledPackage && selectedPackage.canBeUpdated) {
PyPackageCoroutine.launch(project, Dispatchers.IO) {
val nextVersion = selectedPackage.nextVersion ?: return@launch
val specification = selectedPackage.repository.createPackageSpecification(selectedPackage.name, nextVersion.presentableText)
project.service<PyPackagingToolWindowService>().updatePackage(specification)
}
}
}
}

View File

@@ -0,0 +1,25 @@
// 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.packages.table
import com.intellij.ui.hover.TableHoverListener
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
import java.awt.Cursor
import javax.swing.JTable
internal class PyPackagesHoverListener(private val pyPackageTable: PyPackagesTable) : TableHoverListener() {
override fun onHover(table: JTable, row: Int, column: Int) {
pyPackageTable.hoveredColumn = column
if (column == 1) {
table.repaint(table.getCellRect(row, column, true))
val currentPackage = pyPackageTable.items[row]
if (currentPackage is InstallablePackage
|| (currentPackage is InstalledPackage && currentPackage.canBeUpdated)) {
table.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
return
}
}
table.cursor = Cursor.getDefaultCursor()
}
}

View File

@@ -1,5 +1,5 @@
// 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
package com.jetbrains.python.packaging.toolwindow.packages.table
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionManager
@@ -8,58 +8,43 @@ import com.intellij.openapi.project.Project
import com.intellij.ui.DoubleClickListener
import com.intellij.ui.PopupHandler
import com.intellij.ui.SideBorder
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.hover.TableHoverListener
import com.intellij.ui.table.JBTable
import com.intellij.util.ui.ListTableModel
import com.intellij.util.ui.NamedColorUtil
import com.jetbrains.python.packaging.toolwindow.PyPackagingTablesView
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowPanel
import com.jetbrains.python.packaging.toolwindow.PyPackagingToolWindowService
import com.jetbrains.python.packaging.toolwindow.model.DisplayablePackage
import com.jetbrains.python.packaging.toolwindow.model.ExpandResultNode
import com.jetbrains.python.packaging.toolwindow.model.InstallablePackage
import com.jetbrains.python.packaging.toolwindow.model.InstalledPackage
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.Cursor
import com.jetbrains.python.packaging.toolwindow.packages.PyPaginationAwareRenderer
import com.jetbrains.python.packaging.toolwindow.ui.PyPackagesTableModel
import java.awt.event.ActionEvent
import java.awt.event.KeyEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.AbstractAction
import javax.swing.JTable
import javax.swing.KeyStroke
import javax.swing.ListSelectionModel
import javax.swing.table.TableCellRenderer
internal class PyPackagesTable<T : DisplayablePackage>(
project: Project,
model: ListTableModel<T>,
internal class PyPackagesTable(
val project: Project,
tablesView: PyPackagingTablesView,
val controller: PyPackagingToolWindowPanel,
) : JBTable(model) {
private val scope = PyPackageCoroutine.getIoScope(project)
private var lastSelectedElement: DisplayablePackage? = null
) : JBTable(PyPackagesTableModel<DisplayablePackage>()) {
internal var hoveredColumn = -1
@Suppress("UNCHECKED_CAST")
private val listModel: ListTableModel<T>
get() = model as ListTableModel<T>
val model: PyPackagesTableModel<DisplayablePackage> = getModel() as PyPackagesTableModel<DisplayablePackage>
var items: List<T>
get() = listModel.items
var items: List<DisplayablePackage>
get() = model.items
set(value) {
listModel.items = value.toMutableList()
model.items = value.toMutableList()
}
init {
val service = project.service<PyPackagingToolWindowService>()
setShowGrid(false)
setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
val column = columnModel.getColumn(1)
column.minWidth = 130
column.maxWidth = 130
@@ -69,51 +54,14 @@ internal class PyPackagesTable<T : DisplayablePackage>(
initCrossNavigation(service, tablesView)
val hoverListener = object : TableHoverListener() {
override fun onHover(table: JTable, row: Int, column: Int) {
hoveredColumn = column
if (column == 1) {
table.repaint(table.getCellRect(row, column, true))
val currentPackage = items[row]
if (currentPackage is InstallablePackage
|| (currentPackage is InstalledPackage && currentPackage.canBeUpdated)) {
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
return
}
}
cursor = Cursor.getDefaultCursor()
}
}
val hoverListener = PyPackagesHoverListener(this)
hoverListener.addTo(this)
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.clickCount != 1 || columnAtPoint(e.point) != 1) return // double click or click on package name column, nothing to be done
val hoveredRow = TableHoverListener.getHoveredRow(this@PyPackagesTable)
val selectedPackage = this@PyPackagesTable.items[hoveredRow]
if (selectedPackage is InstallablePackage) {
scope.launch(Dispatchers.IO) {
val details = service.detailsForPackage(selectedPackage)
withContext(Dispatchers.Main) {
PyPackagesUiComponents.createAvailableVersionsPopup(selectedPackage, details, project).show(RelativePoint(e))
}
}
}
else if (selectedPackage is InstalledPackage && selectedPackage.canBeUpdated) {
scope.launch(Dispatchers.IO) {
val specification = selectedPackage.repository.createPackageSpecification(selectedPackage.name,
selectedPackage.nextVersion!!.presentableText)
project.service<PyPackagingToolWindowService>().updatePackage(specification)
}
}
}
})
addMouseListener(PyPackageTableMouseAdapter(this))
selectionModel.addListSelectionListener {
tablesView.requestSelection(this)
val pkg = model.items.getOrNull(selectedRow)
lastSelectedElement = pkg
tablesView.removeSelectionNotFormTable(this)
val pkg = selectedItem()
if (pkg != null && pkg !is ExpandResultNode) {
controller.packageSelected(pkg)
}
@@ -124,8 +72,9 @@ internal class PyPackagesTable<T : DisplayablePackage>(
object : DoubleClickListener() {
override fun onDoubleClick(event: MouseEvent): Boolean {
val pkg = model.items[selectedRow]
if (pkg is ExpandResultNode) loadMoreItems(service, pkg)
val pkg = selectedItem() ?: return true
if (pkg is ExpandResultNode)
loadMoreItems(service, pkg)
return true
}
}.installOn(this)
@@ -134,8 +83,20 @@ internal class PyPackagesTable<T : DisplayablePackage>(
PopupHandler.installPopupMenu(this, packageActionGroup, "PackagePopup")
}
override fun getCellRenderer(row: Int, column: Int) = PyPaginationAwareRenderer()
fun selectedItem(): T? = items.getOrNull(selectedRow)
fun selectedItem(): DisplayablePackage? = items.getOrNull(selectedRow)
fun selectedItems(): Sequence<DisplayablePackage> {
return selectedRows.asSequence().mapNotNull { items.getOrNull(it) }
}
fun selectPackage(pkg: DisplayablePackage) {
val index = items.indexOf(pkg)
if (index != -1) {
setRowSelectionInterval(index, index)
}
}
private fun initCrossNavigation(service: PyPackagingToolWindowService, tablesView: PyPackagingTablesView) {
getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ENTER_ACTION)
@@ -181,43 +142,20 @@ internal class PyPackagesTable<T : DisplayablePackage>(
})
}
@Suppress("UNCHECKED_CAST")
private fun loadMoreItems(service: PyPackagingToolWindowService, node: ExpandResultNode) {
val result = service.getMoreResultsForRepo(node.repository, items.size - 1)
items = items.dropLast(1) + (result.packages as List<T>)
items = items.dropLast(1) + result.packages
if (result.moreItems > 0) {
node.more = result.moreItems
items = items + listOf(node) as List<T>
items = items + listOf(node)
}
this@PyPackagesTable.revalidate()
this@PyPackagesTable.repaint()
}
override fun getCellRenderer(row: Int, column: Int): TableCellRenderer {
return PyPaginationAwareRenderer()
}
fun selectPackage(pkg: DisplayablePackage) {
val index = items.indexOf(pkg)
if (index != -1) {
setRowSelectionInterval(index, index)
}
}
override fun clearSelection() {
lastSelectedElement = null
super.clearSelection()
}
internal fun removeRow(index: Int) = listModel.removeRow(index)
internal fun insertRow(index: Int, pkg: T) = listModel.insertRow(index, pkg)
companion object {
private const val NEXT_ROW_ACTION = "selectNextRow"
private const val PREVIOUS_ROW_ACTION = "selectPreviousRow"
private const val ENTER_ACTION = "ENTER"
}
}
}

View File

@@ -29,6 +29,7 @@ import javax.swing.*
object PyPackagesUiComponents {
val SELECTED_PACKAGE_DATA_CONTEXT = DataKey.create<DisplayablePackage>("SELECTED_PACKAGE_DATA_CONTEXT")
val SELECTED_PACKAGES_DATA_CONTEXT = DataKey.create<List<DisplayablePackage>>("SELECTED_PACKAGES_DATA_CONTEXT")
private val DataContext.selectedPackage: DisplayablePackage?
get() = getData(SELECTED_PACKAGE_DATA_CONTEXT)
@@ -36,6 +37,13 @@ object PyPackagesUiComponents {
internal val AnActionEvent.selectedPackage: DisplayablePackage?
get() = dataContext.selectedPackage
private val DataContext.selectedPackages: List<DisplayablePackage>
get() = getData(SELECTED_PACKAGES_DATA_CONTEXT) ?: emptyList()
internal val AnActionEvent.selectedPackages: List<DisplayablePackage>
get() = dataContext.selectedPackages
fun createAvailableVersionsPopup(selectedPackage: DisplayablePackage, details: PythonPackageDetails, project: Project): ListPopup {
return JBPopupFactory.getInstance().createListPopup(object : BaseListPopupStep<String>(null, details.availableVersions) {
override fun onChosen(selectedValue: String?, finalChoice: Boolean): PopupStep<*>? {