Wed, 26 Jun 2024 18:40:48 +0200
Implemented the 'pipx install' and 'pipx install-all' functions.
--- a/PipxInterface/Pipx.py Wed Jun 26 11:57:04 2024 +0200 +++ b/PipxInterface/Pipx.py Wed Jun 26 18:40:48 2024 +0200 @@ -18,6 +18,8 @@ from eric7 import Preferences from eric7.SystemUtilities import OSUtilities +from .PipxExecDialog import PipxExecDialog + class Pipx(QObject): """ @@ -104,7 +106,7 @@ """ binDir = sysconfig.get_path("scripts") pipx = os.path.join(binDir, "pipx") - if OSUtilities.isWindowsPlatform: + if OSUtilities.isWindowsPlatform(): pipx += ".exe" return pipx @@ -180,3 +182,112 @@ packages.append(package) return packages + + def installPackages( + self, + packages, + interpreterVersion="", + fetchMissingInterpreter=False, + forceVenvModification=False, + systemSitePackages=False + ): + """ + Public method + + @param packages list of packages to install + @type list of str + @param interpreterVersion version of the Python interpreter (defaults to "") + @type str (optional) + @param fetchMissingInterpreter flag indicating to fetch a standalone Python + build from GitHub if the specified Python version is not found locally + on the system (defaults to False) + @type bool (optional) + @param forceVenvModification flag indicating to allow modification of already + existing virtual environments (defaults to False) + @type bool (optional) + @param systemSitePackages flag indicating to give access to the system + site-packages directory (defaults to False) + @type bool (optional) + """ + args = ["install"] + if Preferences.getPip("PipSearchIndex"): + indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" + args += ["--index-url", indexUrl] + if interpreterVersion: + args += ["--python", interpreterVersion] + if fetchMissingInterpreter: + args.append("--fetch-missing-python") + if forceVenvModification: + args.append("--force") + if systemSitePackages: + args.append("--system-site-packages") + args += packages + dia = PipxExecDialog(self.tr("Install Packages")) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec() + + def installAllPackages( + self, + specFile, + interpreterVersion="", + fetchMissingInterpreter=False, + forceVenvModification=False, + systemSitePackages=False + ): + """ + Public method + + @param specFile path of the spec metadata file + @type str + @param interpreterVersion version of the Python interpreter (defaults to "") + @type str (optional) + @param fetchMissingInterpreter flag indicating to fetch a standalone Python + build from GitHub if the specified Python version is not found locally + on the system (defaults to False) + @type bool (optional) + @param forceVenvModification flag indicating to allow modification of already + existing virtual environments (defaults to False) + @type bool (optional) + @param systemSitePackages flag indicating to give access to the system + site-packages directory (defaults to False) + @type bool (optional) + """ + args = ["install-all"] + if Preferences.getPip("PipSearchIndex"): + indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" + args += ["--index-url", indexUrl] + if interpreterVersion: + args += ["--python", interpreterVersion] + if fetchMissingInterpreter: + args.append("--fetch-missing-python") + if forceVenvModification: + args.append("--force") + if systemSitePackages: + args.append("--system-site-packages") + args += specFile + dia = PipxExecDialog(self.tr("Install All Packages")) + res = dia.startProcess(self.__getPipxExecutable(), args) + if res: + dia.exec() + + def createSpecMetadataFile(self, specFile): + """ + Public method to create a spec metadata file. + + @param specFile path of the spec metadata file + @type str + @return tuple containing a flag indicating success and an error message in case + of failure + @rtype tuple of (bool, str) + """ + ok, output = self.runProcess(["list", "--json"]) + if ok: + try: + with open(specFile, "w") as f: + f.write(output) + return True, "" + except IOError as err: + return False, str(err) + else: + return False, output
--- a/PipxInterface/PipxAppStartDialog.py Wed Jun 26 11:57:04 2024 +0200 +++ b/PipxInterface/PipxAppStartDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + """ Module implementing a dialog to enter the application command line parameters and to execute the app.
--- a/PipxInterface/PipxDialog.py Wed Jun 26 11:57:04 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,217 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2015 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing a dialog showing the output of a pip command. -""" - -from PyQt6.QtCore import QCoreApplication, QProcess, Qt, QTimer, pyqtSlot -from PyQt6.QtWidgets import QAbstractButton, QDialog, QDialogButtonBox - -from eric7 import Preferences -from eric7.EricWidgets import EricMessageBox - -from .Ui_PipxDialog import Ui_PipxDialog - - -class PipDialog(QDialog, Ui_PipxDialog): - """ - Class implementing a dialog showing the output of a 'python -m pip' - command. - """ - - def __init__(self, text, parent=None): - """ - Constructor - - @param text text to be shown by the label - @type str - @param parent reference to the parent widget - @type QWidget - """ - super().__init__(parent) - self.setupUi(self) - - self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) - self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) - - self.proc = None - self.__processQueue = [] - - self.outputGroup.setTitle(text) - - self.show() - QCoreApplication.processEvents() - - def closeEvent(self, e): - """ - Protected slot implementing a close event handler. - - @param e close event - @type QCloseEvent - """ - self.__cancel() - e.accept() - - def __finish(self): - """ - Private slot called when the process finished or the user pressed - the button. - """ - if ( - self.proc is not None - and self.proc.state() != QProcess.ProcessState.NotRunning - ): - self.proc.terminate() - QTimer.singleShot(2000, self.proc.kill) - self.proc.waitForFinished(3000) - - self.proc = None - - if self.__processQueue: - command, args = self.__processQueue.pop(0) - self.__addOutput("\n\n") - self.startProcess(command, args) - else: - self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled( - True - ) - self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled( - False - ) - self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault( - True - ) - self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setFocus( - Qt.FocusReason.OtherFocusReason - ) - - def __cancel(self): - """ - Private slot to cancel the current action. - """ - self.__processQueue = [] - self.__finish() - - @pyqtSlot(QAbstractButton) - def on_buttonBox_clicked(self, button): - """ - Private slot called by a button of the button box clicked. - - @param button button that was clicked - @type QAbstractButton - """ - if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): - self.close() - elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): - self.__cancel() - - @pyqtSlot(int, QProcess.ExitStatus) - def __procFinished(self, _exitCode, _exitStatus): - """ - Private slot connected to the finished signal. - - @param _exitCode exit code of the process (unused) - @type int - @param _exitStatus exit status of the process (unused) - @type QProcess.ExitStatus - """ - self.__finish() - - def startProcess(self, pipx, args, showArgs=True): - """ - Public slot used to start the process. - - @param pipx path to the 'pipx' executable to be used - @type str - @param args list of arguments for the process - @type list of str - @param showArgs flag indicating to show the arguments - @type bool - @return flag indicating a successful start of the process - @rtype bool - """ - if len(self.errors.toPlainText()) == 0: - self.errorGroup.hide() - - if showArgs: - self.resultbox.append(pipx + " " + " ".join(args)) - self.resultbox.append("") - - self.proc = QProcess() - self.proc.finished.connect(self.__procFinished) - self.proc.readyReadStandardOutput.connect(self.__readStdout) - self.proc.readyReadStandardError.connect(self.__readStderr) - self.proc.start(pipx, args) - procStarted = self.proc.waitForStarted(5000) - if not procStarted: - self.buttonBox.setFocus() - EricMessageBox.critical( - self, - self.tr("Process Generation Error"), - self.tr("The process {0} could not be started.").format(pipx), - ) - return procStarted - - def startProcesses(self, processParams): - """ - Public method to issue a list of commands to be executed. - - @param processParams list of tuples containing the command - and arguments - @type list of tuples of (str, list of str) - @return flag indicating a successful start of the first process - @rtype bool - """ - if len(processParams) > 1: - for command, args in processParams[1:]: - self.__processQueue.append((command, args[:])) - command, args = processParams[0] - return self.startProcess(command, args) - - def __readStdout(self): - """ - Private slot to handle the readyReadStandardOutput signal. - - It reads the output of the process, formats it and inserts it into - the contents pane. - """ - if self.proc is not None: - txt = str( - self.proc.readAllStandardOutput(), - Preferences.getSystem("IOEncoding"), - "replace", - ) - self.__addOutput(txt) - - def __addOutput(self, txt): - """ - Private method to add some text to the output pane. - - @param txt text to be added - @type str - """ - self.resultbox.insertPlainText(txt) - self.resultbox.ensureCursorVisible() - QCoreApplication.processEvents() - - def __readStderr(self): - """ - Private slot to handle the readyReadStandardError signal. - - It reads the error output of the process and inserts it into the - error pane. - """ - if self.proc is not None: - s = str( - self.proc.readAllStandardError(), - Preferences.getSystem("IOEncoding"), - "replace", - ) - self.errorGroup.show() - self.errors.insertPlainText(s) - self.errors.ensureCursorVisible() - - QCoreApplication.processEvents()
--- a/PipxInterface/PipxDialog.ui Wed Jun 26 11:57:04 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>PipxDialog</class> - <widget class="QDialog" name="PipxDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>600</width> - <height>500</height> - </rect> - </property> - <property name="windowTitle"> - <string notr="true">pipx</string> - </property> - <property name="sizeGripEnabled"> - <bool>true</bool> - </property> - <layout class="QVBoxLayout"> - <item> - <widget class="QGroupBox" name="outputGroup"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>2</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Output</string> - </property> - <layout class="QVBoxLayout"> - <item> - <widget class="QTextEdit" name="resultbox"> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="acceptRichText"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="errorGroup"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>1</verstretch> - </sizepolicy> - </property> - <property name="title"> - <string>Errors</string> - </property> - <layout class="QVBoxLayout"> - <item> - <widget class="QTextEdit" name="errors"> - <property name="readOnly"> - <bool>true</bool> - </property> - <property name="acceptRichText"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> - </property> - </widget> - </item> - </layout> - </widget> - <layoutdefault spacing="6" margin="11"/> - <pixmapfunction>qPixmapFromMimeSource</pixmapfunction> - <tabstops> - <tabstop>resultbox</tabstop> - <tabstop>errors</tabstop> - <tabstop>buttonBox</tabstop> - </tabstops> - <resources/> - <connections/> -</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxExecDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog showing the output of a pip command. +""" + +from PyQt6.QtCore import QCoreApplication, QProcess, Qt, QTimer, pyqtSlot +from PyQt6.QtWidgets import QAbstractButton, QDialog, QDialogButtonBox + +from eric7 import Preferences +from eric7.EricWidgets import EricMessageBox + +from .Ui_PipxExecDialog import Ui_PipxExecDialog + + +class PipxExecDialog(QDialog, Ui_PipxExecDialog): + """ + Class implementing a dialog showing the output of a 'python -m pip' + command. + """ + + def __init__(self, text, parent=None): + """ + Constructor + + @param text text to be shown by the label + @type str + @param parent reference to the parent widget + @type QWidget + """ + super().__init__(parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(True) + + self.proc = None + self.__processQueue = [] + + self.outputGroup.setTitle(text) + + self.show() + QCoreApplication.processEvents() + + def closeEvent(self, e): + """ + Protected slot implementing a close event handler. + + @param e close event + @type QCloseEvent + """ + self.__cancel() + e.accept() + + def __finish(self): + """ + Private slot called when the process finished or the user pressed + the button. + """ + if ( + self.proc is not None + and self.proc.state() != QProcess.ProcessState.NotRunning + ): + self.proc.terminate() + QTimer.singleShot(2000, self.proc.kill) + self.proc.waitForFinished(3000) + + self.proc = None + + if self.__processQueue: + command, args = self.__processQueue.pop(0) + self.__addOutput("\n\n") + self.startProcess(command, args) + else: + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled( + True + ) + self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled( + False + ) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setDefault( + True + ) + self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setFocus( + Qt.FocusReason.OtherFocusReason + ) + + def __cancel(self): + """ + Private slot to cancel the current action. + """ + self.__processQueue = [] + self.__finish() + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close): + self.close() + elif button == self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel): + self.__cancel() + + @pyqtSlot(int, QProcess.ExitStatus) + def __procFinished(self, _exitCode, _exitStatus): + """ + Private slot connected to the finished signal. + + @param _exitCode exit code of the process (unused) + @type int + @param _exitStatus exit status of the process (unused) + @type QProcess.ExitStatus + """ + self.__finish() + + def startProcess(self, pipx, args, showArgs=True): + """ + Public slot used to start the process. + + @param pipx path to the 'pipx' executable to be used + @type str + @param args list of arguments for the process + @type list of str + @param showArgs flag indicating to show the arguments + @type bool + @return flag indicating a successful start of the process + @rtype bool + """ + if len(self.errors.toPlainText()) == 0: + self.errorGroup.hide() + + if showArgs: + self.resultbox.append(pipx + " " + " ".join(args)) + self.resultbox.append("") + + self.proc = QProcess() + self.proc.finished.connect(self.__procFinished) + self.proc.readyReadStandardOutput.connect(self.__readStdout) + self.proc.readyReadStandardError.connect(self.__readStderr) + self.proc.start(pipx, args) + procStarted = self.proc.waitForStarted(5000) + if not procStarted: + self.buttonBox.setFocus() + EricMessageBox.critical( + self, + self.tr("Process Generation Error"), + self.tr("The process {0} could not be started.").format(pipx), + ) + return procStarted + + def startProcesses(self, processParams): + """ + Public method to issue a list of commands to be executed. + + @param processParams list of tuples containing the command + and arguments + @type list of tuples of (str, list of str) + @return flag indicating a successful start of the first process + @rtype bool + """ + if len(processParams) > 1: + for command, args in processParams[1:]: + self.__processQueue.append((command, args[:])) + command, args = processParams[0] + return self.startProcess(command, args) + + def __readStdout(self): + """ + Private slot to handle the readyReadStandardOutput signal. + + It reads the output of the process, formats it and inserts it into + the contents pane. + """ + if self.proc is not None: + txt = str( + self.proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + "replace", + ) + self.__addOutput(txt) + + def __addOutput(self, txt): + """ + Private method to add some text to the output pane. + + @param txt text to be added + @type str + """ + self.resultbox.insertPlainText(txt) + self.resultbox.ensureCursorVisible() + QCoreApplication.processEvents() + + def __readStderr(self): + """ + Private slot to handle the readyReadStandardError signal. + + It reads the error output of the process and inserts it into the + error pane. + """ + if self.proc is not None: + s = str( + self.proc.readAllStandardError(), + Preferences.getSystem("IOEncoding"), + "replace", + ) + self.errorGroup.show() + self.errors.insertPlainText(s) + self.errors.ensureCursorVisible() + + QCoreApplication.processEvents()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxExecDialog.ui Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PipxExecDialog</class> + <widget class="QDialog" name="PipxExecDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string notr="true">pipx</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="outputGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>2</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Output</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QTextEdit" name="resultbox"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="errorGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Errors</string> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QTextEdit" name="errors"> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <layoutdefault spacing="6" margin="11"/> + <pixmapfunction>qPixmapFromMimeSource</pixmapfunction> + <tabstops> + <tabstop>resultbox</tabstop> + <tabstop>errors</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxPackagesInputDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter package specifications. +""" + +from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QDialog, QDialogButtonBox + +from .Ui_PipxPackagesInputDialog import Ui_PipxPackagesInputDialog + + +class PipxPackagesInputDialog(QDialog, Ui_PipxPackagesInputDialog): + """ + Class implementing a dialog to enter package specifications and installation + options. + """ + + def __init__(self, title, install=True, parent=None): + """ + Constructor + + @param title dialog title + @type str + @param install flag indicating an install action + @type bool + @param parent reference to the parent widget + @type QWidget + """ + super().__init__(parent) + self.setupUi(self) + + self.setWindowTitle(title) + + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + @pyqtSlot(str) + def on_packagesEdit_textChanged(self, txt): + """ + Private slot handling entering package names. + + @param txt name of the requirements file + @type str + """ + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(bool(txt)) + + def getData(self): + """ + Public method to get the entered data. + + @return tuple with the list of package specifications, the desired Python + interpreter version, a flag indicating to fetch a missing interpreter + from GitHub, a flag indicating to force the installation and a flag + indicating to give access to the system site-packages directory. + @rtype tuple of (list of str, str, bool, bool, bool) + """ + packages = [p.strip() for p in self.packagesEdit.text().split()] + + return ( + packages, + self.interpreterVersionEdit.text().strip(), + self.fetchMissingCheckBox.isChecked(), + self.forceCheckBox.isChecked(), + self.systemSitePackagesCheckBox.isChecked(), + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxPackagesInputDialog.ui Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PipxPackagesInputDialog</class> + <widget class="QDialog" name="PipxPackagesInputDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>282</height> + </rect> + </property> + <property name="windowTitle"> + <string>Packages </string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Package Specifications (separated by whitespace):</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="packagesEdit"/> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Standalone Python Interpreter</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string><b>Note:</b> Leave this entry empty to use the default Python interpreter.</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="interpreterVersionEdit"> + <property name="toolTip"> + <string>Enter the version number of the Python interpreter to be used.</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QCheckBox" name="fetchMissingCheckBox"> + <property name="toolTip"> + <string>Select to fetch a standalone Python build from GitHub if the specified Python version is not found locally on the system.</string> + </property> + <property name="text"> + <string>Fetch missing Python interpreter</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QCheckBox" name="forceCheckBox"> + <property name="toolTip"> + <string>Select to force the modification of existing virtual environments.</string> + </property> + <property name="text"> + <string>Force virtual environment modifications</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="systemSitePackagesCheckBox"> + <property name="toolTip"> + <string>Select to give the virtual environment access to the system site-packages directory.</string> + </property> + <property name="text"> + <string>System-wide Python Packages</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>packagesEdit</tabstop> + <tabstop>interpreterVersionEdit</tabstop> + <tabstop>fetchMissingCheckBox</tabstop> + <tabstop>forceCheckBox</tabstop> + <tabstop>systemSitePackagesCheckBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PipxPackagesInputDialog</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>PipxPackagesInputDialog</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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxSpecInputDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter the data for an 'install-all' operation. +""" + +import os + +from PyQt6.QtCore import pyqtSlot +from PyQt6.QtWidgets import QDialog, QDialogButtonBox + +from eric7.EricWidgets.EricPathPicker import EricPathPickerModes + +from .Ui_PipxSpecInputDialog import Ui_PipxSpecInputDialog + + +class PipxSpecInputDialog(QDialog, Ui_PipxSpecInputDialog): + """ + Class implementing a dialog to enter the data for an 'install-all' operation. + """ + + def __init__(self, title, parent=None): + """ + Constructor + + @param title dialog title + @type str + @param parent reference to the parent widget (defaults to None) + @type QWidget (optional) + """ + super().__init__(parent) + self.setupUi(self) + + self.setWindowTitle(title) + self.specFilePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE) + + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + @pyqtSlot(str) + def on_specFilePicker_textChanged(self, txt): + """ + Private slot handling entering a file path. + + @param txt path of the spec metadata file + @type str + """ + self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled( + bool(txt) and os.path.isfile(txt) + ) + + def getData(self): + """ + Public method to get the entered data. + + @return tuple with the file path of the spec metadata file, the desired Python + interpreter version, a flag indicating to fetch a missing interpreter + from GitHub, a flag indicating to force the installation and a flag + indicating to give access to the system site-packages directory. + @rtype tuple of (list of str, str, bool, bool, bool) + """ + return ( + self.specFilePicker.text(), + self.interpreterVersionEdit.text().strip(), + self.fetchMissingCheckBox.isChecked(), + self.forceCheckBox.isChecked(), + self.systemSitePackagesCheckBox.isChecked(), + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/PipxSpecInputDialog.ui Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PipxSpecInputDialog</class> + <widget class="QDialog" name="PipxSpecInputDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>282</height> + </rect> + </property> + <property name="windowTitle"> + <string>Spec Metadata File</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Spec Metadata File:</string> + </property> + </widget> + </item> + <item> + <widget class="EricPathPicker" name="specFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::WheelFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the working directory for the application run.</string> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Standalone Python Interpreter</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string><b>Note:</b> Leave this entry empty to use the default Python interpreter.</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="interpreterVersionEdit"> + <property name="toolTip"> + <string>Enter the version number of the Python interpreter to be used.</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QCheckBox" name="fetchMissingCheckBox"> + <property name="toolTip"> + <string>Select to fetch a standalone Python build from GitHub if the specified Python version is not found locally on the system.</string> + </property> + <property name="text"> + <string>Fetch missing Python interpreter</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QCheckBox" name="forceCheckBox"> + <property name="toolTip"> + <string>Select to force the modification of existing virtual environments.</string> + </property> + <property name="text"> + <string>Force virtual environment modifications</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="systemSitePackagesCheckBox"> + <property name="toolTip"> + <string>Select to give the virtual environment access to the system site-packages directory.</string> + </property> + <property name="text"> + <string>System-wide Python Packages</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>EricPathPicker</class> + <extends>QWidget</extends> + <header>eric7/EricWidgets/EricPathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>specFilePicker</tabstop> + <tabstop>interpreterVersionEdit</tabstop> + <tabstop>fetchMissingCheckBox</tabstop> + <tabstop>forceCheckBox</tabstop> + <tabstop>systemSitePackagesCheckBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>PipxSpecInputDialog</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>PipxSpecInputDialog</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 Wed Jun 26 11:57:04 2024 +0200 +++ b/PipxInterface/PipxWidget.py Wed Jun 26 18:40:48 2024 +0200 @@ -8,7 +8,7 @@ """ from PyQt6.QtCore import Qt, pyqtSlot -from PyQt6.QtWidgets import QTreeWidgetItem, QWidget +from PyQt6.QtWidgets import QDialog, QMenu, QTreeWidgetItem, QWidget from eric7.EricGui import EricPixmapCache @@ -56,10 +56,14 @@ self.pipxMenuButton.setIcon(EricPixmapCache.getIcon("superMenu")) self.refreshButton.setIcon(EricPixmapCache.getIcon("reload")) + self.pipxMenuButton.setAutoRaise(True) + self.pipxMenuButton.setShowMenuInside(True) + self.packagesList.header().setSortIndicator( PipxWidget.PackageColumn, Qt.SortOrder.AscendingOrder ) + self.__initPipxMenu() self.__showPipxVersion() pipxPaths = self.__pipx.getPipxStrPaths() @@ -70,6 +74,198 @@ self.__populatePackages() + ####################################################################### + ## Menu related methods below + ####################################################################### + + def __initPipxMenu(self): + """ + Private method to create the super menu and attach it to the super + menu button. + """ + ################################################################### + ## Menu with install related actions + ################################################################### + + self.__installSubmenu = QMenu(self.tr("Install")) + self.__installPackagesAct = self.__installSubmenu.addAction( + self.tr("Install Packages"), self.__installPackages + ) + self.__installAllPackagesAct = self.__installSubmenu.addAction( + self.tr("Install All Packages"), self.__installAllPackages + ) + self.__installSubmenu.addSeparator() + self.__reinstallPackagesAct = self.__installSubmenu.addAction( + self.tr("Re-Install Selected Packages"), self.__reinstallPackages + ) + self.__reinstallAllPackagesAct = self.__installSubmenu.addAction( + self.tr("Re-Install All Packages"), self.__reinstallAllPackages + ) + self.__installSubmenu.addSeparator() + self.__createSpecMetadataAct = self.__installSubmenu.addAction( + self.tr("Create Spec Metadata File"), self.__createSpecMetadataFile + ) + + ################################################################### + ## Menu with upgrade related actions + ################################################################### + + self.__upgradeSubmenu = QMenu(self.tr("Upgrade")) + self.__upgradePackagesAct = self.__upgradeSubmenu.addAction( + self.tr("Upgrade Selected Packages"), self.__upgradePackages + ) + self.__upgradeAllPackagesAct = self.__upgradeSubmenu.addAction( + self.tr("Upgrade All Packages"), self.__upgradeAllPackages + ) + self.__upgradeSubmenu.addSeparator() + self.__upgradeSharedLibsAct = self.__upgradeSubmenu.addAction( + self.tr("Upgrade Shared Libraries"), self.__upgradeSharedLibs + ) + + ################################################################### + ## Menu with upgrade related actions + ################################################################### + + self.__uninstallSubmenu = QMenu(self.tr("Uninstall")) + self.__uninstallPackagesAct = self.__uninstallSubmenu.addAction( + self.tr("Uninstall Selected Packages"), self.__uninstallPackages + ) + self.__uninstallAllPackagesAct = self.__uninstallSubmenu.addAction( + self.tr("Uninstall All Packages"), self.__uninstallAllPackages + ) + + ################################################################### + ## Main menu + ################################################################### + + self.__pipxMenu = QMenu() + self.__installSubmenuAct = self.__pipxMenu.addMenu(self.__installSubmenu) + self.__pipxMenu.addSeparator() + self.__upgradeSubmenuAct = self.__pipxMenu.addMenu(self.__upgradeSubmenu) + self.__pipxMenu.addSeparator() + self.__uninstallSubmenuAct = self.__pipxMenu.addMenu(self.__uninstallSubmenu) + + self.__pipxMenu.aboutToShow.connect(self.__aboutToShowPipxMenu) + + self.pipxMenuButton.setMenu(self.__pipxMenu) + + @pyqtSlot() + def __aboutToShowPipxMenu(self): + """ + Private slot to set the action enabled status. + """ + hasPackagesSelected = bool(self.__selectedPackages()) + self.__reinstallPackagesAct.setEnabled(hasPackagesSelected) + self.__upgradePackagesAct.setEnabled(hasPackagesSelected) + self.__uninstallPackagesAct.setEnabled(hasPackagesSelected) + + @pyqtSlot() + def __installPackages(self): + """ + Private slot to install packages to be given by the user. + """ + from .PipxPackagesInputDialog import PipxPackagesInputDialog + + dlg = PipxPackagesInputDialog(self.tr("Install Packages")) + if dlg.exec() == QDialog.DialogCode.Accepted: + packages, pyVersion, fetchMissing, force, systemSitePackages = dlg.getData() + self.__pipx.installPackages( + packages, + interpreterVersion=pyVersion, + fetchMissingInterpreter=fetchMissing, + forceVenvModification=force, + systemSitePackages=systemSitePackages, + ) + self.on_refreshButton_clicked() + + @pyqtSlot() + def __installAllPackages(self): + """ + Private slot to install all packages listed in a specification file. + """ + # TODO: not implemented yet + from .PipxSpecInputDialog import PipxSpecInputDialog + + dlg = PipxSpecInputDialog(self.tr("Install All Packages")) + if dlg.exec() == QDialog.DialogCode.Accepted: + specFile, pyVersion, fetchMissing, force, systemSitePackages = dlg.getData() + self.__pipx.installPackages( + specFile, + interpreterVersion=pyVersion, + fetchMissingInterpreter=fetchMissing, + forceVenvModification=force, + systemSitePackages=systemSitePackages, + ) + self.on_refreshButton_clicked() + + @pyqtSlot() + def __createSpecMetadataFile(self): + """ + Private slot to create a spec metadata file needed by 'pipx install-all'. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __reinstallPackages(self): + """ + Private slot to force a re-installation of the selected packages. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __reinstallAllPackages(self): + """ + Private slot to force a re-installation of all packages. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __upgradePackages(self): + """ + Private slot to upgrade the selected packages. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __upgradeAllPackages(self): + """ + Private slot to upgrade all packages. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __upgradeSharedLibs(self): + """ + Private slot to upgrade the shared libraries. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __uninstallPackages(self): + """ + Private slot to uninstall the selected packages. + """ + # TODO: not implemented yet + pass + + @pyqtSlot() + def __uninstallAllPackages(self): + """ + Private slot to uninstall all packages. + """ + # TODO: not implemented yet + pass + + ####################################################################### + ## Main widget related methods below + ####################################################################### + def __showPipxVersion(self): """ Private method to show the pipx version in the widget header. @@ -139,3 +335,19 @@ app = item.data(0, PipxWidget.AppPathRole) dlg = PipxAppStartDialog(app, self.__plugin, self) dlg.show() + + def __selectedPackages(self): + """ + Private method to determine the list of selected packages. + + @return list of selected packages + @rtype list of QTreeWidgetItem + """ + packages = [] + + for row in range(self.packagesList.topLevelItemCount()): + itm = self.packagesList.topLevelItem(row) + if itm.isSelected(): + packages.append(itm.text(PipxWidget.PackageColumn)) + + return packages
--- a/PipxInterface/PipxWidget.ui Wed Jun 26 11:57:04 2024 +0200 +++ b/PipxInterface/PipxWidget.ui Wed Jun 26 18:40:48 2024 +0200 @@ -47,7 +47,11 @@ </spacer> </item> <item> - <widget class="QToolButton" name="pipxMenuButton"/> + <widget class="EricToolButton" name="pipxMenuButton"> + <property name="popupMode"> + <enum>QToolButton::InstantPopup</enum> + </property> + </widget> </item> </layout> </item> @@ -183,6 +187,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>EricToolButton</class> + <extends>QToolButton</extends> + <header>eric7/EricWidgets/EricToolButton.h</header> + </customwidget> + </customwidgets> <tabstops> <tabstop>packagesList</tabstop> <tabstop>refreshButton</tabstop>
--- a/PipxInterface/Ui_PipxDialog.py Wed Jun 26 11:57:04 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -# Form implementation generated from reading ui file 'PipxInterface/PipxDialog.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_PipxDialog(object): - def setupUi(self, PipxDialog): - PipxDialog.setObjectName("PipxDialog") - PipxDialog.resize(600, 500) - PipxDialog.setWindowTitle("pipx") - PipxDialog.setSizeGripEnabled(True) - self.vboxlayout = QtWidgets.QVBoxLayout(PipxDialog) - self.vboxlayout.setContentsMargins(11, 11, 11, 11) - self.vboxlayout.setSpacing(6) - self.vboxlayout.setObjectName("vboxlayout") - self.outputGroup = QtWidgets.QGroupBox(parent=PipxDialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(2) - sizePolicy.setHeightForWidth(self.outputGroup.sizePolicy().hasHeightForWidth()) - self.outputGroup.setSizePolicy(sizePolicy) - self.outputGroup.setObjectName("outputGroup") - self.vboxlayout1 = QtWidgets.QVBoxLayout(self.outputGroup) - self.vboxlayout1.setContentsMargins(11, 11, 11, 11) - self.vboxlayout1.setSpacing(6) - self.vboxlayout1.setObjectName("vboxlayout1") - self.resultbox = QtWidgets.QTextEdit(parent=self.outputGroup) - self.resultbox.setReadOnly(True) - self.resultbox.setAcceptRichText(False) - self.resultbox.setObjectName("resultbox") - self.vboxlayout1.addWidget(self.resultbox) - self.vboxlayout.addWidget(self.outputGroup) - self.errorGroup = QtWidgets.QGroupBox(parent=PipxDialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.errorGroup.sizePolicy().hasHeightForWidth()) - self.errorGroup.setSizePolicy(sizePolicy) - self.errorGroup.setObjectName("errorGroup") - self.vboxlayout2 = QtWidgets.QVBoxLayout(self.errorGroup) - self.vboxlayout2.setContentsMargins(11, 11, 11, 11) - self.vboxlayout2.setSpacing(6) - self.vboxlayout2.setObjectName("vboxlayout2") - self.errors = QtWidgets.QTextEdit(parent=self.errorGroup) - self.errors.setReadOnly(True) - self.errors.setAcceptRichText(False) - self.errors.setObjectName("errors") - self.vboxlayout2.addWidget(self.errors) - self.vboxlayout.addWidget(self.errorGroup) - self.buttonBox = QtWidgets.QDialogButtonBox(parent=PipxDialog) - self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) - self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Close) - self.buttonBox.setObjectName("buttonBox") - self.vboxlayout.addWidget(self.buttonBox) - - self.retranslateUi(PipxDialog) - QtCore.QMetaObject.connectSlotsByName(PipxDialog) - PipxDialog.setTabOrder(self.resultbox, self.errors) - PipxDialog.setTabOrder(self.errors, self.buttonBox) - - def retranslateUi(self, PipxDialog): - _translate = QtCore.QCoreApplication.translate - self.outputGroup.setTitle(_translate("PipxDialog", "Output")) - self.errorGroup.setTitle(_translate("PipxDialog", "Errors"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/Ui_PipxExecDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,70 @@ +# Form implementation generated from reading ui file 'PipxInterface/PipxExecDialog.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_PipxExecDialog(object): + def setupUi(self, PipxExecDialog): + PipxExecDialog.setObjectName("PipxExecDialog") + PipxExecDialog.resize(600, 500) + PipxExecDialog.setWindowTitle("pipx") + PipxExecDialog.setSizeGripEnabled(True) + self.vboxlayout = QtWidgets.QVBoxLayout(PipxExecDialog) + self.vboxlayout.setContentsMargins(11, 11, 11, 11) + self.vboxlayout.setSpacing(6) + self.vboxlayout.setObjectName("vboxlayout") + self.outputGroup = QtWidgets.QGroupBox(parent=PipxExecDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(2) + sizePolicy.setHeightForWidth(self.outputGroup.sizePolicy().hasHeightForWidth()) + self.outputGroup.setSizePolicy(sizePolicy) + self.outputGroup.setObjectName("outputGroup") + self.vboxlayout1 = QtWidgets.QVBoxLayout(self.outputGroup) + self.vboxlayout1.setContentsMargins(11, 11, 11, 11) + self.vboxlayout1.setSpacing(6) + self.vboxlayout1.setObjectName("vboxlayout1") + self.resultbox = QtWidgets.QTextEdit(parent=self.outputGroup) + self.resultbox.setReadOnly(True) + self.resultbox.setAcceptRichText(False) + self.resultbox.setObjectName("resultbox") + self.vboxlayout1.addWidget(self.resultbox) + self.vboxlayout.addWidget(self.outputGroup) + self.errorGroup = QtWidgets.QGroupBox(parent=PipxExecDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.errorGroup.sizePolicy().hasHeightForWidth()) + self.errorGroup.setSizePolicy(sizePolicy) + self.errorGroup.setObjectName("errorGroup") + self.vboxlayout2 = QtWidgets.QVBoxLayout(self.errorGroup) + self.vboxlayout2.setContentsMargins(11, 11, 11, 11) + self.vboxlayout2.setSpacing(6) + self.vboxlayout2.setObjectName("vboxlayout2") + self.errors = QtWidgets.QTextEdit(parent=self.errorGroup) + self.errors.setReadOnly(True) + self.errors.setAcceptRichText(False) + self.errors.setObjectName("errors") + self.vboxlayout2.addWidget(self.errors) + self.vboxlayout.addWidget(self.errorGroup) + self.buttonBox = QtWidgets.QDialogButtonBox(parent=PipxExecDialog) + self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Close) + self.buttonBox.setObjectName("buttonBox") + self.vboxlayout.addWidget(self.buttonBox) + + self.retranslateUi(PipxExecDialog) + QtCore.QMetaObject.connectSlotsByName(PipxExecDialog) + PipxExecDialog.setTabOrder(self.resultbox, self.errors) + PipxExecDialog.setTabOrder(self.errors, self.buttonBox) + + def retranslateUi(self, PipxExecDialog): + _translate = QtCore.QCoreApplication.translate + self.outputGroup.setTitle(_translate("PipxExecDialog", "Output")) + self.errorGroup.setTitle(_translate("PipxExecDialog", "Errors"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/Ui_PipxPackagesInputDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,76 @@ +# Form implementation generated from reading ui file 'PipxInterface/PipxPackagesInputDialog.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_PipxPackagesInputDialog(object): + def setupUi(self, PipxPackagesInputDialog): + PipxPackagesInputDialog.setObjectName("PipxPackagesInputDialog") + PipxPackagesInputDialog.resize(600, 282) + PipxPackagesInputDialog.setSizeGripEnabled(True) + self.verticalLayout = QtWidgets.QVBoxLayout(PipxPackagesInputDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.label_2 = QtWidgets.QLabel(parent=PipxPackagesInputDialog) + self.label_2.setObjectName("label_2") + self.verticalLayout.addWidget(self.label_2) + self.packagesEdit = QtWidgets.QLineEdit(parent=PipxPackagesInputDialog) + self.packagesEdit.setObjectName("packagesEdit") + self.verticalLayout.addWidget(self.packagesEdit) + self.groupBox = QtWidgets.QGroupBox(parent=PipxPackagesInputDialog) + self.groupBox.setObjectName("groupBox") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(parent=self.groupBox) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 2) + self.label_3 = QtWidgets.QLabel(parent=self.groupBox) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1) + self.interpreterVersionEdit = QtWidgets.QLineEdit(parent=self.groupBox) + self.interpreterVersionEdit.setObjectName("interpreterVersionEdit") + self.gridLayout.addWidget(self.interpreterVersionEdit, 1, 1, 1, 1) + self.fetchMissingCheckBox = QtWidgets.QCheckBox(parent=self.groupBox) + self.fetchMissingCheckBox.setObjectName("fetchMissingCheckBox") + self.gridLayout.addWidget(self.fetchMissingCheckBox, 2, 0, 1, 2) + self.verticalLayout.addWidget(self.groupBox) + self.forceCheckBox = QtWidgets.QCheckBox(parent=PipxPackagesInputDialog) + self.forceCheckBox.setObjectName("forceCheckBox") + self.verticalLayout.addWidget(self.forceCheckBox) + self.systemSitePackagesCheckBox = QtWidgets.QCheckBox(parent=PipxPackagesInputDialog) + self.systemSitePackagesCheckBox.setObjectName("systemSitePackagesCheckBox") + self.verticalLayout.addWidget(self.systemSitePackagesCheckBox) + self.buttonBox = QtWidgets.QDialogButtonBox(parent=PipxPackagesInputDialog) + self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(PipxPackagesInputDialog) + self.buttonBox.accepted.connect(PipxPackagesInputDialog.accept) # type: ignore + self.buttonBox.rejected.connect(PipxPackagesInputDialog.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(PipxPackagesInputDialog) + PipxPackagesInputDialog.setTabOrder(self.packagesEdit, self.interpreterVersionEdit) + PipxPackagesInputDialog.setTabOrder(self.interpreterVersionEdit, self.fetchMissingCheckBox) + PipxPackagesInputDialog.setTabOrder(self.fetchMissingCheckBox, self.forceCheckBox) + PipxPackagesInputDialog.setTabOrder(self.forceCheckBox, self.systemSitePackagesCheckBox) + + def retranslateUi(self, PipxPackagesInputDialog): + _translate = QtCore.QCoreApplication.translate + PipxPackagesInputDialog.setWindowTitle(_translate("PipxPackagesInputDialog", "Packages ")) + self.label_2.setText(_translate("PipxPackagesInputDialog", "Package Specifications (separated by whitespace):")) + self.groupBox.setTitle(_translate("PipxPackagesInputDialog", "Standalone Python Interpreter")) + self.label.setText(_translate("PipxPackagesInputDialog", "<b>Note:</b> Leave this entry empty to use the default Python interpreter.")) + self.label_3.setText(_translate("PipxPackagesInputDialog", "Version:")) + self.interpreterVersionEdit.setToolTip(_translate("PipxPackagesInputDialog", "Enter the version number of the Python interpreter to be used.")) + self.fetchMissingCheckBox.setToolTip(_translate("PipxPackagesInputDialog", "Select to fetch a standalone Python build from GitHub if the specified Python version is not found locally on the system.")) + self.fetchMissingCheckBox.setText(_translate("PipxPackagesInputDialog", "Fetch missing Python interpreter")) + self.forceCheckBox.setToolTip(_translate("PipxPackagesInputDialog", "Select to force the modification of existing virtual environments.")) + self.forceCheckBox.setText(_translate("PipxPackagesInputDialog", "Force virtual environment modifications")) + self.systemSitePackagesCheckBox.setToolTip(_translate("PipxPackagesInputDialog", "Select to give the virtual environment access to the system site-packages directory.")) + self.systemSitePackagesCheckBox.setText(_translate("PipxPackagesInputDialog", "System-wide Python Packages"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/Ui_PipxSpecInputDialog.py Wed Jun 26 18:40:48 2024 +0200 @@ -0,0 +1,84 @@ +# Form implementation generated from reading ui file 'PipxInterface/PipxSpecInputDialog.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_PipxSpecInputDialog(object): + def setupUi(self, PipxSpecInputDialog): + PipxSpecInputDialog.setObjectName("PipxSpecInputDialog") + PipxSpecInputDialog.resize(600, 282) + PipxSpecInputDialog.setSizeGripEnabled(True) + self.verticalLayout = QtWidgets.QVBoxLayout(PipxSpecInputDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.label_2 = QtWidgets.QLabel(parent=PipxSpecInputDialog) + self.label_2.setObjectName("label_2") + self.verticalLayout.addWidget(self.label_2) + self.specFilePicker = EricPathPicker(parent=PipxSpecInputDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.specFilePicker.sizePolicy().hasHeightForWidth()) + self.specFilePicker.setSizePolicy(sizePolicy) + self.specFilePicker.setFocusPolicy(QtCore.Qt.FocusPolicy.WheelFocus) + self.specFilePicker.setObjectName("specFilePicker") + self.verticalLayout.addWidget(self.specFilePicker) + self.groupBox = QtWidgets.QGroupBox(parent=PipxSpecInputDialog) + self.groupBox.setObjectName("groupBox") + self.gridLayout = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(parent=self.groupBox) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 2) + self.label_3 = QtWidgets.QLabel(parent=self.groupBox) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1) + self.interpreterVersionEdit = QtWidgets.QLineEdit(parent=self.groupBox) + self.interpreterVersionEdit.setObjectName("interpreterVersionEdit") + self.gridLayout.addWidget(self.interpreterVersionEdit, 1, 1, 1, 1) + self.fetchMissingCheckBox = QtWidgets.QCheckBox(parent=self.groupBox) + self.fetchMissingCheckBox.setObjectName("fetchMissingCheckBox") + self.gridLayout.addWidget(self.fetchMissingCheckBox, 2, 0, 1, 2) + self.verticalLayout.addWidget(self.groupBox) + self.forceCheckBox = QtWidgets.QCheckBox(parent=PipxSpecInputDialog) + self.forceCheckBox.setObjectName("forceCheckBox") + self.verticalLayout.addWidget(self.forceCheckBox) + self.systemSitePackagesCheckBox = QtWidgets.QCheckBox(parent=PipxSpecInputDialog) + self.systemSitePackagesCheckBox.setObjectName("systemSitePackagesCheckBox") + self.verticalLayout.addWidget(self.systemSitePackagesCheckBox) + self.buttonBox = QtWidgets.QDialogButtonBox(parent=PipxSpecInputDialog) + self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + + self.retranslateUi(PipxSpecInputDialog) + self.buttonBox.accepted.connect(PipxSpecInputDialog.accept) # type: ignore + self.buttonBox.rejected.connect(PipxSpecInputDialog.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(PipxSpecInputDialog) + PipxSpecInputDialog.setTabOrder(self.specFilePicker, self.interpreterVersionEdit) + PipxSpecInputDialog.setTabOrder(self.interpreterVersionEdit, self.fetchMissingCheckBox) + PipxSpecInputDialog.setTabOrder(self.fetchMissingCheckBox, self.forceCheckBox) + PipxSpecInputDialog.setTabOrder(self.forceCheckBox, self.systemSitePackagesCheckBox) + + def retranslateUi(self, PipxSpecInputDialog): + _translate = QtCore.QCoreApplication.translate + PipxSpecInputDialog.setWindowTitle(_translate("PipxSpecInputDialog", "Spec Metadata File")) + self.label_2.setText(_translate("PipxSpecInputDialog", "Spec Metadata File:")) + self.specFilePicker.setToolTip(_translate("PipxSpecInputDialog", "Enter the working directory for the application run.")) + self.groupBox.setTitle(_translate("PipxSpecInputDialog", "Standalone Python Interpreter")) + self.label.setText(_translate("PipxSpecInputDialog", "<b>Note:</b> Leave this entry empty to use the default Python interpreter.")) + self.label_3.setText(_translate("PipxSpecInputDialog", "Version:")) + self.interpreterVersionEdit.setToolTip(_translate("PipxSpecInputDialog", "Enter the version number of the Python interpreter to be used.")) + self.fetchMissingCheckBox.setToolTip(_translate("PipxSpecInputDialog", "Select to fetch a standalone Python build from GitHub if the specified Python version is not found locally on the system.")) + self.fetchMissingCheckBox.setText(_translate("PipxSpecInputDialog", "Fetch missing Python interpreter")) + self.forceCheckBox.setToolTip(_translate("PipxSpecInputDialog", "Select to force the modification of existing virtual environments.")) + self.forceCheckBox.setText(_translate("PipxSpecInputDialog", "Force virtual environment modifications")) + self.systemSitePackagesCheckBox.setToolTip(_translate("PipxSpecInputDialog", "Select to give the virtual environment access to the system site-packages directory.")) + self.systemSitePackagesCheckBox.setText(_translate("PipxSpecInputDialog", "System-wide Python Packages")) +from eric7.EricWidgets.EricPathPicker import EricPathPicker
--- a/PipxInterface/Ui_PipxWidget.py Wed Jun 26 11:57:04 2024 +0200 +++ b/PipxInterface/Ui_PipxWidget.py Wed Jun 26 18:40:48 2024 +0200 @@ -25,7 +25,8 @@ self.horizontalLayout.addWidget(self.pipxVersionLabel) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) self.horizontalLayout.addItem(spacerItem1) - self.pipxMenuButton = QtWidgets.QToolButton(parent=PipxWidget) + self.pipxMenuButton = EricToolButton(parent=PipxWidget) + self.pipxMenuButton.setPopupMode(QtWidgets.QToolButton.ToolButtonPopupMode.InstantPopup) self.pipxMenuButton.setObjectName("pipxMenuButton") self.horizontalLayout.addWidget(self.pipxMenuButton) self.verticalLayout.addLayout(self.horizontalLayout) @@ -99,3 +100,4 @@ self.packagesList.headerItem().setText(0, _translate("PipxWidget", "Package/Application")) self.packagesList.headerItem().setText(1, _translate("PipxWidget", "Version")) self.packagesList.headerItem().setText(2, _translate("PipxWidget", "Python Version")) +from eric7.EricWidgets.EricToolButton import EricToolButton
--- a/PluginPipxInterface.epj Wed Jun 26 11:57:04 2024 +0200 +++ b/PluginPipxInterface.epj Wed Jun 26 18:40:48 2024 +0200 @@ -40,7 +40,9 @@ }, "FORMS": [ "PipxInterface/PipxAppStartDialog.ui", - "PipxInterface/PipxDialog.ui", + "PipxInterface/PipxExecDialog.ui", + "PipxInterface/PipxPackagesInputDialog.ui", + "PipxInterface/PipxSpecInputDialog.ui", "PipxInterface/PipxWidget.ui" ], "HASH": "e670b8ea0fd5593abf0187483d113c50db352d90", @@ -126,10 +128,14 @@ "SOURCES": [ "PipxInterface/Pipx.py", "PipxInterface/PipxAppStartDialog.py", - "PipxInterface/PipxDialog.py", + "PipxInterface/PipxExecDialog.py", + "PipxInterface/PipxPackagesInputDialog.py", + "PipxInterface/PipxSpecInputDialog.py", "PipxInterface/PipxWidget.py", "PipxInterface/Ui_PipxAppStartDialog.py", - "PipxInterface/Ui_PipxDialog.py", + "PipxInterface/Ui_PipxExecDialog.py", + "PipxInterface/Ui_PipxPackagesInputDialog.py", + "PipxInterface/Ui_PipxSpecInputDialog.py", "PipxInterface/Ui_PipxWidget.py", "PipxInterface/__init__.py", "PluginPipxInterface.py",