예제 #1
0
class App(QApplication):
    def __init__(self, argv):
        super().__init__(argv)

        self._create_tray_icon()
        self._create_ui()
        self._create_interaction_server()

        self._session = None

    def open_preferences(self):
        prefs_dialog = PreferencesDialog()
        prefs_dialog.exec()

    def _mode_changed(self):
        action = self._mode_group.checkedAction()
        if action == self._mode_off:
            self._stop_session()
        elif action == self._mode_enabled:
            self._interaction_server.train = False
            self._start_session()
        elif action == self._mode_training:
            self._interaction_server.train = True
            self._start_session()

    def _start_session(self):
        if self._session is not None:
            return

        self._session = QProcess(self)
        self._session.finished.connect(self._session_ended)
        self._session.readyReadStandardOutput.connect(self._log_append_stdout)
        self._session.readyReadStandardError.connect(self._log_append_stderr)

        settings = QSettings()
        self._session.start(sys.executable, [
            'run_session.py',
            settings.value('CyKitAddress', app.DEFAULT_CYKIT_ADDRESS),
            str(settings.value('CyKitPort', app.DEFAULT_CYKIT_PORT)),
            str(self._interaction_server.port)
        ])

    def _stop_session(self):
        if self._session is not None:
            self._session.close()

    # TODO: Handle non-null exit codes
    def _session_ended(self):
        self._session = None
        self._mode_off.setChecked(True)

    def _log_append_stdout(self):
        process = self.sender()
        self._log_window.moveCursor(QTextCursor.End)
        self._log_window.insertPlainText(
            process.readAllStandardOutput().data().decode('utf-8'))
        self._log_window.moveCursor(QTextCursor.End)

    def _log_append_stderr(self):
        process = self.sender()
        self._log_window.moveCursor(QTextCursor.End)
        self._log_window.insertPlainText(
            process.readAllStandardError().data().decode('utf-8'))
        self._log_window.moveCursor(QTextCursor.End)

    def _select_letter(self, letter):
        self._letter_ui.setText(letter)

    def _create_tray_icon(self):
        menu = QMenu()

        self._mode_group = QActionGroup(menu)
        self._mode_group.triggered.connect(self._mode_changed)

        self._mode_off = QAction("&Off", parent=menu)
        self._mode_off.setCheckable(True)
        self._mode_off.setChecked(True)
        self._mode_group.addAction(self._mode_off)
        menu.addAction(self._mode_off)

        self._mode_enabled = QAction("&Enabled", parent=menu)
        self._mode_enabled.setCheckable(True)
        self._mode_group.addAction(self._mode_enabled)
        menu.addAction(self._mode_enabled)

        self._mode_training = QAction("&Training mode", parent=menu)
        self._mode_training.setCheckable(True)
        self._mode_group.addAction(self._mode_training)
        menu.addAction(self._mode_training)

        menu.addSeparator()
        menu.addAction("&Preferences", self.open_preferences)
        menu.addSeparator()
        menu.addAction("E&xit", self.exit)

        pixmap = QPixmap(32, 32)
        pixmap.fill(Qt.white)
        icon = QIcon(pixmap)

        self._tray_icon = QSystemTrayIcon(parent=self)
        self._tray_icon.setContextMenu(menu)
        self._tray_icon.setIcon(icon)
        self._tray_icon.show()

    def _create_ui(self):
        self._keyboard_ui = KeyboardUI()
        self._keyboard_ui.show()

        # TODO: Get rid of this in favor of os_interaction
        self._letter_ui = QLabel("-")
        self._letter_ui.setWindowTitle("Selected letter")
        self._letter_ui.setStyleSheet('font-size: 72pt')
        self._letter_ui.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self._letter_ui.setGeometry(600, 0, 100, 100)
        self._letter_ui.show()

        # TODO: Replace with more user-friendly log
        self._log_window = QTextBrowser()
        self._log_window.setWindowTitle("Session Log")
        self._log_window.setGeometry(700, 0, 500, 500)
        self._log_window.show()

    def _create_interaction_server(self):
        self._interaction_server = InteractionServer(self)
        self._interaction_server.keyboard_flash_row.connect(
            self._keyboard_ui.flash_row)
        self._interaction_server.keyboard_flash_col.connect(
            self._keyboard_ui.flash_col)
        self._interaction_server.keyboard_highlight_letter.connect(
            self._keyboard_ui.highlight_letter)
        self._interaction_server.keyboard_select_letter.connect(
            self._select_letter)
