diff -r dd3f6bfb85f7 -r f31df56510a1 ProjectFlask/FlaskBabelExtension/PyBabelCommandDialog.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/FlaskBabelExtension/PyBabelCommandDialog.py Sat Nov 21 20:37:54 2020 +0100 @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to run a flask command and show its output. +""" + +from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton + +from E5Gui import E5MessageBox + +from ..Ui_FlaskCommandDialog import Ui_FlaskCommandDialog + + +class PyBabelCommandDialog(QDialog, Ui_FlaskCommandDialog): + """ + Class implementing a dialog to run a flask command and show its output. + """ + def __init__(self, project, title="", msgSuccess="", msgError="", + parent=None): + """ + Constructor + + @param project reference to the project object + @type Project + @param title window title of the dialog + @type str + @param msgSuccess success message to be shown + @type str + @param msgError message to be shown on error + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(PyBabelCommandDialog, self).__init__(parent) + self.setupUi(self) + + if title: + self.setWindowTitle(title) + + self.__project = project + self.__successMessage = msgSuccess + self.__errorMessage = msgError + + self.__process = None + self.__argsLists = [] + self.__workdir = "" + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + + def startCommand(self, command, args, workdir, clearOutput=True): + """ + Public method to start a pybabel command and show its output. + + @param command pybabel command to be run + @type str + @param args list of command line arguments for the command + @type list of str + @param workdir working directory for the command + @type str + @param clearOutput flag indicating to clear the output + @type bool + @return flag indicating a successful start + @rtype bool + """ + babelCommand = self.__project.getBabelCommand() + + self.__process = QProcess() + self.__process.setWorkingDirectory(workdir) + self.__process.setProcessChannelMode(QProcess.MergedChannels) + + self.__process.readyReadStandardOutput.connect(self.__readStdOut) + self.__process.finished.connect(self.__processFinished) + + if clearOutput: + self.outputEdit.clear() + + babelArgs = [command] + if args: + babelArgs += args + + self.__process.start(babelCommand, babelArgs) + ok = self.__process.waitForStarted(10000) + if not ok: + E5MessageBox.critical( + None, + self.tr("Execute PyBabel Command"), + self.tr("""The pybabel process could not be started.""")) + else: + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setFocus( + Qt.OtherFocusReason) + + return ok + + def startBatchCommand(self, argsLists, workdir): + """ + Public method to start a pybabel command repeatedly with a list of + arguments and show the output. + + @param argsLists list of command line arguments for the batch commands + @type list of lists of str + @param workdir working directory for the command + @type str + @return flag indicating a successful start of the first process + @rtype bool + """ + self.__argsLists = argsLists[:] + self.__workdir = workdir + + # start the first process + args = self.__argsLists.pop(0) + res = self.startCommand(args[0], args[1:], workdir) + if not res: + self.__argsLists = [] + + return res + + def closeEvent(self, evt): + """ + Protected method handling the close event of the dialog. + + @param evt reference to the close event object + @type QCloseEvent + """ + self.__argsLists = [] + self.__cancelProcess() + evt.accept() + + @pyqtSlot() + def __readStdOut(self): + """ + Private slot to add the server process output to the output pane. + """ + if self.__process is not None: + out = str(self.__process.readAllStandardOutput(), "utf-8") + self.outputEdit.insertPlainText(out) + + def __processFinished(self, exitCode, exitStatus): + """ + Private slot connected to the finished signal. + + @param exitCode exit code of the process + @type int + @param exitStatus exit status of the process + @type QProcess.ExitStatus + """ + normal = (exitStatus == QProcess.NormalExit) and (exitCode == 0) + self.__cancelProcess() + + if self.__argsLists: + args = self.__argsLists.pop(0) + self.startCommand(args[0], args[1:], self.__workdir, + clearOutput=False) + return + + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + self.buttonBox.button(QDialogButtonBox.Close).setFocus( + Qt.OtherFocusReason) + + if normal and self.__successMessage: + self.outputEdit.insertPlainText(self.__successMessage) + elif not normal and self.__errorMessage: + self.outputEdit.insertPlainText(self.__errorMessage) + + @pyqtSlot() + def __cancelProcess(self): + """ + Private slot to terminate the current process. + """ + if ( + self.__process is not None and + self.__process.state() != QProcess.NotRunning + ): + self.__process.terminate() + QTimer.singleShot(2000, self.__process.kill) + self.__process.waitForFinished(3000) + + self.__process = None + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot handling presses of the button box buttons. + + @param button reference to the button been clicked + @type QAbstractButton + """ + if button is self.buttonBox.button(QDialogButtonBox.Close): + self.close() + elif button is self.buttonBox.button(QDialogButtonBox.Cancel): + self.__argsLists = [] + self.__cancelProcess()