Fri, 28 Jun 2024 16:25:21 +0200
Added functionality to upgrade single or all pipx managed packages and to manage the standalone interpreters.
--- a/PipxInterface/Pipx.py Thu Jun 27 17:54:24 2024 +0200 +++ b/PipxInterface/Pipx.py Fri Jun 28 16:25:21 2024 +0200 @@ -11,12 +11,12 @@ import json import os import pathlib -##import sys import sysconfig from PyQt6.QtCore import QObject, QProcess from eric7 import Preferences +from eric7.EricWidgets import EricMessageBox from eric7.SystemUtilities import OSUtilities from .PipxExecDialog import PipxExecDialog @@ -166,6 +166,43 @@ return jsonDict ############################################################################ + ## pipx interpreter list function (modified from original to work here) + ############################################################################ + + def getPipxInterpretersList(self): + """ + Public method returning a list all standalone interpreters. + + @return dictionary containing data of standalone interpreters + @rtype dict + """ + from pipx.commands.interpreter import ( + get_installed_standalone_interpreters, + get_venvs_using_standalone_interpreter, + get_interpreter_users, + ) + from pipx.paths import ctx + from pipx.venv import VenvContainer + + interpreters = get_installed_standalone_interpreters() + venvs = get_venvs_using_standalone_interpreter(VenvContainer(ctx.venvs)) + + interpretersDict = { + "path": str(ctx.standalone_python_cachedir), + "interpreters": {}, + } + for interpreter in interpreters: + usedBy = get_interpreter_users(interpreter, venvs) + interpretersDict["interpreters"][interpreter.name] = { + "used": bool(usedBy), + "used_by": [ + (p.main_package.package, p.main_package.package_version) + for p in usedBy + ], + } + return interpretersDict + + ############################################################################ ## Command methods ############################################################################ @@ -176,6 +213,8 @@ @return list of dictionaries containing the installed packages and apps @rtype list of dict[str, str | list] """ + from pipx.paths import ctx + packages = [] ok, output = self.runPipxProcess(["list", "--json"]) @@ -190,6 +229,13 @@ "version": metadata["main_package"]["package_version"], "apps": [], "python": metadata["python_version"], + "is_standalone": ( + str(metadata["source_interpreter"]).startswith( + str(ctx.standalone_python_cachedir.resolve()) + ) + if metadata["source_interpreter"] + else False + ), } for appPath in metadata["main_package"]["app_paths"]: package["apps"].append((appPath.name, str(appPath))) @@ -203,7 +249,7 @@ interpreterVersion="", fetchMissingInterpreter=False, forceVenvModification=False, - systemSitePackages=False + systemSitePackages=False, ): """ Public method to install a list of packages with the given options. @@ -247,7 +293,7 @@ interpreterVersion="", fetchMissingInterpreter=False, forceVenvModification=False, - systemSitePackages=False + systemSitePackages=False, ): """ Public method to install all packages define by a given spec metadata file @@ -391,3 +437,100 @@ res = dia.startProcess(self.__getPipxExecutable(), args) if res: dia.exec() + + def checkPackageOutdated(self, package): + """ + Public method to check, if a given package is outdated. + + @param package name of the package + @type str + @return latest version in case the package is outdated and None otherwise + @rtype str or None + """ + args = ["runpip", package, "list", "--outdated", "--format", "json"] + if Preferences.getPip("PipSearchIndex"): + indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" + args += ["--index-url", indexUrl] + ok, output = self.runPipxProcess(args) + if not ok: + EricMessageBox.information( + None, + self.tr("Check Outdated Package"), + self.tr( + "<p>The status of package <b>{0}</b> could not be determined.</p>" + "<p>Reason: {1}</p>" + ).format(package, output), + ) + return None + + outdatedList = json.loads(output) + # check if the main package is in the list + for outdatedPackage in outdatedList: + if outdatedPackage["name"] == package: + return outdatedPackage["latest_version"] + + return None + + def upgradePackage(self, package): + """ + Public method to upgrade the given package. + + @param package name of the package + @type str + """ + args = ["upgrade"] + if Preferences.getPip("PipSearchIndex"): + indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" + args += ["--index-url", indexUrl] + args.append(package) + dia = PipxExecDialog(self.tr("Upgrade Package")) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec() + + def upgradeAllPackages(self): + """ + Public method to upgrade all package. + """ + args = ["upgrade-all"] + dia = PipxExecDialog(self.tr("Upgrade All Packages")) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec() + + def upgradeSharedLibraries(self): + """ + Public method to upgrade shared libraries. + """ + args = ["upgrade-shared"] + dia = PipxExecDialog(self.tr("Upgrade Shared Libraries")) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec() + + def upgradeInterpreters(self, dialogParent=None): + """ + Public method to upgrade the installed interpreters to the latest available + micro/patch version + + @param dialogParent parent widget of the execution dialog + @type QWidget + """ + args = ["interpreter", "upgrade"] + dia = PipxExecDialog(self.tr("Upgrade Interpreters"), parent=dialogParent) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec() + + def pruneInterpreters(self, dialogParent=None): + """ + Public method to prune unused interpreters. + + @param dialogParent parent widget of the execution dialog + @type QWidget + """ + args = ["interpreter", "prune"] + dia = PipxExecDialog(self.tr("Prune Unused Interpreters"), parent=dialogParent) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxInterpretersDialog.py Fri Jun 28 16:25:21 2024 +0200 @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show the available standalone Python interpreters. +""" + +from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QDialog, QTreeWidgetItem + +from eric7.EricGui import EricPixmapCache + +from .Ui_PipxInterpretersDialog import Ui_PipxInterpretersDialog + + +class PipxInterpretersDialog(QDialog, Ui_PipxInterpretersDialog): + """ + Class implementing a dialog to show the available standalone Python interpreters. + """ + + def __init__(self, pipx, parent=None): + """ + Constructor + + @param pipx reference to the pipx interface + @type Pipx + @param parent reference to the parent widget (defaults to None) + @type QWidget (optional) + """ + super().__init__(parent) + self.setupUi(self) + + self.refreshButton.setIcon(EricPixmapCache.getIcon("reload")) + self.upgradeButton.setIcon(EricPixmapCache.getIcon("upgrade")) + self.pruneButton.setIcon(EricPixmapCache.getIcon("clear")) + + self.__pipx = pipx + + self.refreshButton.clicked.connect(self.__populateInterpretersList) + + self.__populateInterpretersList() + + @pyqtSlot() + def __populateInterpretersList(self): + """ + Private slot to populate the list of standalone Python interpreters. + """ + self.interpretersList.clear() + + interpreters = self.__pipx.getPipxInterpretersList()["interpreters"] + for interpreter in interpreters: + pyItem = QTreeWidgetItem( + self.interpretersList, + [ + self.tr("Python {0}{1}").format( + interpreter, + "" + if interpreters[interpreter]["used"] + else self.tr(" (unused)"), + ) + ] + ) + for package, packageVersion in interpreters[interpreter]["used_by"]: + QTreeWidgetItem( + pyItem, + [ + self.tr("{0} {1}", "package, version") + .format(package, packageVersion) + ] + ) + pyItem.setExpanded(True) + + @pyqtSlot() + def on_upgradeButton_clicked(self): + """ + Private slot to upgrade all pipx managed interpreters to the latest available + micro/patch version. + """ + self.__pipx.upgradeInterpreters(dialogParent=self) + self.__populateInterpretersList() + + @pyqtSlot() + def on_pruneButton_clicked(self): + """ + Private slot to prune unused interpreters. + """ + self.__pipx.pruneInterpreters(dialogParent=self) + self.__populateInterpretersList()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxInterpretersDialog.ui Fri Jun 28 16:25:21 2024 +0200 @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PipxInterpretersDialog</class> + <widget class="QDialog" name="PipxInterpretersDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>450</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Standalone Python Interpreters</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="refreshButton"> + <property name="toolTip"> + <string>Press to refresh the list of interpreters.</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="upgradeButton"> + <property name="toolTip"> + <string>Press to upgrade installed interpreters to the latest micro version.</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="pruneButton"> + <property name="toolTip"> + <string>Press to prune all unused interpreters.</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QTreeWidget" name="interpretersList"> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + <column> + <property name="text"> + <string>Interpreter/Package</string> + </property> + </column> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>refreshButton</tabstop> + <tabstop>upgradeButton</tabstop> + <tabstop>pruneButton</tabstop> + <tabstop>interpretersList</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PipxInterpretersDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>PipxInterpretersDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- a/PipxInterface/PipxWidget.py Thu Jun 27 17:54:24 2024 +0200 +++ b/PipxInterface/PipxWidget.py Fri Jun 28 16:25:21 2024 +0200 @@ -13,6 +13,7 @@ from PyQt6.QtWidgets import QDialog, QMenu, QTreeWidgetItem, QWidget from eric7.EricGui import EricPixmapCache +from eric7.EricGui.EricOverrideCursor import EricOverrideCursor from eric7.EricWidgets import EricFileDialog, EricMessageBox from .Pipx import Pipx @@ -31,6 +32,8 @@ PythonVersionColumn = 2 AppPathRole = Qt.ItemDataRole.UserRole + VersionRole = Qt.ItemDataRole.UserRole + 1 + LatestVersionRole = Qt.ItemDataRole.UserRole + 2 def __init__(self, plugin, fromEric=True, parent=None): """ @@ -59,10 +62,12 @@ self.pipxMenuButton.setIcon(EricPixmapCache.getIcon("superMenu")) self.refreshButton.setIcon(EricPixmapCache.getIcon("reload")) self.installButton.setIcon(EricPixmapCache.getIcon("plus")) + self.outdatedButton.setIcon(EricPixmapCache.getIcon("question")) self.upgradeButton.setIcon(EricPixmapCache.getIcon("upgrade")) self.uninstallButton.setIcon(EricPixmapCache.getIcon("minus")) self.installButton.clicked.connect(self.__installPackages) + self.outdatedButton.clicked.connect(self.__checkOutdatedPackages) self.upgradeButton.clicked.connect(self.__upgradePackage) self.uninstallButton.clicked.connect(self.__uninstallPackage) @@ -122,6 +127,10 @@ ################################################################### self.__upgradeSubmenu = QMenu(self.tr("Upgrade")) + self.__checkOutdatedPackagesAct = self.__upgradeSubmenu.addAction( + self.tr("Check Outdated Packages"), self.__checkOutdatedPackages + ) + self.__upgradeSubmenu.addSeparator() self.__upgradePackagesAct = self.__upgradeSubmenu.addAction( self.tr("Upgrade Selected Package"), self.__upgradePackage ) @@ -155,6 +164,10 @@ self.__upgradeSubmenuAct = self.__pipxMenu.addMenu(self.__upgradeSubmenu) self.__pipxMenu.addSeparator() self.__uninstallSubmenuAct = self.__pipxMenu.addMenu(self.__uninstallSubmenu) + self.__pipxMenu.addSeparator() + self.__pipxMenu.addAction( + self.tr("Standalone Interpreters"), self.__showInterpreters + ) self.__pipxMenu.aboutToShow.connect(self.__aboutToShowPipxMenu) @@ -310,28 +323,42 @@ self.on_refreshButton_clicked() @pyqtSlot() + def __checkOutdatedPackages(self): + """ + Private slot to check, if there are any outdated packages. + """ + with EricOverrideCursor(): + for row in range(self.packagesList.topLevelItemCount()): + itm = self.packagesList.topLevelItem(row) + package = itm.text(PipxWidget.PackageColumn) + latestVersion = self.__pipx.checkPackageOutdated(package) + if latestVersion is not None: + self.__markPackageOutdated(itm, latestVersion) + self.__resizePackagesColumns() + + @pyqtSlot() def __upgradePackage(self): """ Private slot to upgrade the selected package. """ - # TODO: not implemented yet - pass + package = self.__selectedPackages()[0] + self.__pipx.upgradePackage(package) + self.on_refreshButton_clicked() @pyqtSlot() def __upgradeAllPackages(self): """ Private slot to upgrade all packages. """ - # TODO: not implemented yet - pass + self.__pipx.upgradeAllPackages() + self.on_refreshButton_clicked() @pyqtSlot() def __upgradeSharedLibs(self): """ Private slot to upgrade the shared libraries. """ - # TODO: not implemented yet - pass + self.__pipx.upgradeSharedLibraries() @pyqtSlot() def __uninstallPackage(self): @@ -355,7 +382,6 @@ """ Private slot to uninstall all packages. """ - # TODO: not implemented yet yes = EricMessageBox.yesNo( self, self.tr("Uninstall All Packages"), @@ -367,6 +393,16 @@ self.__pipx.uninstallAllPackages() self.on_refreshButton_clicked() + @pyqtSlot() + def __showInterpreters(self): + """ + Private slot to show a list of standalone Python interpreters. + """ + from .PipxInterpretersDialog import PipxInterpretersDialog + + dlg = PipxInterpretersDialog(self.__pipx, self) + dlg.exec() + ####################################################################### ## Main widget related methods below ####################################################################### @@ -388,6 +424,23 @@ self.packagesList.resizeColumnToContents(PipxWidget.VersionColumn) self.packagesList.resizeColumnToContents(PipxWidget.PythonVersionColumn) + def __markPackageOutdated(self, item, latestVersion): + """ + Private method to mark the given package item as outdated. + + @param item reference to the outdated package item + @type QTreeWidgetItem + @param latestVersion latest version of the package + @type str + """ + version = item.data(0, PipxWidget.VersionRole) + item.setData(0, PipxWidget.LatestVersionRole, latestVersion) + item.setText( + PipxWidget.VersionColumn, + self.tr("{0} ({1})", "current version, latest version") + .format(version, latestVersion), + ) + def __populatePackages(self): """ Private method to populate the packages list. @@ -398,8 +451,17 @@ for package in packages: topItem = QTreeWidgetItem( self.packagesList, - [package["name"], package["version"], package["python"]], + [ + package["name"], + package["version"], + self.tr("{0}{1}", "Python version, standalone indicator") + .format( + package["python"], + self.tr(" (standalone)") if package["is_standalone"] else "", + ), + ], ) + topItem.setData(0, PipxWidget.VersionRole, package["version"]) for app, appPath in package["apps"]: itm = QTreeWidgetItem(topItem, [app]) itm.setData(0, PipxWidget.AppPathRole, appPath) @@ -413,17 +475,29 @@ self.__showPipxVersion() expandedPackages = [] + outdatedPackages = {} for row in range(self.packagesList.topLevelItemCount()): itm = self.packagesList.topLevelItem(row) if itm.isExpanded(): expandedPackages.append(itm.text(PipxWidget.PackageColumn)) + latestVersion = itm.data(0, PipxWidget.LatestVersionRole) + if latestVersion is not None: + outdatedPackages[itm.text(PipxWidget.PackageColumn)] = latestVersion self.__populatePackages() for row in range(self.packagesList.topLevelItemCount()): itm = self.packagesList.topLevelItem(row) - if itm.text(PipxWidget.PackageColumn) in expandedPackages: + package = itm.text(PipxWidget.PackageColumn) + if package in expandedPackages: itm.setExpanded(True) + + if ( + package in outdatedPackages + and itm.data(0, PipxWidget.VersionRole) != outdatedPackages[package] + ): + self.__markPackageOutdated(itm, outdatedPackages[package]) + self.__resizePackagesColumns() @pyqtSlot(QTreeWidgetItem, int)
--- a/PipxInterface/PipxWidget.ui Thu Jun 27 17:54:24 2024 +0200 +++ b/PipxInterface/PipxWidget.ui Fri Jun 28 16:25:21 2024 +0200 @@ -150,6 +150,13 @@ </widget> </item> <item> + <widget class="QToolButton" name="outdatedButton"> + <property name="toolTip"> + <string>Press to check for outdated packages.</string> + </property> + </widget> + </item> + <item> <widget class="QToolButton" name="upgradeButton"> <property name="toolTip"> <string>Press to upgrade the selected package.</string> @@ -219,6 +226,7 @@ <tabstop>packagesList</tabstop> <tabstop>refreshButton</tabstop> <tabstop>installButton</tabstop> + <tabstop>outdatedButton</tabstop> <tabstop>upgradeButton</tabstop> <tabstop>uninstallButton</tabstop> <tabstop>pipxMenuButton</tabstop>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/Ui_PipxInterpretersDialog.py Fri Jun 28 16:25:21 2024 +0200 @@ -0,0 +1,62 @@ +# Form implementation generated from reading ui file 'PipxInterface/PipxInterpretersDialog.ui' +# +# Created by: PyQt6 UI code generator 6.7.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_PipxInterpretersDialog(object): + def setupUi(self, PipxInterpretersDialog): + PipxInterpretersDialog.setObjectName("PipxInterpretersDialog") + PipxInterpretersDialog.resize(450, 500) + PipxInterpretersDialog.setSizeGripEnabled(True) + self.verticalLayout = QtWidgets.QVBoxLayout(PipxInterpretersDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.refreshButton = QtWidgets.QToolButton(parent=PipxInterpretersDialog) + self.refreshButton.setObjectName("refreshButton") + self.horizontalLayout.addWidget(self.refreshButton) + self.upgradeButton = QtWidgets.QToolButton(parent=PipxInterpretersDialog) + self.upgradeButton.setObjectName("upgradeButton") + self.horizontalLayout.addWidget(self.upgradeButton) + self.pruneButton = QtWidgets.QToolButton(parent=PipxInterpretersDialog) + self.pruneButton.setObjectName("pruneButton") + self.horizontalLayout.addWidget(self.pruneButton) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.verticalLayout.addLayout(self.horizontalLayout) + self.interpretersList = QtWidgets.QTreeWidget(parent=PipxInterpretersDialog) + self.interpretersList.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) + self.interpretersList.setAlternatingRowColors(True) + self.interpretersList.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.NoSelection) + self.interpretersList.setObjectName("interpretersList") + self.verticalLayout.addWidget(self.interpretersList) + self.buttonBox = QtWidgets.QDialogButtonBox(parent=PipxInterpretersDialog) + self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Close) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(PipxInterpretersDialog) + self.buttonBox.accepted.connect(PipxInterpretersDialog.accept) # type: ignore + self.buttonBox.rejected.connect(PipxInterpretersDialog.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(PipxInterpretersDialog) + PipxInterpretersDialog.setTabOrder(self.refreshButton, self.upgradeButton) + PipxInterpretersDialog.setTabOrder(self.upgradeButton, self.pruneButton) + PipxInterpretersDialog.setTabOrder(self.pruneButton, self.interpretersList) + + def retranslateUi(self, PipxInterpretersDialog): + _translate = QtCore.QCoreApplication.translate + PipxInterpretersDialog.setWindowTitle(_translate("PipxInterpretersDialog", "Standalone Python Interpreters")) + self.refreshButton.setToolTip(_translate("PipxInterpretersDialog", "Press to refresh the list of interpreters.")) + self.upgradeButton.setToolTip(_translate("PipxInterpretersDialog", "Press to upgrade installed interpreters to the latest micro version.")) + self.pruneButton.setToolTip(_translate("PipxInterpretersDialog", "Press to prune all unused interpreters.")) + self.interpretersList.setSortingEnabled(True) + self.interpretersList.headerItem().setText(0, _translate("PipxInterpretersDialog", "Interpreter/Package"))
--- a/PipxInterface/Ui_PipxWidget.py Thu Jun 27 17:54:24 2024 +0200 +++ b/PipxInterface/Ui_PipxWidget.py Fri Jun 28 16:25:21 2024 +0200 @@ -73,6 +73,9 @@ self.installButton = QtWidgets.QToolButton(parent=PipxWidget) self.installButton.setObjectName("installButton") self.horizontalLayout_2.addWidget(self.installButton) + self.outdatedButton = QtWidgets.QToolButton(parent=PipxWidget) + self.outdatedButton.setObjectName("outdatedButton") + self.horizontalLayout_2.addWidget(self.outdatedButton) self.upgradeButton = QtWidgets.QToolButton(parent=PipxWidget) self.upgradeButton.setObjectName("upgradeButton") self.horizontalLayout_2.addWidget(self.upgradeButton) @@ -92,7 +95,8 @@ QtCore.QMetaObject.connectSlotsByName(PipxWidget) PipxWidget.setTabOrder(self.packagesList, self.refreshButton) PipxWidget.setTabOrder(self.refreshButton, self.installButton) - PipxWidget.setTabOrder(self.installButton, self.upgradeButton) + PipxWidget.setTabOrder(self.installButton, self.outdatedButton) + PipxWidget.setTabOrder(self.outdatedButton, self.upgradeButton) PipxWidget.setTabOrder(self.upgradeButton, self.uninstallButton) PipxWidget.setTabOrder(self.uninstallButton, self.pipxMenuButton) PipxWidget.setTabOrder(self.pipxMenuButton, self.venvsPathEdit) @@ -109,6 +113,7 @@ self.label_5.setText(_translate("PipxWidget", "Standalone Interpreters:")) self.refreshButton.setToolTip(_translate("PipxWidget", "Press to refresh the packages list.")) self.installButton.setToolTip(_translate("PipxWidget", "Press to install packages.")) + self.outdatedButton.setToolTip(_translate("PipxWidget", "Press to check for outdated packages.")) self.upgradeButton.setToolTip(_translate("PipxWidget", "Press to upgrade the selected package.")) self.uninstallButton.setToolTip(_translate("PipxWidget", "Press to uninstall the selected package.")) self.packagesList.setSortingEnabled(True)
--- a/PluginPipxInterface.epj Thu Jun 27 17:54:24 2024 +0200 +++ b/PluginPipxInterface.epj Fri Jun 28 16:25:21 2024 +0200 @@ -41,6 +41,7 @@ "FORMS": [ "PipxInterface/PipxAppStartDialog.ui", "PipxInterface/PipxExecDialog.ui", + "PipxInterface/PipxInterpretersDialog.ui", "PipxInterface/PipxPackagesInputDialog.ui", "PipxInterface/PipxReinstallDialog.ui", "PipxInterface/PipxSpecInputDialog.ui", @@ -130,12 +131,14 @@ "PipxInterface/Pipx.py", "PipxInterface/PipxAppStartDialog.py", "PipxInterface/PipxExecDialog.py", + "PipxInterface/PipxInterpretersDialog.py", "PipxInterface/PipxPackagesInputDialog.py", "PipxInterface/PipxReinstallDialog.py", "PipxInterface/PipxSpecInputDialog.py", "PipxInterface/PipxWidget.py", "PipxInterface/Ui_PipxAppStartDialog.py", "PipxInterface/Ui_PipxExecDialog.py", + "PipxInterface/Ui_PipxInterpretersDialog.py", "PipxInterface/Ui_PipxPackagesInputDialog.py", "PipxInterface/Ui_PipxReinstallDialog.py", "PipxInterface/Ui_PipxSpecInputDialog.py",