class QProcessExecutionManager(ExecutionManager):
    """Class to manage tool instance execution using a PySide2 QProcess."""

    def __init__(self, logger, program="", args=None, silent=False, semisilent=False):
        """Class constructor.

        Args:
            logger (LoggerInterface): a logger instance
            program (str): Path to program to run in the subprocess (e.g. julia.exe)
            args (list, optional): List of argument for the program (e.g. path to script file)
            silent (bool): Whether or not to emit logger msg signals
            semisilent (bool): If True, show Process Log messages
        """
        super().__init__(logger)
        self._program = program
        self._args = args if args is not None else []
        self._silent = silent  # Do not show Event Log nor Process Log messages
        self._semisilent = semisilent  # Do not show Event Log messages but show Process Log messages
        self.process_failed = False
        self.process_failed_to_start = False
        self.user_stopped = False
        self._process = QProcess(self)
        self.process_output = None  # stdout when running silent
        self.process_error = None  # stderr when running silent
        self._out_chunks = []
        self._err_chunks = []

    def program(self):
        """Program getter method."""
        return self._program

    def args(self):
        """Program argument getter method."""
        return self._args

    # noinspection PyUnresolvedReferences
    def start_execution(self, workdir=None):
        """Starts the execution of a command in a QProcess.

        Args:
            workdir (str, optional): Work directory
        """
        if workdir is not None:
            self._process.setWorkingDirectory(workdir)
        self._process.started.connect(self.process_started)
        self._process.finished.connect(self.on_process_finished)
        if not self._silent and not self._semisilent:  # Loud
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
            self._process.errorOccurred.connect(self.on_process_error)
            self._process.stateChanged.connect(self.on_state_changed)
        elif self._semisilent:  # semi-silent
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
        self._process.start(self._program, self._args)
        if self._process is not None and not self._process.waitForStarted(msecs=10000):
            self.process_failed = True
            self.process_failed_to_start = True
            self._process.deleteLater()
            self._process = None
            self.execution_finished.emit(-9998)

    def wait_for_process_finished(self, msecs=30000):
        """Wait for subprocess to finish.

        Args:
            msecs (int): Timeout in milliseconds

        Return:
            True if process finished successfully, False otherwise
        """
        if self._process is None:
            return False
        if self.process_failed or self.process_failed_to_start:
            return False
        if not self._process.waitForFinished(msecs):
            self.process_failed = True
            self._process.close()
            self._process = None
            return False
        return True

    @Slot()
    def process_started(self):
        """Run when subprocess has started."""

    @Slot(int)
    def on_state_changed(self, new_state):
        """Runs when QProcess state changes.

        Args:
            new_state (int): Process state number (``QProcess::ProcessState``)
        """
        if new_state == QProcess.Starting:
            self._logger.msg.emit("\tStarting program <b>{0}</b>".format(self._program))
            arg_str = " ".join(self._args)
            self._logger.msg.emit("\tArguments: <b>{0}</b>".format(arg_str))
        elif new_state == QProcess.Running:
            self._logger.msg_warning.emit("\tExecution in progress...")
        elif new_state == QProcess.NotRunning:
            # logging.debug("Process is not running")
            pass
        else:
            self._logger.msg_error.emit("Process is in an unspecified state")
            logging.error("QProcess unspecified state: %s", new_state)

    @Slot(int)
    def on_process_error(self, process_error):
        """Runs if there is an error in the running QProcess.

        Args:
            process_error (int): Process error number (``QProcess::ProcessError``)
        """
        if process_error == QProcess.FailedToStart:
            self.process_failed = True
            self.process_failed_to_start = True
            self._logger.msg_error.emit("Process failed to start")
        elif process_error == QProcess.Timedout:
            self.process_failed = True
            self._logger.msg_error.emit("Timed out")
        elif process_error == QProcess.Crashed:
            self.process_failed = True
            if not self.user_stopped:
                self._logger.msg_error.emit("Process crashed")
        elif process_error == QProcess.WriteError:
            self._logger.msg_error.emit("Process WriteError")
        elif process_error == QProcess.ReadError:
            self._logger.msg_error.emit("Process ReadError")
        elif process_error == QProcess.UnknownError:
            self._logger.msg_error.emit("Unknown error in process")
        else:
            self._logger.msg_error.emit("Unspecified error in process: {0}".format(process_error))
        self.teardown_process()

    def teardown_process(self):
        """Tears down the QProcess in case a QProcess.ProcessError occurred.
        Emits execution_finished signal."""
        if not self._process:
            pass
        else:
            out = str(self._process.readAllStandardOutput().data(), "utf-8", errors="replace")
            errout = str(self._process.readAllStandardError().data(), "utf-8", errors="replace")
            if out is not None:
                self._logger.msg_proc.emit(out.strip())
            if errout is not None:
                self._logger.msg_proc.emit(errout.strip())
            self._process.deleteLater()
            self._process = None
        self.execution_finished.emit(-9998)

    def stop_execution(self):
        """See base class."""
        self.user_stopped = True
        self.process_failed = True
        if not self._process:
            return
        try:
            self._process.kill()
            if not self._process.waitForFinished(5000):
                self._process.finished.emit(-1, -1)
                self._process.deleteLater()
        except Exception as ex:  # pylint: disable=broad-except
            self._logger.msg_error.emit("[{0}] exception when terminating process".format(ex))
            logging.exception("Exception in closing QProcess: %s", ex)
        finally:
            self._process = None

    @Slot(int, int)
    def on_process_finished(self, exit_code, exit_status):
        """Runs when subprocess has finished.

        Args:
            exit_code (int): Return code from external program (only valid for normal exits)
            exit_status (int): Crash or normal exit (``QProcess::ExitStatus``)
        """
        if not self._process:
            return
        if exit_status == QProcess.CrashExit:
            if not self._silent:
                self._logger.msg_error.emit("\tProcess crashed")
            exit_code = -1
        elif exit_status == QProcess.NormalExit:
            pass
        else:
            if not self._silent:
                self._logger.msg_error.emit("Unknown QProcess exit status [{0}]".format(exit_status))
            exit_code = -1
        if not exit_code == 0:
            self.process_failed = True
        if not self.user_stopped:
            out = str(self._process.readAllStandardOutput().data(), "utf-8", errors="replace")
            errout = str(self._process.readAllStandardError().data(), "utf-8", errors="replace")
            if out is not None:
                if not self._silent:
                    self._logger.msg_proc.emit(out.strip())
                else:
                    self.process_output = out.strip()
                    self.process_error = errout.strip()
        else:
            self._logger.msg.emit("*** Terminating process ***")
        # Delete QProcess
        self._process.deleteLater()
        self._process = None
        self.execution_finished.emit(exit_code)

    @Slot()
    def on_ready_stdout(self):
        """Emit data from stdout."""
        if not self._process:
            return
        self._process.setReadChannel(QProcess.StandardOutput)
        chunk = self._process.readLine().data()
        self._out_chunks.append(chunk)
        if not chunk.endswith(b"\n"):
            return
        line = b"".join(self._out_chunks)
        line = str(line, "unicode_escape", errors="replace").strip()
        self._logger.msg_proc.emit(line)
        self._out_chunks.clear()

    @Slot()
    def on_ready_stderr(self):
        """Emit data from stderr."""
        if not self._process:
            return
        self._process.setReadChannel(QProcess.StandardError)
        chunk = self._process.readLine().data()
        self._err_chunks.append(chunk)
        if not chunk.endswith(b"\n"):
            return
        line = b"".join(self._err_chunks)
        line = str(line, "utf-8", errors="replace").strip()
        self._logger.msg_proc_error.emit(line)
        self._err_chunks.clear()
