PipxInterface/PipxAppStartDialog.py

Sun, 29 Dec 2024 14:56:04 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 29 Dec 2024 14:56:04 +0100
changeset 121
8deb7d8d9b86
parent 104
45c88e73e3dd
permissions
-rw-r--r--

Prepared a new release.

# -*- coding: utf-8 -*-

# Copyright (c) 2024 - 2025 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to enter the application command line parameters and
to execute the app.
"""

import os
import shlex

from PyQt6.QtCore import QCoreApplication, QProcess, Qt, QTimer, pyqtSlot
from PyQt6.QtWidgets import QAbstractButton, QComboBox, QDialog, QDialogButtonBox

from eric7 import Preferences
from eric7.EricGui import EricPixmapCache
from eric7.EricWidgets import EricMessageBox
from eric7.EricWidgets.EricPathPicker import EricPathPickerModes

from .Ui_PipxAppStartDialog import Ui_PipxAppStartDialog


class PipxAppStartDialog(QDialog, Ui_PipxAppStartDialog):
    """
    Class implementing a dialog to enter the application command line parameters and
    to execute the app.
    """

    def __init__(self, app, plugin, parent=None):
        """
        Constructor

        @param app path of the application to be executed
        @type str
        @param plugin reference to the plug-in object
        @type PluginPipxInterface
        @param parent reference to the parent widget (defaults to None)
        @type QWidget (optional)
        """
        super().__init__(parent)
        self.setupUi(self)

        self.executeButton.setIcon(EricPixmapCache.getIcon("start"))

        self.workdirPicker.setMode(EricPathPickerModes.DIRECTORY_MODE)
        self.workdirPicker.setInsertPolicy(QComboBox.InsertPolicy.InsertAtTop)

        self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)

        self.__plugin = plugin

        self.__process = None

        self.appLabel.setText(app)
        self.errorGroup.hide()

        self.__populateWorkDirs()

        self.parametersEdit.returnPressed.connect(self.on_executeButton_clicked)

    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.__process is not None
            and self.__process.state() != QProcess.ProcessState.NotRunning
        ):
            self.__process.terminate()
            QTimer.singleShot(2000, self.__process.kill)
            self.__process.waitForFinished(3000)

        self.__process = None

        self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(True)
        self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(False)
        self.executeButton.setEnabled(True)

        self.parametersEdit.setFocus(Qt.FocusReason.OtherFocusReason)
        self.parametersEdit.selectAll()

    def __cancel(self):
        """
        Private slot to cancel the current action.
        """
        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()

    @pyqtSlot()
    def on_executeButton_clicked(self):
        """
        Private slot to execute the selected app with the entered parameters.
        """
        self.buttonBox.button(QDialogButtonBox.StandardButton.Close).setEnabled(False)
        self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setEnabled(True)
        self.executeButton.setEnabled(False)

        # clear output of last run
        self.resultbox.clear()
        self.errors.clear()
        self.errorGroup.hide()

        workdir = self.workdirPicker.text()
        command = self.parametersEdit.text()
        args = shlex.split(command)

        self.__process = QProcess()
        self.__process.finished.connect(self.__procFinished)
        self.__process.readyReadStandardOutput.connect(self.__readStdout)
        self.__process.readyReadStandardError.connect(self.__readStderr)
        if os.path.isdir(workdir) or workdir == "":
            self.__populateWorkDirs(workdir)
            self.__process.setWorkingDirectory(workdir)
        self.__process.start(self.appLabel.text(), args)
        procStarted = self.__process.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(
                    os.path.basename(self.appLabel.text())
                ),
            )

    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.__process is not None:
            txt = str(
                self.__process.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.__process is not None:
            s = str(
                self.__process.readAllStandardError(),
                Preferences.getSystem("IOEncoding"),
                "replace",
            )
            self.errorGroup.show()
            self.errors.insertPlainText(s)
            self.errors.ensureCursorVisible()

            QCoreApplication.processEvents()

    def __populateWorkDirs(self, mostRecent=None):
        """
        Private method to populate the working directory selector.

        @param mostRecent most recently used working directory
        @type str
        """
        workDirList = self.__plugin.getPreferences("RecentAppWorkdirs")
        if mostRecent is not None:
            if mostRecent in workDirList:
                # push it to the beginning of the list
                workDirList.remove(mostRecent)
            workDirList.insert(0, mostRecent)
            workDirList = workDirList[
                : self.__plugin.getPreferences("MaxRecentAppWorkdirs")
            ]
            self.__plugin.setPreferences("RecentAppWorkdirs", workDirList)

        self.workdirPicker.clear()
        self.workdirPicker.addItems(workDirList)
        if len(workDirList) > 0:
            self.workdirPicker.setCurrentIndex(0)

eric ide

mercurial