Implemented the 'pipx install' and 'pipx install-all' functions.

Wed, 26 Jun 2024 18:40:48 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 26 Jun 2024 18:40:48 +0200
changeset 9
2ab7d3ac8283
parent 8
02b45cd11e64
child 10
89e0e6e5ca7a

Implemented the 'pipx install' and 'pipx install-all' functions.

PipxInterface/Pipx.py file | annotate | diff | comparison | revisions
PipxInterface/PipxAppStartDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxDialog.ui file | annotate | diff | comparison | revisions
PipxInterface/PipxExecDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxExecDialog.ui file | annotate | diff | comparison | revisions
PipxInterface/PipxPackagesInputDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxPackagesInputDialog.ui file | annotate | diff | comparison | revisions
PipxInterface/PipxSpecInputDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxSpecInputDialog.ui file | annotate | diff | comparison | revisions
PipxInterface/PipxWidget.py file | annotate | diff | comparison | revisions
PipxInterface/PipxWidget.ui file | annotate | diff | comparison | revisions
PipxInterface/Ui_PipxDialog.py file | annotate | diff | comparison | revisions
PipxInterface/Ui_PipxExecDialog.py file | annotate | diff | comparison | revisions
PipxInterface/Ui_PipxPackagesInputDialog.py file | annotate | diff | comparison | revisions
PipxInterface/Ui_PipxSpecInputDialog.py file | annotate | diff | comparison | revisions
PipxInterface/Ui_PipxWidget.py file | annotate | diff | comparison | revisions
PluginPipxInterface.epj file | annotate | diff | comparison | revisions
__init__.py file | annotate | diff | comparison | revisions
--- 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>&lt;b&gt;Note:&lt;/b&gt; 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>&lt;b&gt;Note:&lt;/b&gt; 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",
--- a/__init__.py	Wed Jun 26 11:57:04 2024 +0200
+++ b/__init__.py	Wed Jun 26 18:40:48 2024 +0200
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the pipx interface plug-in.
+"""

eric ide

mercurial