예제 #3
0
class QSubProcess(QObject):
    """Class to handle starting, running, and finishing PySide2 QProcesses."""

    subprocess_finished_signal = Signal(int, name="subprocess_finished_signal")

    def __init__(self, toolbox, program=None, args=None, silent=False):
        """Class constructor.

        Args:
            toolbox (ToolboxUI): Instance of Main UI class.
            program (str): Path to program to run in the subprocess (e.g. julia.exe)
            args (list): List of argument for the program (e.g. path to script file)
            silent (bool): Whether or not to emit toolbox msg signals
        """
        super().__init__()
        self._toolbox = toolbox
        self._program = program
        self._args = args
        self._silent = silent
        self.process_failed = False
        self.process_failed_to_start = False
        self._user_stopped = False
        self._process = QProcess(self)
        self.output = None

    def program(self):
        """Program getter method."""
        return self._program

    def args(self):
        """Program argument getter method."""
        return self._args

    # noinspection PyUnresolvedReferences
    def start_process(self, workdir=None):
        """Start the execution of a command in a QProcess.

        Args:
            workdir (str): Directory for the script (at least with Julia this is a must)
        """
        if workdir is not None:
            self._process.setWorkingDirectory(workdir)
        self._process.started.connect(self.process_started)
        self._process.finished.connect(self.process_finished)
        if not self._silent:
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
            self._process.error.connect(
                self.on_process_error)  # errorOccurred available in Qt 5.6
            self._process.stateChanged.connect(self.on_state_changed)
        # self._toolbox.msg.emit("\tStarting program: <b>{0}</b>".format(self._program))
        self._process.start(self._program, self._args)
        if not self._process.waitForStarted(
                msecs=10000
        ):  # This blocks until process starts or timeout happens
            self.process_failed = True
            self.process_failed_to_start = True
            self._process.deleteLater()
            self._process = None
            self.subprocess_finished_signal.emit(-9998)

    def wait_for_finished(self, msecs=30000):
        """Wait for subprocess to finish.

        Return:
            True if process finished successfully, False otherwise
        """
        if not self._process:
            return False
        if self.process_failed or self.process_failed_to_start:
            return False
        if not self._process.waitForFinished(msecs):
            self.process_failed = True
            self._process.close()
            self._process = None
            return False
        return True

    @Slot(name="process_started")
    def process_started(self):
        """Run when subprocess has started."""
        pass
        # self._toolbox.msg.emit("\tSubprocess started...")

    @Slot("QProcess::ProcessState", name="on_state_changed")
    def on_state_changed(self, new_state):
        """Runs when QProcess state changes.

        Args:
            new_state (QProcess::ProcessState): Process state number
        """
        if new_state == QProcess.Starting:
            self._toolbox.msg.emit("\tStarting program <b>{0}</b>".format(
                self._program))
            arg_str = " ".join(self._args)
            self._toolbox.msg.emit("\tArguments: <b>{0}</b>".format(arg_str))
        elif new_state == QProcess.Running:
            self._toolbox.msg_warning.emit(
                "\tExecution is in progress. See Process Log for messages "
                "(stdout&stderr)")
        elif new_state == QProcess.NotRunning:
            # logging.debug("QProcess is not running")
            pass
        else:
            self._toolbox.msg_error.emit("Process is in an unspecified state")
            logging.error("QProcess unspecified state: {0}".format(new_state))

    @Slot("QProcess::ProcessError", name="'on_process_error")
    def on_process_error(self, process_error):
        """Run if there is an error in the running QProcess.

        Args:
            process_error (QProcess::ProcessError): Process error number
        """
        if process_error == QProcess.FailedToStart:
            # self._toolbox.msg_error.emit("Failed to start")
            self.process_failed = True
            self.process_failed_to_start = True
        elif process_error == QProcess.Timedout:
            self.process_failed = True
            self._toolbox.msg_error.emit("Timed out")
        elif process_error == QProcess.Crashed:
            self.process_failed = True
            self._toolbox.msg_error.emit("Process crashed")
        elif process_error == QProcess.WriteError:
            self._toolbox.msg_error.emit("Process WriteError")
        elif process_error == QProcess.ReadError:
            self._toolbox.msg_error.emit("Process ReadError")
        elif process_error == QProcess.UnknownError:
            self._toolbox.msg_error.emit("Unknown error in process")
        else:
            self._toolbox.msg_error.emit(
                "Unspecified error in process: {0}".format(process_error))

    def terminate_process(self):
        """Shutdown simulation in a QProcess."""
        self._toolbox.msg_error.emit("<br/>Terminating process")
        # logging.debug("Terminating QProcess nr.{0}. ProcessState:{1} and ProcessError:{2}"
        #               .format(self._process.processId(), self._process.state(), self._process.error()))
        self._user_stopped = True
        self.process_failed = True
        try:
            self._process.close()
        except Exception as ex:
            self._toolbox.msg_error.emit(
                "[{0}] exception when terminating process".format(ex))
            logging.exception("Exception in closing QProcess: {}".format(ex))

    @Slot(int, name="process_finished")
    def process_finished(self, exit_code):
        """Run when subprocess has finished.

        Args:
            exit_code (int): Return code from external program (only valid for normal exits)
        """
        # logging.debug("Error that occurred last: {0}".format(self._process.error()))
        exit_status = self._process.exitStatus()  # Normal or crash exit
        if exit_status == QProcess.CrashExit:
            if not self._silent:
                self._toolbox.msg_error.emit("\tProcess crashed")
            exit_code = -1
        elif exit_status == QProcess.NormalExit:
            if not self._silent:
                self._toolbox.msg.emit("\tProcess finished")
        else:
            if not self._silent:
                self._toolbox.msg_error.emit(
                    "Unknown QProcess exit status [{0}]".format(exit_status))
            exit_code = -1
        if not exit_code == 0:
            self.process_failed = True
        if not self._user_stopped:
            out = str(self._process.readAllStandardOutput().data(), "utf-8")
            if out is not None:
                if not self._silent:
                    self._toolbox.msg_proc.emit(out.strip())
                else:
                    self.output = out.strip()
        else:
            self._toolbox.msg.emit("*** Terminating process ***")
        # Delete QProcess
        self._process.deleteLater()
        self._process = None
        self.subprocess_finished_signal.emit(exit_code)

    @Slot(name="on_ready_stdout")
    def on_ready_stdout(self):
        """Emit data from stdout."""
        out = str(self._process.readAllStandardOutput().data(), "utf-8")
        self._toolbox.msg_proc.emit(out.strip())

    @Slot(name="on_ready_stderr")
    def on_ready_stderr(self):
        """Emit data from stderr."""
        err = str(self._process.readAllStandardError().data(), "utf-8")
        self._toolbox.msg_proc_error.emit(err.strip())
