Пример #1
0
class MotivoAccess:

    motivoDirectory = MOTIVO_DIR

    def __init__(self):

        self.motivoProcess = QProcess()

        self.buildProcess = QProcess()
        self.mergeProcess = QProcess()
        self.sampleProcess = QProcess()

        self.graphProcess = QProcess()

        # TODO check motivo directory and produce an error message if needed
        self.motivoProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                               '/scripts/')
        self.motivoProcess.setProgram('motivo.sh')
        # Qproces emit readReady signal when data is ready to be read
        # Connect the handler for read the data

        self.buildProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                              '/build/bin/')
        self.buildProcess.setProgram('motivo-build')

        self.mergeProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                              '/build/bin/')
        self.mergeProcess.setProgram('motivo-merge')

        self.sampleProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                               '/build/bin/')
        self.sampleProcess.setProgram('motivo-sample')

        self.graphProcess.setWorkingDirectory(MotivoAccess.motivoDirectory +
                                              '/build/bin/')
        self.graphProcess.setProgram('motivo-graph')

    def runMotivo(self, motivoArguments):
        self.motivoProcess.setArguments(motivoArguments)
        self.motivoProcess.start()

    def runBuild(self, buildArguments):
        self.buildProcess.setArguments(buildArguments)
        self.buildProcess.start()

    def runMerge(self, mergeArguments):
        self.mergeProcess.setArguments(mergeArguments)
        self.mergeProcess.start()

    def runSample(self, sampleArguments):
        self.sampleProcess.setArguments(sampleArguments)
        self.sampleProcess.start()

    def runConvertTxtToBinary(self, convertTxtToBinaryArguments):
        self.graphProcess.setArguments(convertTxtToBinaryArguments)
        self.graphProcess.start()
Пример #2
0
class Form(QPlainTextEdit):
    processFinished = Signal()

    def __init__(self, fileName, parent=None):
        super(Form, self).__init__(parent)
        self._fileName = fileName

        self.setStyleSheet("font-family: Source Code Pro; font-size: 16px; ")

        self._process = QProcess()
        self._process.readyReadStandardOutput.connect(self._processStdOut)
        self._process.readyReadStandardError.connect(self._processStdErr)
        self._process.finished.connect(self._processFinished)

    def run(self, args):
        self._append("> " + shlex.join(args.args) + "\n")
        self._lastPos = 0
        self._process.setWorkingDirectory(args.dir)
        self._process.setProgram(args.args[0])
        self._process.setArguments(args.args[1:])
        self._process.start()

    @Slot()
    def putTextToFile(self):
        open(self._fileName, "w").write(self.toPlainText())

    def keyPressEvent(self, event):
        k = event.key()
        if not self.textCursor().position() < self._lastPos:
            super().keyPressEvent(event)
        if k == Qt.Key_Return or k == Qt.Key_Enter:
            self._process.write(
                bytes(self.toPlainText()[self._lastPos:], "utf-8"))
            self._lastPos = self.textCursor().position()

    def _processStdOut(self):
        self._append(str(self._process.readAllStandardOutput(), "utf-8"))

    def _processStdErr(self):
        self._append(str(self._process.readAllStandardError(), "utf-8"))

    def _append(self, output):
        self.moveCursor(QTextCursor.End)
        self.insertPlainText(output)
        self.moveCursor(QTextCursor.End)
        self._lastPos = self.textCursor().position()

    def _processFinished(self, exitCode):
        self._append("\nProcess Finished with exit code: " + str(exitCode) +
                     "\n")
        self.processFinished.emit()
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()
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 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 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()