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()
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())
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
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()
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()
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