class QProcessExecutionManager(ExecutionManager):
    """Class to manage tool instance execution using a PySide2 QProcess."""

    def __init__(self, logger, program=None, args=None, silent=False, semisilent=False):
        """Class constructor.

        Args:
            logger (LoggerInterface): a logger instance
            program (str): Path to program to run in the subprocess (e.g. julia.exe)
            args (list): List of argument for the program (e.g. path to script file)
            silent (bool): Whether or not to emit logger msg signals
        """
        super().__init__(logger)
        self._program = program
        self._args = args
        self._silent = silent  # Do not show Event Log nor Process Log messages
        self._semisilent = semisilent  # Do not show Event Log messages but show Process Log messages
        self.process_failed = False
        self.process_failed_to_start = False
        self._user_stopped = False
        self._process = QProcess(self)
        self.process_output = None  # stdout when running silent
        self.error_output = None  # stderr when running silent

    def program(self):
        """Program getter method."""
        return self._program

    def args(self):
        """Program argument getter method."""
        return self._args

    # noinspection PyUnresolvedReferences
    def start_execution(self, workdir=None):
        """Starts the execution of a command in a QProcess.

        Args:
            workdir (str): Work directory
        """
        if workdir is not None:
            self._process.setWorkingDirectory(workdir)
        self._process.started.connect(self.process_started)
        self._process.finished.connect(self.on_process_finished)
        if not self._silent and not self._semisilent:  # Loud
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
            self._process.error.connect(self.on_process_error)  # errorOccurred available in Qt 5.6
            self._process.stateChanged.connect(self.on_state_changed)
        elif self._semisilent:  # semi-silent
            self._process.readyReadStandardOutput.connect(self.on_ready_stdout)
            self._process.readyReadStandardError.connect(self.on_ready_stderr)
        self._process.start(self._program, self._args)
        if not self._process.waitForStarted(msecs=10000):  # This blocks until process starts or timeout happens
            self.process_failed = True
            self.process_failed_to_start = True
            self._process.deleteLater()
            self._process = None
            self.execution_finished.emit(-9998)

    def wait_for_process_finished(self, msecs=30000):
        """Wait for subprocess to finish.

        Return:
            True if process finished successfully, False otherwise
        """
        if not self._process:
            return False
        if self.process_failed or self.process_failed_to_start:
            return False
        if not self._process.waitForFinished(msecs):
            self.process_failed = True
            self._process.close()
            self._process = None
            return False
        return True

    @Slot(name="process_started")
    def process_started(self):
        """Run when subprocess has started."""

    @Slot("QProcess::ProcessState", name="on_state_changed")
    def on_state_changed(self, new_state):
        """Runs when QProcess state changes.

        Args:
            new_state (QProcess::ProcessState): Process state number
        """
        if new_state == QProcess.Starting:
            self._logger.msg.emit("\tStarting program <b>{0}</b>".format(self._program))
            arg_str = " ".join(self._args)
            self._logger.msg.emit("\tArguments: <b>{0}</b>".format(arg_str))
        elif new_state == QProcess.Running:
            self._logger.msg_warning.emit(
                "\tExecution is in progress. See Process Log for messages " "(stdout&stderr)"
            )
        elif new_state == QProcess.NotRunning:
            # logging.debug("QProcess is not running")
            pass
        else:
            self._logger.msg_error.emit("Process is in an unspecified state")
            logging.error("QProcess unspecified state: %s", new_state)

    @Slot("QProcess::ProcessError", name="'on_process_error")
    def on_process_error(self, process_error):
        """Run if there is an error in the running QProcess.

        Args:
            process_error (QProcess::ProcessError): Process error number
        """
        if process_error == QProcess.FailedToStart:
            self.process_failed = True
            self.process_failed_to_start = True
        elif process_error == QProcess.Timedout:
            self.process_failed = True
            self._logger.msg_error.emit("Timed out")
        elif process_error == QProcess.Crashed:
            self.process_failed = True
            if not self._user_stopped:
                self._logger.msg_error.emit("Process crashed")
        elif process_error == QProcess.WriteError:
            self._logger.msg_error.emit("Process WriteError")
        elif process_error == QProcess.ReadError:
            self._logger.msg_error.emit("Process ReadError")
        elif process_error == QProcess.UnknownError:
            self._logger.msg_error.emit("Unknown error in process")
        else:
            self._logger.msg_error.emit("Unspecified error in process: {0}".format(process_error))

    def stop_execution(self):
        """See base class."""
        self._logger.msg_error.emit("Terminating process")
        self._user_stopped = True
        self.process_failed = True
        if not self._process:
            return
        try:
            self._process.terminate()
        except Exception as ex:  # pylint: disable=broad-except
            self._logger.msg_error.emit("[{0}] exception when terminating process".format(ex))
            logging.exception("Exception in closing QProcess: %s", ex)
        finally:
            self._process.deleteLater()
            self._process = None

    @Slot(int)
    def on_process_finished(self, exit_code):
        """Runs when subprocess has finished.

        Args:
            exit_code (int): Return code from external program (only valid for normal exits)
        """
        # logging.debug("Error that occurred last: {0}".format(self._process.error()))
        if not self._process:
            return
        exit_status = self._process.exitStatus()  # Normal or crash exit
        if exit_status == QProcess.CrashExit:
            if not self._silent:
                self._logger.msg_error.emit("\tProcess crashed")
            exit_code = -1
        elif exit_status == QProcess.NormalExit:
            pass
        else:
            if not self._silent:
                self._logger.msg_error.emit("Unknown QProcess exit status [{0}]".format(exit_status))
            exit_code = -1
        if not exit_code == 0:
            self.process_failed = True
        if not self._user_stopped:
            out = str(self._process.readAllStandardOutput().data(), "utf-8")
            errout = str(self._process.readAllStandardError().data(), "utf-8")
            if out is not None:
                if not self._silent:
                    self._logger.msg_proc.emit(out.strip())
                else:
                    self.process_output = out.strip()
                    self.error_output = errout.strip()
        else:
            self._logger.msg.emit("*** Terminating process ***")
        # Delete QProcess
        self._process.deleteLater()
        self._process = None
        self.execution_finished.emit(exit_code)

    @Slot(name="on_ready_stdout")
    def on_ready_stdout(self):
        """Emit data from stdout."""
        if not self._process:
            return
        out = str(self._process.readAllStandardOutput().data(), "utf-8")
        self._logger.msg_proc.emit(out.strip())

    @Slot(name="on_ready_stderr")
    def on_ready_stderr(self):
        """Emit data from stderr."""
        if not self._process:
            return
        err = str(self._process.readAllStandardError().data(), "utf-8")
        self._logger.msg_proc_error.emit(err.strip())
예제 #5
0
class MediaText(Media):

    def __init__(self, media, parent_widget):
        super(MediaText, self).__init__(media, parent_widget)
        self.widget = QWidget(parent_widget)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        self.rect = self.widget.geometry()

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if  not self.is_started():
            self.started_signal.emit()
        super(MediaText, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if  float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        #---- kong ----
        path = f'file:///home/pi/rdtone/urd/content/{self.layout_id}_{self.region_id}_{self.id}.html'
        
        print(path)
        
        l = str(self.rect.left())
        t = str(self.rect.top())
        w = str(self.rect.width())
        h = str(self.rect.height())
        s = f'--window-size={w},{h}'
        p = f'--window-position={l},{t}'
        args = [
            '--kiosk', s, p, path
            #l, t, w, h, path
        ]
        self.process.start('chromium-browser', args)
        #self.process.start('./xWeb', args)
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ----
        if  not self.widget:
            return False
        if  self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if  self.process.state() == QProcess.ProcessState.Running:
            #---- kill process ----
            os.system('pkill chromium')
            #os.system('pkill xWeb')
            #----
            self.process.waitForFinished()
            self.process.close()
        super(MediaText, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
예제 #6
0
class MediaVideo(Media):

    def __init__(self, media, parent_widget):
        super(MediaVideo, self).__init__(media, parent_widget)
        self.widget = QWidget(parent_widget)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        #---- kong ---- for RPi
        self.rect = media['geometry']
        #----

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if  not self.is_started():
            self.started_signal.emit()
        super(MediaVideo, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if  float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        uri = self.options['uri']
        path = f'content/{uri}'
        #---- kong ---- for RPi
        left, top, right, bottom = self.rect.getCoords()
        rect = f'{left},{top},{right},{bottom}'
        args = [ '--win', rect, '--no-osd', '--layer', self.zindex, path ]
        self.process.start('omxplayer.bin', args)
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ---- for RPi
        if  not self.widget:
            return False
        if  self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if  self.process.state() == QProcess.ProcessState.Running:
            self.process.write(b'q')
            self.process.waitForFinished()
            self.process.close()
        super(MediaVideo, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True
class MainWindow(QDialog):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle(APP_NAME)
        self.setWindowIcon(QIcon(get_resource_path(os.path.join('resources', 'noun_Plant.ico'))))
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.ui.lineEdit_invoiceNumber.setValidator(QIntValidator())
        self.ui.progressBar.setMaximum(1)
        self.proc = QProcess()

        self.ui.tableWidget_invoiceContent.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.savePath = create_path_it_not_exist(os.path.join(get_config_path(APP_CONFIG_FOLDER), 'save.json'))
        self.saveData = SaveData(self.savePath)
        self.outputPath = os.path.join(get_exe_path(), create_path_it_not_exist(os.path.join(get_exe_path(), 'output')))
        self.texGenerator = LatexTemplateGenerator(get_resource_path('resources').replace('\\', '/'))

        self.currentGeneralInfo = GeneralInfo()
        self.currentClientInfo = ClientInfo()
        self.currentInvoiceInfo = InvoiceInfo()

        self.ui.pushButton_saveQuickRecallInvoice.clicked.connect(self.save_invoice_info)
        self.ui.pushButton_saveQuickRecallClient.clicked.connect(self.save_client_info)
        self.ui.pushButton_saveQuickRecallGeneral.clicked.connect(self.save_general_info)

        self.ui.comboBox_quickRecallInvoice.activated.connect(self.on_combo_box_invoice_changed)
        self.ui.comboBox_quickRecallClient.activated.connect(self.on_combo_box_client_changed)
        self.ui.comboBox_quickRecallGeneral.activated.connect(self.on_combo_box_general_changed)

        self.ui.pushButton_generateInvoice.clicked.connect(self.generate_invoice)

        self.proc.finished.connect(functools.partial(self._handleProcFinished, self.proc))

        self.ui.toolButton_add.clicked.connect(self.add_row)
        self.ui.toolButton_delete.clicked.connect(self.delete_row)
        self.update_ui()

    def _handleProcFinished(self, process, exitCode):
        stdOut = process.readAllStandardOutput()
        stdErr = process.readAllStandardError()
        print("Standard Out:")
        print(stdOut)
        print("Standard Error:")
        print(stdErr)
        if(exitCode == 0):
            self.success_message('Invoice Generated successfully')
        self.ui.progressBar.setMaximum(1)

    def make_pdf(self, input_folder, input_file):
        if self.proc.isOpen():
            print('cancelled running process')
            self.proc.close()
        self.proc.setWorkingDirectory(input_folder)
        self.proc.start("xelatex", [os.path.join(input_folder, input_file)])
        self.ui.progressBar.setMaximum(0)

    def save_data(self):
        self.save_invoice_info()
        self.save_client_info()
        self.save_general_info()

        if self.saveData.save_client(self.currentClientInfo) and \
                self.saveData.save_invoice(self.currentInvoiceInfo) and \
                self.saveData.save_general(self.currentGeneralInfo):
            self.saveData.save_to_file()
        else:
            self.warning_message("Invalid invoice formatting\n Check for empty or incorrect fields")

    def load_data(self):
        pass

    def list_client(self):
        self.ui.comboBox_quickRecallClient.clear()
        for client in self.saveData.clients:
            self.ui.comboBox_quickRecallClient.addItem(client.name)

    def list_general(self):
        self.ui.comboBox_quickRecallGeneral.clear()
        for general in self.saveData.generals:
            self.ui.comboBox_quickRecallGeneral.addItem(general.company_name)
        pass

    def list_invoice(self):
        self.ui.comboBox_quickRecallInvoice.clear()
        for invoice in self.saveData.invoices:
            self.ui.comboBox_quickRecallInvoice.addItem(invoice.invoice_number)

    def generate_invoice(self):
        print("generating invoice")
        self.save_data()
        filename = ''.join(e for e in unidecode.unidecode(self.currentInvoiceInfo.client.name) if e.isalnum())
        self.texGenerator.render(SaveData.asflatdict(self.currentInvoiceInfo), create_path_it_not_exist(
            os.path.join(self.outputPath, self.currentInvoiceInfo.invoice_number,
                         'Facture_' + self.currentInvoiceInfo.invoice_number + '_' + filename + '.tex')))
        self.make_pdf(os.path.join(self.outputPath, self.currentInvoiceInfo.invoice_number), 'Facture_' + self.currentInvoiceInfo.invoice_number + '_' + filename + '.tex')
        self.update_ui()

    def recall_general_info(self, company_name):
        newGeneral = self.saveData.get_general(company_name)
        self.ui.lineEdit_companyName.setText(newGeneral.company_name)
        self.ui.lineEdit_firstName.setText(newGeneral.first_name)
        self.ui.lineEdit_lastName.setText(newGeneral.last_name)
        self.ui.lineEdit_fullAddress.setText(newGeneral.full_address)
        self.ui.lineEdit_companySIRET.setText(newGeneral.company_siret)
        self.ui.lineEdit_companySIREN.setText(newGeneral.company_siren)
        self.ui.lineEdit_companyAPE.setText(newGeneral.company_ape)
        self.ui.lineEdit_companyEmail.setText(newGeneral.company_email)
        self.ui.lineEdit_companyTelephone.setText(newGeneral.company_phone)
        self.ui.lineEdit_bankIBAN.setText(newGeneral.bank_iban)
        self.ui.lineEdit_bankBIC.setText(newGeneral.bank_bic)

    def recall_client_info(self, name):
        newClient = self.saveData.get_client(name)
        self.ui.lineEdit_clientName.setText(newClient.name)
        self.ui.lineEdit_clientAddressFirst.setText(newClient.address_first_line)
        self.ui.lineEdit_clientAdressSecond.setText(newClient.address_second_line)
        self.update_infos_from_UI()

    def recall_invoice_info(self, invoice_number):
        newInvoice = self.saveData.get_invoice(invoice_number)

        self.ui.lineEdit_invoiceNumber.setText(newInvoice.invoice_number)
        self.ui.lineEdit_invoiceDate.setText(newInvoice.invoice_date)
        self.ui.lineEdit_invoiceName.setText(newInvoice.invoice_name)

        self.ui.tableWidget_invoiceContent.clearContents()
        for item in newInvoice.items:
            row_position = self.ui.tableWidget_invoiceContent.rowCount()
            self.ui.tableWidget_invoiceContent.insertRow(row_position)
            self.ui.tableWidget_invoiceContent.setItem(row_position, 0, QTableWidgetItem(str(item.product_name)))
            self.ui.tableWidget_invoiceContent.setItem(row_position, 1, QTableWidgetItem(str(item.quantity)))
            self.ui.tableWidget_invoiceContent.setItem(row_position, 2, QTableWidgetItem(str(item.price)))

        self.recall_client_info(newInvoice.client.name)
        self.recall_general_info(newInvoice.general.company_name)
        self.update_infos_from_UI()

    def on_combo_box_client_changed(self, index):
        self.recall_client_info(self.ui.comboBox_quickRecallClient.itemText(index))

    def on_combo_box_general_changed(self, index):
        self.recall_general_info(self.ui.comboBox_quickRecallGeneral.itemText(index))

    def on_combo_box_invoice_changed(self, index):
        self.recall_invoice_info(self.ui.comboBox_quickRecallInvoice.itemText(index))

    def save_invoice_info(self):
        self.update_invoice_infos_from_UI()
        if not self.saveData.save_invoice(self.currentInvoiceInfo):
            self.warning_message("Couldn't save new Invoice")
        self.update_ui()

    def save_general_info(self):
        self.update_general_infos_from_UI()
        if not self.saveData.save_general(self.currentGeneralInfo):
            self.warning_message("Couldn't save new General")
        self.update_ui()

    def save_client_info(self):
        self.update_client_infos_from_UI()
        if not self.saveData.save_client(self.currentClientInfo):
            self.warning_message("Couldn't save new Client")
        self.update_ui()

    def add_row(self):
        self.ui.tableWidget_invoiceContent.insertRow(self.ui.tableWidget_invoiceContent.rowCount())

    def delete_row(self):
        self.ui.tableWidget_invoiceContent.removeRow(self.ui.tableWidget_invoiceContent.currentRow())

    def ask_validation(self, text, informative_text, title="Validation Dialog"):
        msgBox = QMessageBox()
        msgBox.setWindowTitle("hey")
        msgBox.setText(text)
        msgBox.setInformativeText(informative_text)
        msgBox.setStandardButtons(QMessageBox.Apply | QMessageBox.Cancel)
        msgBox.setDefaultButton(QMessageBox.Cancel)
        ret = msgBox.exec_()
        return True if ret == QMessageBox.Apply else False

    def success_message(self, text):
        ret = QMessageBox.information(self, self.tr("Success"),
                                  self.tr(text),
                                  QMessageBox.Ok,
                                  QMessageBox.Ok)
    def warning_message(self, text):
        ret = QMessageBox.warning(self, self.tr("Warning"),
                                  self.tr(text),
                                  QMessageBox.Ok,
                                  QMessageBox.Ok)

    def update_client_infos_from_UI(self):
        currentClientInfo = ClientInfo()
        currentClientInfo.name = self.ui.lineEdit_clientName.text()
        currentClientInfo.address_first_line = self.ui.lineEdit_clientAddressFirst.text()
        currentClientInfo.address_second_line = self.ui.lineEdit_clientAdressSecond.text()

        self.currentClientInfo = currentClientInfo

    def update_general_infos_from_UI(self):
        currentGeneralInfo = GeneralInfo()
        currentGeneralInfo.company_name = self.ui.lineEdit_companyName.text()
        currentGeneralInfo.first_name = self.ui.lineEdit_firstName.text()
        currentGeneralInfo.last_name = self.ui.lineEdit_lastName.text()
        currentGeneralInfo.full_address = self.ui.lineEdit_fullAddress.text()
        currentGeneralInfo.company_siret = self.ui.lineEdit_companySIRET.text()
        currentGeneralInfo.company_siren = self.ui.lineEdit_companySIREN.text()
        currentGeneralInfo.company_ape = self.ui.lineEdit_companyAPE.text()
        currentGeneralInfo.company_email = self.ui.lineEdit_companyEmail.text()
        currentGeneralInfo.company_phone = self.ui.lineEdit_companyTelephone.text()
        currentGeneralInfo.bank_iban = self.ui.lineEdit_bankIBAN.text()
        currentGeneralInfo.bank_bic = self.ui.lineEdit_bankBIC.text()

        self.currentGeneralInfo = currentGeneralInfo

    def update_invoice_infos_from_UI(self):
        currentInvoiceInfo = InvoiceInfo()
        currentInvoiceInfo.invoice_number = self.ui.lineEdit_invoiceNumber.text()
        currentInvoiceInfo.invoice_date = self.ui.lineEdit_invoiceDate.text()
        currentInvoiceInfo.invoice_name = self.ui.lineEdit_invoiceName.text()
        currentInvoiceInfo.general = self.currentGeneralInfo
        currentInvoiceInfo.client = self.currentClientInfo

        currentInvoiceInfo.items = []
        try:
            for row in range(self.ui.tableWidget_invoiceContent.rowCount()):
                newItem = InvoiceItem()
                newItem.product_name = self.ui.tableWidget_invoiceContent.item(row, 0).text() \
                    if self.ui.tableWidget_invoiceContent.item(row, 0) is not None else ""
                newItem.quantity = int(self.ui.tableWidget_invoiceContent.item(row, 1).text()) \
                    if self.ui.tableWidget_invoiceContent.item(row, 1) is not None else 0
                newItem.price = float(self.ui.tableWidget_invoiceContent.item(row, 2).text()) \
                    if self.ui.tableWidget_invoiceContent.item(row, 2) is not None else 0
                currentInvoiceInfo.items.append(newItem)
        except ValueError:
            self.warning_message("oops Something went wrong, make sure you entered appropriate values")

        self.currentInvoiceInfo = currentInvoiceInfo

    def update_infos_from_UI(self):
        self.update_general_infos_from_UI()
        self.update_client_infos_from_UI()
        self.update_invoice_infos_from_UI()

    def update_ui(self):
        self.list_invoice()
        self.list_client()
        self.list_general()
예제 #8
0
class QKonsol(QPlainTextEdit):

    userTextEntry = ""
    commandList = ["cd " + QDir.homePath()]
    length = 0
    history = -1

    def __init__(self, parent=None):
        super().__init__()
        self.setParent(parent)
        self.setWindowTitle(self.tr("Terminal"))
        self.setCursorWidth(7)
        self.setContextMenuPolicy(Qt.NoContextMenu)
        font = self.font()
        font.setFamily("Consolas")
        font.setPointSize(10)
        self.setFont(font)
        self.setUndoRedoEnabled(False)
        palette = QPalette()
        palette.setColor(QPalette.Base, Qt.black)
        palette.setColor(QPalette.Text, Qt.white)
        palette.setColor(QPalette.Highlight, Qt.white)
        palette.setColor(QPalette.HighlightedText, Qt.black)
        self.setFrameShape(QFrame.NoFrame)
        self.setPalette(palette)
        self.resize(720, 480)

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)
        self.process.setReadChannel(QProcess.StandardOutput)

        self.process.readyReadStandardOutput.connect(self.readStandartOutput)
        self.process.readyReadStandardError.connect(
            lambda: print(self.readAllStandartError()))
        self.cursorPositionChanged.connect(self.cursorPosition)
        self.textChanged.connect(self.whatText)

        if sys.platform == "win32":
            self.process.start("cmd.exe", [], mode=QProcess.ReadWrite)

        else:
            self.process.start(
                "bash", ["-i"],
                mode=QProcess.ReadWrite)  # bash -i interactive mode

    def readStandartOutput(self):
        if sys.platform == "win32":
            st = self.process.readAllStandardOutput().data().decode(
                str(ctypes.cdll.kernel32.GetConsoleOutputCP()))

        else:
            st = self.process.readAllStandardOutput().data().decode("utf-8")

        # print(repr(st), self.commandList)
        if not st.startswith(self.commandList[-1]):
            self.appendPlainText(st)

    def __line_end(self):
        if sys.platform == "win32":
            return "\r\n"

        elif sys.platform == "linux":
            return "\n"

        elif sys.platform == "darwin":
            return "\r"

    def keyPressEvent(self, event: QKeyEvent):

        if event.key() in (Qt.Key_Enter, Qt.Key_Return):
            if self.commandList[
                    -1] != self.userTextEntry and self.userTextEntry != "":
                self.commandList.append(self.userTextEntry)

            self.length = len(self.userTextEntry + self.__line_end())
            self.process.writeData(self.userTextEntry + self.__line_end(),
                                   self.length)
            self.userTextEntry = ""

        elif event.key() == Qt.Key_Backspace:
            if self.userTextEntry == "":
                return

            else:
                self.userTextEntry = self.userTextEntry[:-1]
                super().keyPressEvent(event)

        elif event.key() == Qt.Key_Up:
            if -len(self.commandList) < self.history:
                self.history -= 1
                print(self.commandList[self.history])
            return

        elif event.key() == Qt.Key_Down:
            if self.history < -1:
                self.history += 1
                print(self.commandList[self.history])
            return

        elif event.key() == Qt.Key_Delete:
            return

        elif event.modifiers() == Qt.ControlModifier:
            super().keyPressEvent(event)

        else:
            super().keyPressEvent(event)
            self.userTextEntry += event.text()

    def cursorPosition(self):
        pass

    # print(self.textCursor().position())

    def whatText(self):
        pass

    # print(self.blockCount())

    def insertFromMimeData(self, source):
        super().insertFromMimeData(source)
        self.userTextEntry += source.text()

    def mouseReleaseEvent(self, event):
        super().mousePressEvent(event)
        cur = self.textCursor()

        if event.button() == Qt.LeftButton:
            cur.movePosition(QTextCursor.End, QTextCursor.MoveAnchor, 1)
            self.setTextCursor(cur)

    def closeEvent(self, event):
        self.process.close()
예제 #9
0
class WebMediaView(MediaView):
    def __init__(self, media, parent):
        super(WebMediaView, self).__init__(media, parent)
        self.widget = QWidget(parent)
        self.process = QProcess(self.widget)
        self.process.setObjectName('%s-process' % self.objectName())
        self.std_out = []
        self.errors = []
        self.stopping = False
        self.mute = False
        self.widget.setGeometry(media['geometry'])
        self.connect(self.process, SIGNAL('error()'), self.process_error)
        self.connect(self.process, SIGNAL('finished()'), self.process_finished)
        self.connect(self.process, SIGNAL('started()'), self.process_started)
        self.set_default_widget_prop()
        self.stop_timer = QTimer(self)
        self.stop_timer.setSingleShot(True)
        self.stop_timer.setInterval(1000)
        self.stop_timer.timeout.connect(self.process_timeout)
        self.rect = self.widget.geometry()

    @Slot()
    def process_timeout(self):
        os.kill(self.process.pid(), signal.SIGTERM)
        self.stopping = False
        if not self.is_started():
            self.started_signal.emit()
        super(WebMediaView, self).stop()

    @Slot(object)
    def process_error(self, err):
        print('---- process error ----')
        self.errors.append(err)
        self.stop()

    @Slot()
    def process_finished(self):
        self.stop()

    @Slot()
    def process_started(self):
        self.stop_timer.stop()
        if float(self.duration) > 0:
            self.play_timer.setInterval(int(float(self.duration) * 1000))
            self.play_timer.start()
        self.started_signal.emit()
        pass

    @Slot()
    def play(self):
        self.finished = 0
        self.widget.show()
        self.widget.raise_()
        #---- kong ----
        url = self.options['uri']
        args = [
            str(self.rect.left()),
            str(self.rect.top()),
            str(self.rect.width()),
            str(self.rect.height()),
            QUrl.fromPercentEncoding(QByteArray(url.encode('utf-8')))
        ]
        #self.process.start('dist/web.exe', args) # for windows
        #self.process.start('./dist/web', args) # for RPi
        self.stop_timer.start()
        #----

    @Slot()
    def stop(self, delete_widget=False):
        #---- kong ----
        if not self.widget:
            return False
        if self.stopping or self.is_finished():
            return False
        self.stop_timer.start()
        self.stopping = True
        if self.process.state() == QProcess.ProcessState.Running:
            #---- kill process ----
            self.process.terminate()  # for windows
            self.process.kill()  # for linux
            #os.system('pkill web') # for RPi
            #----
            self.process.waitForFinished()
            self.process.close()
        super(WebMediaView, self).stop(delete_widget)
        self.stopping = False
        self.stop_timer.stop()
        return True