Example #1
0
class PMQThreadManager(QObject):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)

    def __init__(self,
                 parent=None,
                 work_fcn: Callable = None,
                 loop_fcn: Callable = None,
                 exit_fcn: Callable = None):
        super().__init__(parent)
        self.thread_recv = QThread()
        self.worker_recv = PMGThreadWorker()

        if work_fcn is not None and loop_fcn is not None:
            raise ValueError(
                'work_fcn and loop_fcn cannot be both not None at the same time!'
            )
        if work_fcn is not None:
            self.worker_recv.work = work_fcn
        else:
            self.worker_recv.work_loop_fcn = loop_fcn
        self.worker_recv.moveToThread(self.thread_recv)
        self.thread_recv.started.connect(
            self.worker_recv.work)  # self.worker_recv.work)
        self.worker_recv.on_exit_fcn = exit_fcn
        self.thread_recv.start()

    def shut_down(self):
        logger.info('client quit')
        self.worker_recv.on_exit()
        # self.worker_recv.close_socket()
        self.thread_recv.quit()
        self.thread_recv.wait(500)
Example #2
0
class PMClient(QObject, BaseClient):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)

    def __init__(self, parent=None, name='Anonymous QtClient'):
        super().__init__(parent)
        self.name = name
        self.client = self.init_socket(12306)
        self.thread_recv = QThread()
        self.worker_recv = RecvWork(self.client, self.name)
        self.signal_received = self.worker_recv.signal_received
        self.worker_recv.signal_received.connect(self.on_server_message_received)

        self.worker_recv.moveToThread(self.thread_recv)
        self.thread_recv.started.connect(self.worker_recv.work)
        self.thread_recv.start()

    def shut_down(self):
        logger.info('client quit')
        self.worker_recv.close_socket()
        self.thread_recv.quit()
        self.thread_recv.wait(500)

    def on_server_message_received(self, packet: bytes):
        try:
            dic = json.loads(packet)
            logger.info(dic)
            msg = dic.get('message')
            if msg == 'data_changed':
                data_name = dic.get('data_name')
                if data_name is not None:
                    self.signal_data_changed.emit(data_name)
            self.signal_server_message_received.emit(dic)
        except:
            import traceback
            traceback.print_exc()
            pass

    def send_bytes(self, packet: bytes):
        self.client.sendall(packet)

    @timeit
    def compress(self, byte_str):
        return zlib.compress(byte_str)
Example #3
0
    def _start(self, worker=None):
        """Start threads and check for inactive workers."""
        if worker:
            self._queue_workers.append(worker)

        if self._queue_workers and self._running_threads < self._max_threads:
            #print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            worker = self._queue_workers.popleft()
            thread = QThread(None)
            if isinstance(worker, PythonWorker):
                worker.moveToThread(thread)
                worker.sig_finished.connect(thread.quit)
                thread.started.connect(worker._start)
                thread.start()
            elif isinstance(worker, ProcessWorker):
                thread.quit()
                thread.wait()
                worker._start()
            self._threads.append(thread)
        else:
            self._timer.start()

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()
Example #4
0
class PMQThreadObject(QObject):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)

    def __init__(self, parent=None, worker: QObject = None):
        super().__init__(parent)
        self.thread = QThread()
        self.worker = worker
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.work)  # self.worker_recv.work)
        self.thread.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    def terminate(self):
        logger.info('client quit')
        self.worker.on_exit()

        self.thread.quit()
        self.thread.wait(500)
Example #5
0
class PMGQThreadManager(QObject):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)
    signal_finished = Signal()

    def __init__(self, parent=None, worker: QObject = None):
        super().__init__(parent)
        self.thread_recv = QThread()
        self.worker_recv = worker
        self.worker_recv.moveToThread(self.thread_recv)
        self.thread_recv.started.connect(self.worker_recv.work)
        self.thread_recv.start()
        self.thread_recv.finished.connect(self.signal_finished.emit)

    def shut_down(self):
        """
        关闭线程,并且退出。
        :return:
        """
        self.worker_recv.on_exit()
        self.thread_recv.quit()
        self.thread_recv.wait(500)
Example #6
0
class PMGIpythonConsole(RichJupyterWidget):
    def __init__(self, *args, **kwargs):
        super(PMGIpythonConsole, self).__init__(*args, **kwargs)
        self.is_first_execution = True
        self.confirm_restart = False

        self.commands_pool = []
        self.command_callback_pool: Dict[str, Callable] = {}

    def change_ui_theme(self, style: str):
        """
        改变界面主题颜色
        :param style:
        :return:
        """
        style = style.lower()
        if style == 'fusion':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

        elif style == 'qdarkstyle':
            self.style_sheet = default_dark_style_sheet
            self.syntax_style = default_dark_syntax_style

        elif style.lower() == 'windowsvista':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

        elif style.lower() == 'windows':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

    def _handle_kernel_died(self, since_last_heartbit):
        self.is_first_execution = True
        self.restart_kernel(None, True)
        self.initialize_ipython_builtins()
        self.execute_command('')
        return True

    def _handle_execute_input(self, msg):
        super()._handle_execute_result(msg)

    def setup_ui(self):
        self.kernel_manager = None
        self.kernel_client = None
        # initialize by thread
        self.init_thread = QThread(self)
        self.console_object = ConsoleInitThread()
        self.console_object.moveToThread(self.init_thread)
        self.console_object.initialized.connect(self.slot_initialized)
        self.init_thread.finished.connect(self.console_object.deleteLater)
        self.init_thread.finished.connect(self.init_thread.deleteLater)
        self.init_thread.started.connect(self.console_object.run)
        self.init_thread.start()
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)

    def _context_menu_make(self, pos: 'QPoint') -> QMenu:
        menu = super(PMGIpythonConsole, self)._context_menu_make(pos)
        _translate = QCoreApplication.translate
        trans_dic = {
            'Cut': _translate("PMGIpythonConsole", 'Cut'),
            'Copy': _translate("PMGIpythonConsole", 'Copy'),
            'Copy (Raw Text)': _translate("PMGIpythonConsole",
                                          'Copy(Raw Text)'),
            'Paste': _translate("PMGIpythonConsole", 'Paste'),
            'Select All': _translate("PMGIpythonConsole", 'Select All'),
            'Save as HTML/XML': _translate("PMGIpythonConsole",
                                           'Save as HTML/XML'),
            'Print': _translate("PMGIpythonConsole", 'Print')
        }
        for action in menu.actions():
            trans = trans_dic.get(action.text())
            trans = trans if trans is not None else action.text()
            action.setText(trans)
        restart_action = menu.addAction(
            _translate("PMGIpythonConsole", 'Restart'))
        restart_action.triggered.connect(self.slot_restart_kernel)

        stop_action = menu.addAction(
            _translate("PMGIpythonConsole", 'Interrupt'))
        # stop_action.triggered.connect(self.request_interrupt_kernel)
        stop_action.triggered.connect(self.on_interrupt_kernel)
        # stop_action.setEnabled(self._executing)

        return menu

    def on_interrupt_kernel(self):
        """
        当点击中断执行时。
        IPython会出现一个奇怪的问题——当中断执行时,可能_executing恒为False。
        因此干脆不屏蔽了。
        Returns:

        """
        self.interrupt_kernel()

    def _custom_context_menu_requested(self, pos):
        super(PMGIpythonConsole, self)._custom_context_menu_requested(pos)

    def slot_restart_kernel(self, arg):
        ret = QMessageBox.warning(self, '提示', '是否要重启控制台?\n一切变量都将被重置。',
                                  QMessageBox.Ok | QMessageBox.Cancel,
                                  QMessageBox.Cancel)
        if ret == QMessageBox.Ok:
            self._restart_kernel(arg)

    def _restart_kernel(self, arg1):

        self.is_first_execution = True
        self.restart_kernel(None, True)
        self.initialize_ipython_builtins()
        self.execute_command('')
        return True

    def slot_initialized(self, kernel_manager, kernel_client):
        """
        Args:
            kernel_manager: `qtconsole.manager.QtKernelManager`
            kernel_client: `qtconsole.manager.QtKernelManager.client`

        Returns:
        """
        self.kernel_manager = kernel_manager
        self.kernel_client = kernel_client
        self.initialize_ipython_builtins()

    def initialize_ipython_builtins(self):
        return

    def _update_list(self):
        try:
            super(PMGIpythonConsole, self)._update_list()
        except BaseException:
            import traceback
            traceback.print_exc()

    def _banner_default(self):
        """
        自定义控制台开始的文字
        Returns:
        """
        return 'Welcome To PMGWidgets Ipython Console!\n'

    def closeEvent(self, event):
        if self.init_thread.isRunning():
            self.console_object.stop()
            self.init_thread.quit()
            self.init_thread.wait(500)
        super(PMGIpythonConsole, self).closeEvent(event)

    def execute_file(self, file: str, hidden: bool = False):
        if not os.path.exists(file) or not file.endswith('.py'):
            raise FileNotFoundError(f'{file} not found or invalid')
        base = os.path.basename(file)
        cmd = os.path.splitext(base)[0]
        with open(file, 'r', encoding='utf-8') as f:
            source = f.read()

        self.execute_command(source, hidden=hidden, hint_text=cmd)

    def execute_command(self,
                        source,
                        hidden: bool = False,
                        hint_text: str = '') -> str:
        """

        :param source:
        :param hidden:
        :param hint_text: 运行代码前显示的提示
        :return: str 执行命令的 msgid
        """
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)
        # 运行文件时,显示文件名,无换行符,执行选中内容时,包含换行符
        # 检测换行符,在ipy console中显示执行脚本内容
        hint_row_list = hint_text.split("\n")
        for hint in hint_row_list:
            if hint != "":
                cursor.insertText('%s\n' % hint)
                self._insert_continuation_prompt(cursor)
        else:
            # 删除多余的continuation_prompt
            self.undo()

        self._finalize_input_request(
        )  # display input string buffer in console.
        cursor.movePosition(QTextCursor.End)
        if self.kernel_client is None:
            self.commands_pool.append((source, hidden, hint_text))
            return ''
        else:
            return self.pmexecute(source, hidden)

    def _handle_stream(self, msg):
        parent_header = msg.get('parent_header')
        if parent_header is not None:
            msg_id = parent_header.get(
                'msg_id')  # 'fee0bee5-074c00d093b1455be6d166b1_10'']
            if msg_id in self.command_callback_pool.keys():
                callback = self.command_callback_pool.pop(msg_id)
                assert callable(callback)
                callback()
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)
        super()._handle_stream(msg)

    def append_stream(self, text):
        """重写的方法。原本before_prompt属性是False。"""
        self._append_plain_text(text, before_prompt=False)

    def pmexecute(self, source: str, hidden: bool = False) -> str:
        """
        执行代码并且返回Msgid
        :param source:
        :param hidden:
        :return:
        """
        is_legal, msg = self.is_source_code_legal(source)
        if not is_legal:
            QMessageBox.warning(self, '警告', msg)
            source = ''
        msg_id = self.kernel_client.execute(source, hidden)
        self._request_info['execute'][msg_id] = self._ExecutionRequest(
            msg_id, 'user')
        self._hidden = hidden
        if not hidden:
            self.executing.emit(source)
        return msg_id
        # super()._execute(source, hidden)

    def is_source_code_legal(self, source_code: str) -> Tuple[bool, str]:
        """判断注入到shell中的命令是否合法。

        如果命令不合法,应当避免执行该命令。

        Args:
            source_code: 注入到shell中的命令。

        Returns:
            * 是否合法;
            * 如不合法,返回原因;如合法,返回空字符串。

        """
        return True, ''

    @staticmethod
    def install_translator():
        global _trans
        app = QApplication.instance()
        assert app is not None
        _trans = QTranslator()
        _trans.load(QLocale.system(),
                    'qt_zh_CN.qm',
                    directory=os.path.join(os.path.dirname(__file__),
                                           'translations'))
        app.installTranslator(_trans)
Example #7
0
class Runner:
    """
    init -> register -> start
    init -> start -> register
    """
    def __init__(self, task_cls: Type[Task], *args, **kwargs):
        super().__init__()

        self.task: Optional[Task] = None
        self.thread: Optional[QThread] = None

        self._started = False

        self.task_cls = task_cls
        self._args = args
        self._kwargs = kwargs

    def init(self) -> None:
        if self._started:
            raise RuntimeError("Cannot init runner when task already started!")

        self.thread = QThread()

        self.task = self.task_cls(*self._args, **self._kwargs)
        self.task.moveToThread(self.thread)
        self.task.finished.connect(self.task.deleteLater)
        self.task.finished.connect(self.thread.quit)
        self.task.failed.connect(self.fail)
        self.thread.started.connect(
            self.task.execute)  # lambdas don't work here hm
        self.thread.finished.connect(self.thread.deleteLater)

    def register(self,
                 *,
                 started: Callable = None,
                 finished: Callable = None,
                 progress: Callable = None,
                 failed: Callable = None,
                 result: Callable = None) -> None:
        if started is not None:
            self.task.started.connect(started)
        if finished is not None:
            self.task.finished.connect(finished)
        if progress is not None:
            self.task._progressThrottled.connect(progress)
        if failed is not None:
            self.task.failed.connect(failed)
        if result is not None:
            self.task.result.connect(result)

    def start(self) -> None:
        self._started = True
        self.thread.start()

    def stop(self) -> None:
        if self.task is not None:
            self.task.stop()

        if self.thread is not None:
            try:
                self.thread.quit()
                self.thread.wait()
            except RuntimeError:
                pass

        self._started = False

    def fail(self, e: Exception) -> None:
        raise e

    @property
    def is_running(self) -> bool:
        """
        Without this try-except block, Qt throws an error saying that the thread has
        already been deleted, even if `self.thread` is not None.
        """
        is_running = False
        try:
            if self.thread is not None:
                is_running = self.thread.isRunning()
        except RuntimeError:
            pass

        return is_running
Example #8
0
class ProcessConsole(QTextEdit):
    signal_stop_qthread = Signal()
    signal_process_stopped = Signal()
    signal_process_started = Signal()

    insert_mode = ''

    def __init__(self, args: list = None):
        super().__init__()
        self._is_running = False
        self.auto_scroll = True
        self.args = args  #
        self.setContentsMargins(20, 20, 0, 0)
        self.monitor_thread: 'ProcessMonitorThread' = None
        self.out_thread: 'QThread' = None

    def set_args(self, args: list = None):
        self.args = args

    def is_running(self) -> bool:
        """
        返回进程是否在运行
        Returns:

        """
        if self.monitor_thread is not None:
            process = self.get_subprocess()
            if process is not None:
                if process.poll() is None:
                    return True
        return False

    def start_process(self):
        if not self.is_running():
            self.out_thread = QThread(self)
            self.monitor_thread = ProcessMonitorThread()
            self.monitor_thread.args = self.args
            self.monitor_thread.moveToThread(self.out_thread)

            self.out_thread.started.connect(self.monitor_thread.run)
            self.out_thread.start()

            self.monitor_thread.on_out.connect(self.on_stdout)
            self.monitor_thread.on_err.connect(self.on_stderr)

            self.signal_stop_qthread.connect(self.monitor_thread.stop)

            self.out_thread.finished.connect(self.out_thread.deleteLater)
            self.out_thread.finished.connect(self.monitor_thread.deleteLater)

            self.monitor_thread.on_finished.connect(self.terminate_process)

    def on_stdout(self, text):
        if self.insert_mode == 'error':
            self.insertHtml('<p style="color:black;">' + '========' +
                            '<br/></p>')
            self.insert_mode = 'stdout'

        self.insertPlainText(text)
        if self.auto_scroll:
            self.ensureCursorVisible()

    def on_stderr(self, text):
        self.insert_mode = 'error'
        self.insertHtml('<p style="color:red;">' + text + '<br/></p>')
        print(text)
        if self.auto_scroll:
            self.ensureCursorVisible()

    def terminate_process(self):
        if self.monitor_thread is not None:
            self.monitor_thread.process_terminated = True
            self.monitor_thread.process.process.terminate()
            if self.out_thread.isRunning():
                self.signal_stop_qthread.emit()
                self.out_thread.quit()
                self.out_thread.wait(500)
        self.monitor_thread = None
        self.out_thread = None
        self.signal_process_stopped.emit()

    def keyPressEvent(self, e: 'QKeyEvent'):
        if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete:
            return
        print(e.key(), e.text())
        if e.key() == Qt.Key_Return:
            text = '\n'
        else:
            text = e.text()
        if text != '' and self.monitor_thread is not None:
            try:
                print('sent:', text)
                self.monitor_thread.process.process.stdin.write(
                    text.encode('utf8'))
                self.monitor_thread.process.process.stdin.flush()
            except:
                import traceback
                traceback.print_exc()
        super(ProcessConsole, self).keyPressEvent(e)

    def get_subprocess(self):
        """
        返回子进程
        Returns:

        """
        if hasattr(self.monitor_thread, 'process'):
            proc = self.monitor_thread.process
            if hasattr(proc, 'process'):
                return proc.process
        return None
Example #9
0
class RefaceDXLibApp(QApplication):
    """The main application object with the central event handling."""

    name = "Reface DX Lib"

    def __init__(self, args=None):
        if args is None:
            args = sys.argv

        super().__init__(args)
        self.setOrganizationName('chrisarndt.de')
        self.setOrganizationDomain('chrisarndt.de')
        self.setApplicationName(self.name)
        QSettings.setDefaultFormat(QSettings.IniFormat)
        self.config = QSettings()
        self.config.setIniCodec('UTF-8')
        QIcon.setThemeName(self.config.value('gui/icon_theme', "tango"))
        self.debug = True if '-v' in args[1:] else self.config.value(
            'application/debug', False)
        logging.basicConfig(
            level=logging.DEBUG if self.debug else logging.INFO,
            format='%(levelname)s - %(message)s')

        self.mainwin = RefaceDXLibMainWin(self.tr(self.name))
        self.load_database(
            self.config.value('database/last_opened', 'refacedx.db'))

        self.midiin_conn = None
        self.midiout_conn = None
        self.setup_midi_thread()

        # signal connections
        self.aboutToQuit.connect(self.quit)
        self.mainwin.action_open.triggered.connect(self.open_database)
        self.mainwin.action_quit.triggered.connect(self.quit)
        self.mainwin.action_import.triggered.connect(self.import_patches)
        self.mainwin.action_export.triggered.connect(self.export_patches)
        self.mainwin.action_send.triggered.connect(self.send_patches)
        self.mainwin.action_request.triggered.connect(self.request_patch)
        self.mainwin.action_delete.triggered.connect(self.delete_patches)

        # dialogs (initialized on-demand)
        self.add_patch_dialog = None

        self.style = DarkAppStyle(self)
        self.mainwin.show()

    def load_database(self, filename):
        db_uri = 'sqlite:///{}'.format(filename)
        self.session = initdb(db_uri,
                              debug=self.config.value('database/debug', False))
        self.patches = PatchlistTableModel(self.session)
        self.mainwin.set_patchtable_model(self.patches)

    def setup_midi_thread(self):
        self.midithread = QThread()
        self.midiworker = MidiWorker(self.config)
        self.midiworker.moveToThread(self.midithread)

        self.midithread.started.connect(self.midiworker.initialize)
        self.midiworker.send_patch_start.connect(
            partial(self.mainwin.set_send_action_enabled, False))
        self.midiworker.send_patch_complete.connect(
            partial(self.mainwin.set_send_action_enabled, True))
        self.midiworker.recv_patch_start.connect(
            partial(self.mainwin.set_request_action_enabled, False))
        self.midiworker.recv_patch_complete.connect(self.receive_patch)
        self.midiworker.recv_patch_failed.connect(
            partial(self.mainwin.set_request_action_enabled, True))
        self.midiworker.input_ports_changed.connect(
            self.build_midi_input_selector)
        self.midiworker.output_ports_changed.connect(
            self.build_midi_output_selector)

        # Start thread
        self.midithread.start()
        self.timer = QTimer()
        self.timer.timeout.connect(self.midiworker.scan_ports.emit)
        self.timer.start(3000)

    @Slot(object)
    def build_midi_input_selector(self, ports):
        log.debug("Building MIDI input selector...")
        cb = self.mainwin.midiin_cb

        if self.midiin_conn:
            cb.currentIndexChanged.disconnect(self.set_midiin_port)

        cb.setEnabled(False)
        cb.clear()
        selected = -1

        for i, (port, is_open) in enumerate(ports):
            cb.addItem(port, port)

            if is_open:
                selected = i

        cb.setCurrentIndex(selected)
        self.midiin_conn = cb.currentIndexChanged.connect(self.set_midiin_port)
        cb.setEnabled(True)
        log.debug("MIDI input selector (re-)built.")

    @Slot(object)
    def build_midi_output_selector(self, ports):
        log.debug("Building MIDI output selector...")
        cb = self.mainwin.midiout_cb

        if self.midiout_conn:
            cb.currentIndexChanged.disconnect(self.set_midiout_port)

        cb.setEnabled(False)
        cb.clear()
        selected = -1

        for i, (port, is_open) in enumerate(ports):
            cb.addItem(port, port)

            if is_open:
                selected = i

        cb.setCurrentIndex(selected)
        self.midiout_conn = cb.currentIndexChanged.connect(
            self.set_midiout_port)
        cb.setEnabled(True)
        log.debug("MIDI output selector (re-)built.")

    def set_midiin_port(self, index):
        log.debug("MIDI input selector index changed: %r", index)
        if index != -1:
            port = self.mainwin.midiin_cb.itemData(index)
            self.midiworker.set_input_port.emit(port)

    def set_midiout_port(self, index):
        log.debug("MIDI output selector index changed: %r", index)
        if index != -1:
            port = self.mainwin.midiout_cb.itemData(index)
            self.midiworker.set_output_port.emit(port)

    # action handlers
    def quit(self):
        self.midiworker.close.emit()
        self.midithread.quit()
        self.midithread.wait()
        self.mainwin.close()

    def open_database(self):
        options = QFileDialog.Options()

        if not self.config.value('native_dialogs', False):
            options |= QFileDialog.DontUseNativeDialog

        filename, _ = QFileDialog.getOpenFileName(
            self.mainwin,
            self.tr("Open patch database"),
            self.config.value('paths/last_database_path', ''),
            "SQLite Database (*.sqlite *.db);;All Files (*)",
            options=options)

        if filename and exists(filename):
            self.config.setValue('paths/last_database_path', dirname(filename))
            log.info(f"Opening database file '{filename}'...")
            try:
                self.load_database(filename)
            except Exception as exc:
                log.exception(f"Error opening database file '{filename}'.")
                dlg = self.create_error_dlg(
                    self.tr("Could not load patch database <i>{}</i>.").format(
                        basename(filename)),
                    detail=str(exc),
                    ignore_buttons=False)
                dlg.exec_()
            else:
                self.config.setValue('database/last_opened', filename)

    def save_patch(self, data, **meta):
        name = meta.get('name', '').strip()

        if not name:
            name = get_patch_name(data)

        with self.session.begin():
            author = meta.get('author', '').strip()
            if author:
                author, created = get_or_create(self.session,
                                                Author,
                                                create_kwargs={'name': author},
                                                displayname=author)
            else:
                author = None

            manufacturer = meta.get('manufacturer', '').strip()
            if manufacturer:
                manufacturer, created = get_or_create(
                    self.session,
                    Manufacturer,
                    create_kwargs={'name': manufacturer},
                    displayname=manufacturer)
            else:
                manufacturer = None

            device = meta.get('device', '').strip()
            if device:
                device, created = get_or_create(self.session,
                                                Device,
                                                create_kwargs={'name': device},
                                                displayname=device)
            else:
                device = None

            patch = Patch(name=name,
                          displayname=meta.get('displayname', '').strip()
                          or name,
                          description=meta.get('description', '').strip()
                          or None,
                          rating=meta.get('rating', 0),
                          author=author,
                          manufacturer=manufacturer,
                          device=device,
                          created=meta.get('created', datetime.now()),
                          data=set_patch_name(data, name))
            self.session.add(patch)

            tags = (tag.strip() for tag in meta.get('tags', '').split(','))
            patch.update_tags(self.session, (tag for tag in tags if tag))

    def request_patch(self):
        self.midiworker.request_patch.emit(None)

    def receive_patch(self, data):
        log.debug("Patch received: %s", get_patch_name(data))

        if self.add_patch_dialog is None:
            self.add_patch_dialog = AddPatchDialog(self)

        metadata = self.add_patch_dialog.new_from_data(
            data, self.tr("Add new patch"))

        if metadata:
            log.debug("Patch meta data: %r", metadata)
            self.save_patch(data, **metadata)
            self.patches._update()
            self.patches.layoutChanged.emit()

        self.mainwin.set_request_action_enabled(True)

    def delete_patches(self):
        if self.mainwin.selection.hasSelection():
            rows = sorted(
                [r.row() for r in self.mainwin.selection.selectedRows()])
            patches = tuple(self.patches.get_row(r).displayname for r in rows)
            msg_box = QMessageBox()

            if len(rows) == 1:
                msg_box.setText(
                    self.tr("Delete patch '{}'?").format(patches[0]))
            else:
                msg_box.setText(
                    self.tr("Delete {} patches?").format(len(rows)))
                msg_box.setDetailedText('\n'.join(patches))

            msg_box.setInformativeText(
                self.tr("Patches can only be restored by re-importing them."))
            msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
            msg_box.setDefaultButton(QMessageBox.Cancel)
            msg_box.setIcon(QMessageBox.Warning)

            if msg_box.exec_() == QMessageBox.Yes:
                with self.session.begin():
                    for n, row in enumerate(rows):
                        self.patches.removeRows(row - n)

    def import_patches(self):
        options = QFileDialog.Options()

        if not self.config.value('native_dialogs', False):
            options |= QFileDialog.DontUseNativeDialog

        files, _ = QFileDialog.getOpenFileNames(
            self.mainwin,
            self.tr("Import SysEx patches"),
            self.config.value('paths/last_import_path', ''),
            "SysEx Files (*.syx);;All Files (*)",
            options=options)

        if files:
            self.config.setValue('paths/last_import_path', dirname(files[0]))

            self.patches.layoutAboutToBeChanged.emit()
            with self.session.begin():
                for file in files:
                    with open(file, 'rb') as syx:
                        data = syx.read()

                    #assert len(data) == 241
                    if is_reface_dx_voice(data):
                        name = get_patch_name(data)
                        displayname = splitext(basename(file))[0].replace(
                            '_', ' ').strip()
                        patch = Patch(name=name,
                                      displayname=displayname,
                                      data=data)
                        self.session.add(patch)

            # TODO: check if any patches were actually added
            self.patches._update()
            self.patches.layoutChanged.emit()

    def export_patches(self):
        if self.mainwin.selection.hasSelection():
            options = QFileDialog.Options()

            if not self.config.value('native_dialogs', False):
                options |= (QFileDialog.DontUseNativeDialog
                            | QFileDialog.ShowDirsOnly
                            | QFileDialog.DontResolveSymlinks)

            dir_ = QFileDialog.getExistingDirectory(
                self.mainwin,
                self.tr("Choose directory to export SysEx patches"),
                self.config.value('paths/last_export_path', ''),
                options=options)

            if dir_:
                self.config.setValue('paths/last_export_path', dir_)

                ignore_all = False
                exported = 0
                for row in self.mainwin.selection.selectedRows():
                    patch = self.patches.get_row(row)

                    try:
                        filename = patch.displayname.replace(' ', '_') + '.syx'
                        with open(join(dir_, filename), 'wb') as syx:
                            syx.write(patch.data)
                    except OSError as exc:
                        log.error("Could not write SysEx file at '%s': %s",
                                  filename, exc)

                        if ignore_all:
                            continue

                        dlg = self.create_error_dlg(
                            self.tr("Could not write SysEx file "
                                    "<i>{}</i>.").format(basename(filename)),
                            info=self.
                            tr("Abort export, ignore error or ignore all errors?"
                               ),
                            detail=str(exc))
                        ret = dlg.exec_()

                        if dlg.clickedButton() == dlg.btn_ignoreall:
                            ignore_all = True
                        elif ret == QMessageBox.Abort:
                            log.warning("Patch export aborted.")
                            break
                        elif ret == QMessageBox.Ignore:
                            pass
                        else:
                            log.error(
                                "Unknown error dialog response. This shouldn't happen."
                            )
                    else:
                        exported += 1

                self.set_status_text(
                    self.tr("{} patches exported.").format(exported))
            else:
                self.set_status_text(self.tr("Patch export cancelled."))

    def send_patches(self):
        if self.mainwin.selection.hasSelection():
            for row in self.mainwin.selection.selectedRows():
                patch = self.patches.get_row(row)
                self.midiworker.send_patch.emit(patch.data)
                log.debug("Sent patch: %s (%s)", patch.displayname, patch.name)

    def create_error_dlg(self,
                         message,
                         info=None,
                         detail=None,
                         ignore_buttons=True):
        dlg = QMessageBox()
        dlg.setText(message)

        if info:
            dlg.setInformativeText(info)

        if detail:
            dlg.setDetailedText(detail)

        dlg.setWindowTitle(self.name + self.tr(" - Error"))
        dlg.setIcon(QMessageBox.Critical)

        if ignore_buttons:
            dlg.setStandardButtons(QMessageBox.Abort | QMessageBox.Ignore)
            dlg.btn_ignoreall = dlg.addButton(self.tr("Ignore A&ll"),
                                              QMessageBox.ActionRole)
            dlg.setDefaultButton(QMessageBox.Ignore)
        else:
            dlg.setDefaultButton(QMessageBox.NoButton)

        return dlg

    def set_status_text(self, message, timeout=5000):
        self.mainwin.statusbar.showMessage(message, timeout)
Example #10
0
class DAQ_Move(Ui_Form, QObject):
    """
        | DAQ_Move object is a module used to control one motor from a specified list.
        |
        | Preset is an optional list of dicts used to managers programatically settings such as the name of the controller from the list of possible controllers, COM address...
        |
        | Init is a boolean to tell the programm to initialize the controller at the start of the programm given the managers options

        ========================= =================================================
        **Attributes**             **Type**
        *command_stage*            instance of Signal
        *move_done_signal*         instance of Signal
        *update_settings_signal*   instance of Signal
        *status_signal*               instance of Signal
        *bounds_signal*            instance of Signal
        *params*                   dictionnary list
        *ui*                       instance of UI_Form
        *parent*                   QObject
        *title*                    string
        *wait_time*                int
        *initialized_state*        boolean
        *Move_done*                boolean
        *controller*               instance of the specific controller object
        *stage*                    instance of the stage (axis or wathever) object
        *current_position*         float
        *target_position*          float
        *wait_position_flag*       boolean
        *stage_types*              string list
        ========================= =================================================

        See Also
        --------
        set_enabled_move_buttons, set_setting_tree, stage_changed, quit_fun, ini_stage_fun, move_Abs, move_Rel, move_Home, get_position, stop_Motion, show_settings, show_fine_tuning

        References
        ----------
        QLocale, QObject, Signal, QStatusBar, ParameterTree
    """
    init_signal = Signal(bool)
    command_stage = Signal(ThreadCommand)
    command_tcpip = Signal(ThreadCommand)
    move_done_signal = Signal(
        str, float
    )  # to be used in external program to make sure the move has been done, export the current position. str refer to the unique title given to the module
    update_settings_signal = Signal(edict)
    status_signal = Signal(str)
    bounds_signal = Signal(bool)
    params = daq_move_params

    def __init__(self, parent, title="pymodaq Move", init=False):
        """DAQ_Move object is a module used to control one motor from a specified list.
        managers is an optional list of dicts used to managers programatically settings such as the name of the
        controller from the list of possible controllers, COM address...
        init is a boolean to tell the programm to initialize the controller at the start of the programm given the
        managers options
        To differenciate various instance of this class
        """
        self.logger = utils.set_logger(f'{logger.name}.{title}')
        self.logger.info(f'Initializing DAQ_Move: {title}')

        super().__init__()

        here = Path(__file__).parent
        splash = QtGui.QPixmap(str(here.parent.joinpath('splash.png')))
        self.splash_sc = QtWidgets.QSplashScreen(splash,
                                                 Qt.WindowStaysOnTopHint)

        self.ui = Ui_Form()
        self.ui.setupUi(parent)
        self.ui.Moveto_pb_bis_2.setVisible(False)
        self.parent = parent
        self.ui.title_label.setText(title)
        self.title = title
        self.ui.statusbar = QtWidgets.QStatusBar(parent)
        self.ui.StatusBarLayout.addWidget(self.ui.statusbar)
        self.ui.statusbar.setMaximumHeight(20)

        self.send_to_tcpip = False
        self.tcpclient_thread = None

        self.wait_time = 1000
        self.ui.Ini_state_LED

        self.ui.Ini_state_LED.clickable = False
        self.ui.Ini_state_LED.set_as_false()
        self.ui.Move_Done_LED.clickable = False
        self.ui.Move_Done_LED.set_as_false()
        self.initialized_state = False
        self.ui.Current_position_sb.setReadOnly(False)
        self.move_done_bool = True

        # ###########IMPORTANT############################
        self.controller = None  # the hardware controller/set after initialization and to be used by other modules
        # ################################################

        self.current_position = 0
        self.target_position = 0
        self.wait_position_flag = True

        self.ui.Current_position_sb.setValue(self.current_position)
        self.set_enabled_move_buttons(enable=False)
        self.ui.groupBox.hide()
        self.parent.resize(150, 200)

        # #Setting stages types
        self.stage_types = [mov['name'] for mov in DAQ_Move_Stage_type]
        self.ui.Stage_type_combo.clear()
        self.ui.Stage_type_combo.addItems(self.stage_types)

        # create main parameter tree
        self.ui.settings_tree = ParameterTree()
        self.ui.verticalLayout_2.addWidget(self.ui.settings_tree)
        self.ui.settings_tree.setMinimumWidth(300)

        self.settings = Parameter.create(name='Settings',
                                         type='group',
                                         children=self.params)
        self.ui.settings_tree.setParameters(self.settings, showTop=False)

        # connecting from tree
        self.settings.sigTreeStateChanged.connect(
            self.parameter_tree_changed
        )  # any changes on the settings will update accordingly the detector
        self.ui.settings_tree.setVisible(False)
        self.set_setting_tree()

        QtWidgets.QApplication.processEvents()
        # #Connecting buttons:
        self.ui.Stage_type_combo.currentIndexChanged.connect(
            self.set_setting_tree)
        self.ui.Stage_type_combo.currentIndexChanged.connect(
            self.stage_changed)

        self.ui.Quit_pb.clicked.connect(self.quit_fun)
        self.ui.IniStage_pb.clicked.connect(self.ini_stage_fun)

        self.update_status("Ready", wait_time=self.wait_time)
        self.ui.Move_Abs_pb.clicked.connect(
            lambda: self.move_Abs(self.ui.Abs_position_sb.value()))
        self.ui.Move_Rel_plus_pb.clicked.connect(
            lambda: self.move_Rel(self.ui.Rel_position_sb.value()))
        self.ui.Move_Rel_minus_pb.clicked.connect(
            lambda: self.move_Rel(-self.ui.Rel_position_sb.value()))
        self.ui.Find_Home_pb.clicked.connect(self.move_Home)
        self.ui.Get_position_pb.clicked.connect(self.get_position)
        self.ui.Stop_pb.clicked.connect(self.stop_Motion)

        self.ui.parameters_pb.clicked.connect(self.show_settings)
        self.ui.fine_tuning_pb.clicked.connect(self.show_fine_tuning)
        self.ui.Abs_position_sb.valueChanged.connect(
            self.ui.Abs_position_sb_bis.setValue)
        self.ui.Abs_position_sb_bis.valueChanged.connect(
            self.ui.Abs_position_sb.setValue)
        self.ui.Moveto_pb_bis.clicked.connect(
            lambda: self.move_Abs(self.ui.Abs_position_sb_bis.value()))

        # initialize the controller if init=True
        if init:
            self.ui.IniStage_pb.click()

    @property
    def actuator(self):
        return self.ui.Stage_type_combo.currentText()

    @actuator.setter
    def actuator(self, actuator):
        self.ui.Stage_type_combo.setCurrentText(actuator)
        if self.actuator != actuator:
            raise ActuatorError(
                f'{actuator} is not a valid installed actuator: {self.stage_types}'
            )

    def init(self):
        self.ui.IniStage_pb.click()

    def ini_stage_fun(self):
        """
            Init :
                * a DAQ_move_stage instance if not exists
                * a linked thread connected by signal to the DAQ_move_main instance

            See Also
            --------
            set_enabled_move_buttons, DAQ_utils.ThreadCommand, DAQ_Move_stage, DAQ_Move_stage.queue_command, thread_status, DAQ_Move_stage.update_settings, update_status
        """
        try:
            if not self.ui.IniStage_pb.isChecked():
                try:
                    self.set_enabled_move_buttons(enable=False)
                    self.ui.Stage_type_combo.setEnabled(True)
                    self.ui.Ini_state_LED.set_as_false()
                    self.command_stage.emit(ThreadCommand(command="close"))
                except Exception as e:
                    self.logger.exception(str(e))

            else:
                self.stage_name = self.ui.Stage_type_combo.currentText()
                stage = DAQ_Move_stage(self.stage_name, self.current_position,
                                       self.title)
                self.stage_thread = QThread()
                stage.moveToThread(self.stage_thread)

                self.command_stage[ThreadCommand].connect(stage.queue_command)
                stage.status_sig[ThreadCommand].connect(self.thread_status)
                self.update_settings_signal[edict].connect(
                    stage.update_settings)

                self.stage_thread.stage = stage
                self.stage_thread.start()

                self.ui.Stage_type_combo.setEnabled(False)
                self.command_stage.emit(
                    ThreadCommand(command="ini_stage",
                                  attributes=[
                                      self.settings.child(
                                          ('move_settings')).saveState(),
                                      self.controller
                                  ]))

        except Exception as e:
            self.logger.exception(str(e))
            self.set_enabled_move_buttons(enable=False)

    def get_position(self):
        """
            Get the current position from the launched thread via the "check_position" Thread Command.

            See Also
            --------
            update_status, DAQ_utils.ThreadCommand
        """
        try:
            self.command_stage.emit(ThreadCommand(command="check_position"))

        except Exception as e:
            self.logger.exception(str(e))

    def move(self, move_command: MoveCommand):
        """Public method to trigger the correct action on the actuator. Should be used by external applications"""
        if move_command.move_type == 'abs':
            self.move_Abs(move_command.value)
        elif move_command.move_type == 'rel':
            self.move_Rel(move_command.value)
        elif move_command.move_type == 'home':
            self.move_Home(move_command.value)

    def move_Abs(self, position, send_to_tcpip=False):
        """
            | Make the move from an absolute position.
            |
            | The move is made if target is in bounds, sending the thread command "Reset_Stop_Motion" and "move_Abs".

            =============== ========== ===========================================
            **Parameters**   **Type**    **Description**

            *position*        float      The absolute target position of the move
            =============== ========== ===========================================

            See Also
            --------
            update_status, check_out_bounds, DAQ_utils.ThreadCommand
        """
        try:
            self.send_to_tcpip = send_to_tcpip
            if not (position == self.current_position
                    and self.stage_name == "Thorlabs_Flipper"):
                self.ui.Move_Done_LED.set_as_false()
                self.move_done_bool = False
                self.target_position = position
                self.update_status("Moving", wait_time=self.wait_time)
                # self.check_out_bounds(position)
                self.command_stage.emit(
                    ThreadCommand(command="Reset_Stop_Motion"))
                self.command_stage.emit(
                    ThreadCommand(command="move_Abs", attributes=[position]))

        except Exception as e:
            self.logger.exception(str(e))

    def move_Home(self, send_to_tcpip=False):
        """
            Send the thread commands "Reset_Stop_Motion" and "move_Home" and update the status.

            See Also
            --------
            update_status, DAQ_utils.ThreadCommand
        """
        self.send_to_tcpip = send_to_tcpip
        try:
            self.ui.Move_Done_LED.set_as_false()
            self.move_done_bool = False
            self.update_status("Moving", wait_time=self.wait_time)
            self.command_stage.emit(ThreadCommand(command="Reset_Stop_Motion"))
            self.command_stage.emit(ThreadCommand(command="move_Home"))

        except Exception as e:
            self.logger.exception(str(e))

    def move_Rel_p(self):
        self.ui.Move_Rel_plus_pb.click()

    def move_Rel_m(self, send_to_tcpip=False):
        self.ui.Move_Rel_minus_pb.click()

    def move_Rel(self, rel_position, send_to_tcpip=False):
        """
            | Make a move from the given relative psition and the current one.
            |
            | The move is done if (current position + relative position) is in bounds sending Threads Commands "Reset_Stop_Motion" and "move_done"

            =============== ========== ===================================================
            **Parameters**   **Type**    **Description**

            *position*        float     The relative target position from the current one
            =============== ========== ===================================================

            See Also
            --------
            update_status, check_out_bounds, DAQ_utils.ThreadCommand
        """
        try:
            self.send_to_tcpip = send_to_tcpip
            self.ui.Move_Done_LED.set_as_false()
            self.move_done_bool = False
            self.target_position = self.current_position + rel_position
            self.update_status("Moving", wait_time=self.wait_time)
            # self.check_out_bounds(self.target_position)
            self.command_stage.emit(ThreadCommand(command="Reset_Stop_Motion"))
            self.command_stage.emit(
                ThreadCommand(command="move_Rel", attributes=[rel_position]))

        except Exception as e:
            self.logger.exception(str(e))

    def parameter_tree_changed(self, param, changes):
        """
            | Check eventual changes in the changes list parameter.
            |
            | In case of changed values, emit the signal containing the current path and parameter via update_settings_signal to the connected hardware.

            =============== ====================================    ==================================================
            **Parameters**   **Type**                                **Description**

             *param*         instance of pyqtgraph parameter         The parameter to be checked

             *changes*       (parameter,change,infos) tuple list     The (parameter,change,infos) list to be treated
            =============== ====================================    ==================================================
        """

        for param, change, data in changes:
            path = self.settings.childPath(param)
            if path is not None:
                childName = '.'.join(path)
            else:
                childName = param.name()
            if change == 'childAdded':
                if 'main_settings' not in path:
                    self.update_settings_signal.emit(
                        edict(path=path,
                              param=data[0].saveState(),
                              change=change))

            elif change == 'value':

                if param.name() == 'connect_server':
                    if param.value():
                        self.connect_tcp_ip()
                    else:
                        self.command_tcpip.emit(ThreadCommand('quit'))

                elif param.name() == 'ip_address' or param.name == 'port':
                    self.command_tcpip.emit(
                        ThreadCommand(
                            'update_connection',
                            dict(ipaddress=self.settings.child(
                                'main_settings', 'tcpip',
                                'ip_address').value(),
                                 port=self.settings.child(
                                     'main_settings', 'tcpip',
                                     'port').value())))

                if path is not None:
                    if 'main_settings' not in path:
                        self.update_settings_signal.emit(
                            edict(path=path, param=param, change=change))
                        if self.settings.child('main_settings', 'tcpip',
                                               'tcp_connected').value():
                            self.command_tcpip.emit(
                                ThreadCommand('send_info',
                                              dict(path=path, param=param)))

            elif change == 'parent':
                if param.name() not in putils.iter_children(
                        self.settings.child('main_settings'), []):
                    self.update_settings_signal.emit(
                        edict(path=['move_settings'],
                              param=param,
                              change=change))

    def connect_tcp_ip(self):
        if self.settings.child('main_settings', 'tcpip',
                               'connect_server').value():
            self.tcpclient_thread = QThread()

            tcpclient = TCPClient(self.settings.child('main_settings', 'tcpip',
                                                      'ip_address').value(),
                                  self.settings.child('main_settings', 'tcpip',
                                                      'port').value(),
                                  self.settings.child(('move_settings')),
                                  client_type="ACTUATOR")
            tcpclient.moveToThread(self.tcpclient_thread)
            self.tcpclient_thread.tcpclient = tcpclient
            tcpclient.cmd_signal.connect(self.process_tcpip_cmds)

            self.command_tcpip[ThreadCommand].connect(tcpclient.queue_command)

            self.tcpclient_thread.start()
            tcpclient.init_connection()

    @Slot(ThreadCommand)
    def process_tcpip_cmds(self, status):
        if 'move_abs' in status.command:
            self.move_Abs(status.attributes[0], send_to_tcpip=True)

        elif 'move_rel' in status.command:
            self.move_Rel(status.attributes[0], send_to_tcpip=True)

        elif 'move_home' in status.command:
            self.move_Home(send_to_tcpip=True)

        elif 'check_position' in status.command:
            self.send_to_tcpip = True
            self.command_stage.emit(ThreadCommand('check_position'))

        elif status.command == 'connected':
            self.settings.child('main_settings', 'tcpip',
                                'tcp_connected').setValue(True)

        elif status.command == 'disconnected':
            self.settings.child('main_settings', 'tcpip',
                                'tcp_connected').setValue(False)

        elif status.command == 'Update_Status':
            self.thread_status(status)

        elif status.command == 'set_info':
            param_dict = ioxml.XML_string_to_parameter(status.attributes[1])[0]
            param_tmp = Parameter.create(**param_dict)
            param = self.settings.child('move_settings',
                                        *status.attributes[0][1:])

            param.restoreState(param_tmp.saveState())

    def quit_fun(self):
        """
            Leave the current instance of DAQ_Move_Main closing the parent widget.
        """
        # insert anything that needs to be closed before leaving
        try:
            if self.initialized_state:
                self.ui.IniStage_pb.click()

            self.parent.close()  # close the parent widget
            try:
                self.parent.parent().parent().close(
                )  # the dock parent (if any)
            except Exception as e:
                self.logger.info('No dock parent to close')

        except Exception as e:
            icon = QtGui.QIcon()
            icon.addPixmap(
                QtGui.QPixmap(":/Labview_icons/Icon_Library/close2.png"),
                QtGui.QIcon.Normal, QtGui.QIcon.Off)
            msgBox = QtWidgets.QMessageBox(parent=None)
            msgBox.addButton(QtWidgets.QMessageBox.Yes)
            msgBox.addButton(QtWidgets.QMessageBox.No)
            msgBox.setWindowTitle("Error")
            msgBox.setText(
                str(e) +
                " error happened when uninitializing the stage.\nDo you still want to quit?"
            )
            msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes)
            ret = msgBox.exec()
            if ret == QtWidgets.QMessageBox.Yes:
                self.parent.close()

    @Slot()
    def raise_timeout(self):
        """
            Update status with "Timeout occured" statement.

            See Also
            --------
            update_status
        """
        self.update_status("Timeout occured", wait_time=self.wait_time)
        self.wait_position_flag = False

    def set_enabled_move_buttons(self, enable=False):
        """
            Set the move buttons enabled (or not) in User Interface from the gridLayout_buttons course.

            =============== ========== ================================================
            **Parameters**   **Type**   **Description**

             *enable*        boolean    The parameter making enable or not the buttons
            =============== ========== ================================================

        """
        Nchildren = self.ui.gridLayout_buttons.count()
        for ind in range(Nchildren):
            widget = self.ui.gridLayout_buttons.itemAt(ind).widget()
            if widget is not None:
                widget.setEnabled(enable)
        self.ui.Moveto_pb_bis.setEnabled(enable)
        self.ui.Abs_position_sb_bis.setEnabled(enable)
        self.ui.Current_position_sb.setEnabled(enable)

    @Slot(int)
    def set_setting_tree(self, index=0):
        """
            Set the move settings parameters tree, clearing the current tree and setting the 'move_settings' node.

            See Also
            --------
            update_status
        """
        self.stage_name = self.ui.Stage_type_combo.currentText()
        self.settings.child('main_settings',
                            'move_type').setValue(self.stage_name)
        try:
            for child in self.settings.child(('move_settings')).children():
                child.remove()
            parent_module = utils.find_dict_in_list_from_key_val(
                DAQ_Move_Stage_type, 'name', self.stage_name)
            class_ = getattr(
                getattr(parent_module['module'],
                        'daq_move_' + self.stage_name),
                'DAQ_Move_' + self.stage_name)
            params = getattr(class_, 'params')
            move_params = Parameter.create(name='move_settings',
                                           type='group',
                                           children=params)

            self.settings.child(
                ('move_settings')).addChildren(move_params.children())

        except Exception as e:
            self.logger.exception(str(e))

    def show_fine_tuning(self):
        """
          Make GroupBox visible if User Interface corresponding attribute is checked to show fine tuning in.
        """
        if self.ui.fine_tuning_pb.isChecked():
            self.ui.groupBox.show()
        else:
            self.ui.groupBox.hide()

    def show_settings(self):
        """
          Make settings tree visible if User Interface corresponding attribute is checked to show the settings tree in.
        """
        if self.ui.parameters_pb.isChecked():

            self.ui.settings_tree.setVisible(True)
        else:
            self.ui.settings_tree.setVisible(False)

    @Slot(int)
    def stage_changed(self, index=0):
        """

            See Also
            --------
            move_Abs
        """
        pass

    def stop_Motion(self):
        """
            stop any motion via the launched thread with the "stop_Motion" Thread Command.

            See Also
            --------
            update_status, DAQ_utils.ThreadCommand
        """
        try:
            self.command_stage.emit(ThreadCommand(command="stop_Motion"))
        except Exception as e:
            self.logger.exception(str(e))

    @Slot(ThreadCommand)
    def thread_status(
        self, status
    ):  # general function to get datas/infos from all threads back to the main
        """
            | General function to get datas/infos from all threads back to the main0
            |

            Interpret a command from the command given by the ThreadCommand status :
                * In case of **'Update_status'** command, call the update_status method with status attributes as parameters
                * In case of **'ini_stage'** command, initialise a Stage from status attributes
                * In case of **'close'** command, close the launched stage thread
                * In case of **'check_position'** command, set the Current_position value from status attributes
                * In case of **'move_done'** command, set the Current_position value, make profile of move_done and send the move done signal with status attributes
                * In case of **'Move_Not_Done'** command, set the current position value from the status attributes, make profile of Not_Move_Done and send the Thread Command "Move_abs"
                * In case of **'update_settings'** command, create child "Move Settings" from  status attributes (if possible)

            ================ ================= ======================================================
            **Parameters**     **Type**         **Description**

            *status*          ThreadCommand()   instance of ThreadCommand containing two attributes :

                                                 * *command*    str
                                                 * *attributes* list

            ================ ================= ======================================================

            See Also
            --------
            update_status, set_enabled_move_buttons, get_position, DAQ_utils.ThreadCommand, parameter_tree_changed, raise_timeout
        """

        if status.command == "Update_Status":
            if len(status.attributes) > 2:
                self.update_status(status.attributes[0],
                                   wait_time=self.wait_time,
                                   log_type=status.attributes[1])
            else:
                self.update_status(status.attributes[0],
                                   wait_time=self.wait_time)

        elif status.command == "ini_stage":
            # status.attributes[0]=edict(initialized=bool,info="", controller=)
            self.update_status("Stage initialized: {:} info: {:}".format(
                status.attributes[0]['initialized'],
                status.attributes[0]['info']),
                               wait_time=self.wait_time)
            if status.attributes[0]['initialized']:
                self.controller = status.attributes[0]['controller']
                self.set_enabled_move_buttons(enable=True)
                self.ui.Ini_state_LED.set_as_true()
                self.initialized_state = True
            else:
                self.initialized_state = False
            if self.initialized_state:
                self.get_position()
            self.init_signal.emit(self.initialized_state)

        elif status.command == "close":
            try:
                self.update_status(status.attributes[0],
                                   wait_time=self.wait_time)
                self.stage_thread.exit()
                self.stage_thread.wait()
                finished = self.stage_thread.isFinished()
                if finished:
                    pass
                    delattr(self, 'stage_thread')
                else:
                    self.update_status('thread is locked?!', self.wait_time,
                                       'log')
            except Exception as e:
                self.logger.exception(str(e))
            self.initialized_state = False
            self.init_signal.emit(self.initialized_state)

        elif status.command == "check_position":
            self.ui.Current_position_sb.setValue(status.attributes[0])
            self.current_position = status.attributes[0]
            if self.settings.child(
                    'main_settings', 'tcpip',
                    'tcp_connected').value() and self.send_to_tcpip:
                self.command_tcpip.emit(
                    ThreadCommand('position_is', status.attributes))

        elif status.command == "move_done":
            self.ui.Current_position_sb.setValue(status.attributes[0])
            self.current_position = status.attributes[0]
            self.move_done_bool = True
            self.ui.Move_Done_LED.set_as_true()
            self.move_done_signal.emit(self.title, status.attributes[0])
            if self.settings.child(
                    'main_settings', 'tcpip',
                    'tcp_connected').value() and self.send_to_tcpip:
                self.command_tcpip.emit(
                    ThreadCommand('move_done', status.attributes))

        elif status.command == "Move_Not_Done":
            self.ui.Current_position_sb.setValue(status.attributes[0])
            self.current_position = status.attributes[0]
            self.move_done_bool = False
            self.ui.Move_Done_LED.set_as_false()
            self.command_stage.emit(
                ThreadCommand(command="move_Abs",
                              attributes=[self.target_position]))

        elif status.command == 'update_main_settings':
            # this is a way for the plugins to update main settings of the ui (solely values, limits and options)
            try:
                if status.attributes[2] == 'value':
                    self.settings.child('main_settings',
                                        *status.attributes[0]).setValue(
                                            status.attributes[1])
                elif status.attributes[2] == 'limits':
                    self.settings.child('main_settings',
                                        *status.attributes[0]).setLimits(
                                            status.attributes[1])
                elif status.attributes[2] == 'options':
                    self.settings.child(
                        'main_settings',
                        *status.attributes[0]).setOpts(**status.attributes[1])
            except Exception as e:
                self.logger.exception(str(e))

        elif status.command == 'update_settings':
            # ThreadCommand(command='update_settings',attributes=[path,data,change]))
            try:
                self.settings.sigTreeStateChanged.disconnect(
                    self.parameter_tree_changed
                )  # any changes on the settings will update accordingly the detector
            except Exception:
                pass
            try:
                if status.attributes[2] == 'value':
                    self.settings.child('move_settings',
                                        *status.attributes[0]).setValue(
                                            status.attributes[1])
                elif status.attributes[2] == 'limits':
                    self.settings.child('move_settings',
                                        *status.attributes[0]).setLimits(
                                            status.attributes[1])
                elif status.attributes[2] == 'options':
                    self.settings.child(
                        'move_settings',
                        *status.attributes[0]).setOpts(**status.attributes[1])
                elif status.attributes[2] == 'childAdded':
                    child = Parameter.create(name='tmp')
                    child.restoreState(status.attributes[1][0])
                    self.settings.child('move_settings',
                                        *status.attributes[0]).addChild(
                                            status.attributes[1][0])

            except Exception as e:
                self.logger.exception(str(e))
            self.settings.sigTreeStateChanged.connect(
                self.parameter_tree_changed
            )  # any changes on the settings will update accordingly the detector

        elif status.command == 'raise_timeout':
            self.raise_timeout()

        elif status.command == 'outofbounds':
            self.bounds_signal.emit(True)

        elif status.command == 'show_splash':
            self.ui.settings_tree.setEnabled(False)
            self.splash_sc.show()
            self.splash_sc.raise_()
            self.splash_sc.showMessage(status.attributes[0], color=Qt.white)

        elif status.command == 'close_splash':
            self.splash_sc.close()
            self.ui.settings_tree.setEnabled(True)

        elif status.command == 'set_allowed_values':
            if 'decimals' in status.attributes:
                self.ui.Current_position_sb.setDecimals(
                    status.attributes['decimals'])
                self.ui.Abs_position_sb.setDecimals(
                    status.attributes['decimals'])
                self.ui.Abs_position_sb_bis.setDecimals(
                    status.attributes['decimals'])
            if 'minimum' in status.attributes:
                self.ui.Current_position_sb.setMinimum(
                    status.attributes['minimum'])
                self.ui.Abs_position_sb.setMinimum(
                    status.attributes['minimum'])
                self.ui.Abs_position_sb_bis.setMinimum(
                    status.attributes['minimum'])
            if 'maximum' in status.attributes:
                self.ui.Current_position_sb.setMaximum(
                    status.attributes['maximum'])
                self.ui.Abs_position_sb.setMaximum(
                    status.attributes['maximum'])
                self.ui.Abs_position_sb_bis.setMaximum(
                    status.attributes['maximum'])
            if 'step' in status.attributes:
                self.ui.Current_position_sb.setSingleStep(
                    status.attributes['step'])
                self.ui.Abs_position_sb.setSingleStep(
                    status.attributes['step'])
                self.ui.Abs_position_sb_bis.setSingleStep(
                    status.attributes['step'])

    def update_status(self, txt, wait_time=0):
        """
            Show the given txt message in the status bar with a delay of wait_time ms if specified (0 by default).

            ================ ========== =================================
            **Parameters**    **Type**   **Description**
             *txt*            string     The message to show
             *wait_time*      int        The delay time of showing
            ================ ========== =================================

        """

        self.ui.statusbar.showMessage(txt, wait_time)
        self.status_signal.emit(txt)
        self.logger.info(txt)
Example #11
0
class MainApp(QWidget):
    LANDSCAPE = 0
    PORTRAIT = 1

    stop_signal = Signal()
    quality_changed = Signal(str)
    style_changed = Signal(object)
    last_frame_changed = Signal(object)

    def __init__(self):
        QWidget.__init__(self)

        self.styles = load_styles()
        self.image = StyledCapture(QImage(256, 256, QImage.Format_RGB888),
                                   '')  # placeholder
        self.freeze = None
        if isinstance(settings.CAPTURE_HANDLER, string_types):
            self.capture_handler = locate(settings.CAPTURE_HANDLER)
        else:
            self.capture_handler = settings.CAPTURE_HANDLER
        self.setup_ui()

        self.frame_grabber = FrameGrabber(settings.SIZES[0])
        self.frame_thread = QThread()
        # self.frame_grabber.image_signal.connect(self.display_frame)
        self.frame_grabber.last_frame_signal.connect(self.last_frame)
        self.frame_grabber.moveToThread(self.frame_thread)
        self.frame_thread.started.connect(self.frame_grabber.grab)
        self.stop_signal.connect(self.frame_grabber.stop_work)
        self.quality_changed.connect(self.frame_grabber.change_size)
        self.frame_thread.start()

        self.image_processor = ImageProcessor(self.styles[0])
        self.image_thread = QThread()
        self.image_processor.image_signal.connect(self.display_frame)
        self.image_processor.moveToThread(self.image_thread)
        self.image_thread.started.connect(self.image_processor.monitor_images)
        self.stop_signal.connect(self.image_processor.stop_work)
        self.style_changed.connect(self.image_processor.change_style)
        self.last_frame_changed.connect(self.image_processor.change_last_frame)
        self.image_thread.start()

    def closeEvent(self, event):
        self.stop_signal.emit()
        self.frame_thread.quit()
        self.image_thread.quit()
        self.frame_thread.wait()
        self.image_thread.wait()

    def setup_ui(self):
        """Initialize widgets."""
        def switch_style(i):
            view = self.landscape_view if self.view_mode == MainApp.LANDSCAPE else self.portrait_view
            self.style_changed.emit(self.styles[i])
            view.selected_style = self.styles[i]

        for i in range(min(len(self.styles), len(settings.STYLE_SHORTCUTS))):
            QShortcut(QKeySequence(settings.STYLE_SHORTCUTS[i]),
                      self,
                      lambda x=i: switch_style(x))

        self.landscape_view = LandscapeView(self.styles)
        self.landscape_view.style_changed.connect(self.style_button_clicked)
        self.landscape_view.toggle_fullscreen_signal.connect(
            self.toggle_fullscreen)
        self.landscape_view.quality_changed.connect(self.quality_choice)
        self.portrait_view = PortraitView(self.styles)
        self.portrait_view.style_changed.connect(self.style_button_clicked)
        self.portrait_view.toggle_fullscreen_signal.connect(
            self.toggle_fullscreen)
        self.portrait_view.quality_changed.connect(self.quality_choice)

        self.main_layout = QStackedLayout()
        self.main_layout.addWidget(self.landscape_view)
        self.main_layout.addWidget(self.portrait_view)
        self.setLayout(self.main_layout)

        self.view_mode = MainApp.LANDSCAPE

        self.setStyleSheet('background-color:black;'
                           'font-family: Arial;'
                           'font-style: normal;'
                           'font-size: 12pt;'
                           'font-weight: bold;'
                           'color:white;')
        self.setWindowTitle('Stylize')

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape and not settings.KIOSK:
            if self.windowState() & Qt.WindowFullScreen:
                self.showNormal()
        elif event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
            self.image_capture()

    def last_frame(self, frame):
        self.last_frame_changed.emit(frame)

    def display_frame(self, image):
        self.image = image
        if not self.freeze:
            if self.view_mode == MainApp.LANDSCAPE:
                self.landscape_view.set_image(self.image.image)
            else:
                self.portrait_view.set_image(self.image.image)

    def resizeEvent(self, event):
        super(MainApp, self).resizeEvent(event)
        new_view_mode = MainApp.LANDSCAPE if self.width() >= self.height(
        ) else MainApp.PORTRAIT

        if self.view_mode != new_view_mode:
            old_view = self.portrait_view if new_view_mode == MainApp.LANDSCAPE else self.landscape_view
            new_view = self.landscape_view if new_view_mode == MainApp.LANDSCAPE else self.portrait_view

            new_view.quality = old_view.quality
            new_view.selected_style = old_view.selected_style

            self.view_mode = new_view_mode
            self.main_layout.setCurrentIndex(self.view_mode)

    def style_button_clicked(self, style):
        self.style_changed.emit(style)

    def toggle_fullscreen(self):
        if self.windowState() & Qt.WindowFullScreen:
            self.showNormal()
        else:
            self.showFullScreen()

    def quality_choice(self, quality):
        self.quality_changed.emit(quality)

    def image_capture(self):
        self.freeze = self.image.copy()  # prevent background update
        try:
            self.capture_handler(self, self.freeze)
        except Exception:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Critical)
            msg.setText('Error during capture.')
            msg.setWindowFlags(Qt.FramelessWindowHint | Qt.Popup)
            msg.exec_()

        self.freeze = None
Example #12
0
class SnippetsActor(QObject):
    #: Signal emitted when the Thread is ready
    sig_snippets_ready = Signal()
    sig_snippets_response = Signal(int, dict)
    sig_update_snippets = Signal(dict)
    sig_mailbox = Signal(dict)

    def __init__(self, parent):
        QObject.__init__(self)
        self.stopped = False
        self.daemon = True
        self.mutex = QMutex()
        self.language_snippets = {}
        self.thread = QThread(None)
        self.moveToThread(self.thread)

        self.thread.started.connect(self.started)
        self.sig_mailbox.connect(self.handle_msg)
        self.sig_update_snippets.connect(self.update_snippets)

    def stop(self):
        """Stop actor."""
        with QMutexLocker(self.mutex):
            logger.debug("Snippets plugin stopping...")
            self.thread.quit()
            self.thread.wait()

    def start(self):
        """Start thread."""
        self.thread.start()

    def started(self):
        """Thread started."""
        logger.debug('Snippets plugin starting...')
        self.sig_snippets_ready.emit()

    @Slot(dict)
    def update_snippets(self, snippets):
        """Update available snippets."""
        logger.debug('Updating snippets...')
        for language in snippets:
            lang_snippets = snippets[language]
            lang_trie = Trie()
            for trigger in lang_snippets:
                trigger_descriptions = lang_snippets[trigger]
                lang_trie[trigger] = (trigger, trigger_descriptions)
            self.language_snippets[language] = lang_trie

    @Slot(dict)
    def handle_msg(self, message):
        """Handle one message"""
        msg_type, _id, file, msg = [
            message[k] for k in ('type', 'id', 'file', 'msg')]
        logger.debug(u'Perform request {0} with id {1}'.format(msg_type, _id))
        if msg_type == CompletionRequestTypes.DOCUMENT_COMPLETION:
            language = msg['language']
            current_word = msg['current_word']
            snippets = []

            if current_word is None:
                snippets = {'params': snippets}
                self.sig_snippets_response.emit(_id, snippets)
                return

            if language in self.language_snippets:
                language_snippets = self.language_snippets[language]
                if language_snippets[current_word]:
                    for node in language_snippets[current_word]:
                        trigger, info = node.value
                        for description in info:
                            description_snippet = info[description]
                            text = description_snippet['text']
                            remove_trigger = description_snippet[
                                'remove_trigger']
                            snippets.append({
                                'kind': CompletionItemKind.SNIPPET,
                                'insertText': text,
                                'label': f'{trigger} ({description})',
                                'sortText': f'zzz{trigger}',
                                'filterText': trigger,
                                'documentation': '',
                                'provider': SNIPPETS_COMPLETION,
                                'remove_trigger': remove_trigger
                            })

            snippets = {'params': snippets}
            self.sig_snippets_response.emit(_id, snippets)
Example #13
0
class QThreadedClient(QObject):
    """Threaded socket client.

    Available signals:
        - finished(): When client thread stops.
        - closed(): After closing socket.
        - connected(ip: str, port: int): Successfully connected to server.
        - message(data: dict): Received message from server.
        - disconnected(): Disconnected from server.
        - error(error_string: str): Socket error.
        - failed_to_connect(ip: str, port: int): Failed to connect to server.

    Available slots:
        - start(): Start client.
        - write(data: bytes): Write message to server.
        - reconnect(): Reconnect to server.
        - close(): Close connection.
        - disconnect_from_server(): Disconnect from server.
        - connect_to(ip: str, port: int): (Re)connect to server.
    """
    finished = Signal()
    closed = Signal()
    connected = Signal(str, int)
    message = Signal(bytes)
    disconnected = Signal()
    error = Signal(str)
    failed_to_connect = Signal(str, int)

    def __init__(self, loggerName=None):
        super(QThreadedClient, self).__init__(None)
        self.__ip = None
        self.__port = None

        self.__client = None
        self.__client_thread = None
        self.__logger_name = loggerName

    @Slot(str, int)
    def start(self, ip: str, port: int):
        """Start client thread and connect to server."""
        self.__ip = ip
        self.__port = port

        self.__client = _SocketClient(self.__ip,
                                      self.__port,
                                      loggerName=self.__logger_name)
        self.__client_thread = QThread()

        self.__client_thread.started.connect(self.__client.start)
        self.__client_thread.finished.connect(self.finished.emit)

        self.__client.moveToThread(self.__client_thread)

        self.__client.connected.connect(self.on_connected)
        self.__client.failed_to_connect.connect(self.on_failed_to_connect)
        self.__client.message.connect(self.on_message)
        self.__client.disconnected.connect(self.on_disconnected)
        self.__client.error.connect(self.on_error)
        self.__client.closed.connect(self.on_closed)

        self.__client_thread.start()

    @Slot(str, int)
    def on_connected(self, ip, port):
        """Called when client connects to server.
        Emits connected signal.

        Args:
            ip (str): Client ip address.
            port (int): Client port.
        """
        self.connected.emit(ip, port)

    @Slot(str, int)
    def on_failed_to_connect(self, ip, port):
        """Called when client fails to connect to server.
        Emits failed_to_connect signal.

        Args:
            ip (str): Client ip address.
            port (int): Client port.
        """
        self.failed_to_connect.emit(ip, port)

    @Slot(bytes)
    def on_message(self, message: bytes):
        """Called when client receives message from server.
        Emits message signal.

        Args:
            message (bytes): Message.
        """
        self.message.emit(message)

    @Slot()
    def on_disconnected(self):
        """Called when device disconnects from server.
        Emits disconnected signal."""
        self.disconnected.emit()

    @Slot(str)
    def on_error(self, error: str):
        """Called when a socket error occurs.
        Emits error signal.

        Args:
            error (str): Error string.
        """
        self.error.emit(error)

    @Slot()
    def on_closed(self):
        """Called when when the socket is closed.
        Emits closed signal."""
        self.closed.emit()

    @Slot(bytes)
    def write(self, data: bytes):
        """Write data to server.

        Args:
            data (bytes): Data to write.
        """
        self.__client.write_signal.emit(data)

    @Slot()
    def close(self):
        """Disconnect from server and close socket."""
        if self.__client and self.__client_thread:
            self.__client.close_signal.emit()
            self.__client_thread.quit()
        else:
            self.error.emit("Client not running")

    @Slot()
    def disconnect_from_server(self):
        """Disconnect from server."""
        self.__client.disconnect_signal.emit()

    @Slot(str, int)
    def connect_to(self, ip: str, port: int):
        """(Re)connect to server.

        Args:
            ip (str): IP address.
            port (int): Port.
        """
        self.__client.connect_signal.emit(ip, port)

    @Slot()
    def reconnect(self):
        self.__client.reconnect_signal.emit()

    @Slot()
    def is_running(self):
        """Check if server is running"""
        if self.__client_thread:
            return self.__client_thread.isRunning()
        return False

    @Slot()
    def wait(self):
        """Wait for server thread to finish."""
        if self.__client_thread:
            return self.__client_thread.wait()
        return True
Example #14
0
class ApplicationContainer(PluginMainContainer):

    sig_report_issue_requested = Signal()
    """
    Signal to request reporting an issue to Github.
    """

    sig_load_log_file = Signal(str)
    """
    Signal to load a log file
    """
    def __init__(self, name, plugin, parent=None):
        super().__init__(name, plugin, parent)

        # Keep track of dpi message
        self.current_dpi = None
        self.dpi_messagebox = None

    # ---- PluginMainContainer API
    # -------------------------------------------------------------------------
    def setup(self):
        # Compute dependencies in a thread to not block the interface.
        self.dependencies_thread = QThread(None)

        # Attributes
        self.dialog_manager = DialogManager()
        self.give_updates_feedback = False
        self.thread_updates = None
        self.worker_updates = None
        self.updates_timer = None

        # Actions
        # Documentation actions
        self.documentation_action = self.create_action(
            ApplicationActions.SpyderDocumentationAction,
            text=_("Spyder documentation"),
            icon=self.create_icon("DialogHelpButton"),
            triggered=lambda: start_file(__docs_url__),
            context=Qt.ApplicationShortcut,
            register_shortcut=True,
            shortcut_context="_")

        spyder_video_url = ("https://www.youtube.com/playlist"
                            "?list=PLPonohdiDqg9epClEcXoAPUiK0pN5eRoc")
        self.video_action = self.create_action(
            ApplicationActions.SpyderDocumentationVideoAction,
            text=_("Tutorial videos"),
            icon=self.create_icon("VideoIcon"),
            triggered=lambda: start_file(spyder_video_url))

        # Support actions
        self.trouble_action = self.create_action(
            ApplicationActions.SpyderTroubleshootingAction,
            _("Troubleshooting..."),
            triggered=lambda: start_file(__trouble_url__))
        self.report_action = self.create_action(
            ConsoleActions.SpyderReportAction,
            _("Report issue..."),
            icon=self.create_icon('bug'),
            triggered=self.sig_report_issue_requested)
        self.dependencies_action = self.create_action(
            ApplicationActions.SpyderDependenciesAction,
            _("Dependencies..."),
            triggered=self.show_dependencies,
            icon=self.create_icon('advanced'))
        self.check_updates_action = self.create_action(
            ApplicationActions.SpyderCheckUpdatesAction,
            _("Check for updates..."),
            triggered=self.check_updates)
        self.support_group_action = self.create_action(
            ApplicationActions.SpyderSupportAction,
            _("Spyder support..."),
            triggered=lambda: start_file(__forum_url__))

        # About action
        self.about_action = self.create_action(
            ApplicationActions.SpyderAbout,
            _("About %s...") % "Spyder",
            icon=self.create_icon('MessageBoxInformation'),
            triggered=self.show_about,
            menurole=QAction.AboutRole)

        # Tools actions
        if WinUserEnvDialog is not None:
            self.winenv_action = self.create_action(
                ApplicationActions.SpyderWindowsEnvVariables,
                _("Current user environment variables..."),
                icon=self.create_icon('win_env'),
                tip=_("Show and edit current user environment "
                      "variables in Windows registry "
                      "(i.e. for all sessions)"),
                triggered=self.show_windows_env_variables)
        else:
            self.winenv_action = None

        # Application base actions
        self.restart_action = self.create_action(
            ApplicationActions.SpyderRestart,
            _("&Restart"),
            icon=self.create_icon('restart'),
            tip=_("Restart"),
            triggered=self.restart_normal,
            context=Qt.ApplicationShortcut,
            shortcut_context="_",
            register_shortcut=True)

        self.restart_debug_action = self.create_action(
            ApplicationActions.SpyderRestartDebug,
            _("&Restart in debug mode"),
            tip=_("Restart in debug mode"),
            triggered=self.restart_debug,
            context=Qt.ApplicationShortcut,
            shortcut_context="_",
            register_shortcut=True)

        # Debug logs
        if get_debug_level() >= 2:
            self.menu_debug_logs = self.create_menu(
                ApplicationPluginMenus.DebugLogsMenu, _("Debug logs"))

            # The menu can't be built at startup because Completions can
            # start after Application.
            self.menu_debug_logs.aboutToShow.connect(
                self.create_debug_log_actions)

    def update_actions(self):
        pass

    # ---- Other functionality
    # -------------------------------------------------------------------------
    def on_close(self):
        """To call from Spyder when the plugin is closed."""
        self.dialog_manager.close_all()
        if self.updates_timer is not None:
            self.updates_timer.stop()
        if self.thread_updates is not None:
            self.thread_updates.quit()
            self.thread_updates.wait()
        if self.dependencies_thread is not None:
            self.dependencies_thread.quit()
            self.dependencies_thread.wait()

    @Slot()
    def show_about(self):
        """Show Spyder About dialog."""
        abt = AboutDialog(self)
        abt.show()

    @Slot()
    def show_windows_env_variables(self):
        """Show Windows current user environment variables."""
        self.dialog_manager.show(WinUserEnvDialog(self))

    # ---- Updates
    # -------------------------------------------------------------------------
    def _check_updates_ready(self):
        """Show results of the Spyder update checking process."""

        # `feedback` = False is used on startup, so only positive feedback is
        # given. `feedback` = True is used when after startup (when using the
        # menu action, and gives feeback if updates are, or are not found.
        feedback = self.give_updates_feedback

        # Get results from worker
        update_available = self.worker_updates.update_available
        latest_release = self.worker_updates.latest_release
        error_msg = self.worker_updates.error

        # Release url
        if sys.platform == 'darwin':
            url_r = ('https://github.com/spyder-ide/spyder/releases/latest/'
                     'download/Spyder.dmg')
        else:
            url_r = ('https://github.com/spyder-ide/spyder/releases/latest/'
                     'download/Spyder_64bit_full.exe')
        url_i = 'https://docs.spyder-ide.org/installation.html'

        # Define the custom QMessageBox
        box = MessageCheckBox(icon=QMessageBox.Information, parent=self)
        box.setWindowTitle(_("New Spyder version"))
        box.setAttribute(Qt.WA_ShowWithoutActivating)
        box.set_checkbox_text(_("Check for updates at startup"))
        box.setStandardButtons(QMessageBox.Ok)
        box.setDefaultButton(QMessageBox.Ok)

        # Adjust the checkbox depending on the stored configuration
        option = 'check_updates_on_startup'
        check_updates = self.get_conf(option)
        box.set_checked(check_updates)

        if error_msg is not None:
            msg = error_msg
            box.setText(msg)
            box.set_check_visible(False)
            box.exec_()
            check_updates = box.is_checked()
        else:
            if update_available:
                header = _("<b>Spyder {} is available!</b><br><br>").format(
                    latest_release)
                footer = _(
                    "For more information visit our "
                    "<a href=\"{}\">installation guide</a>.").format(url_i)
                if is_anaconda():
                    content = _(
                        "<b>Important note:</b> Since you installed "
                        "Spyder with Anaconda, please <b>don't</b> use "
                        "<code>pip</code> to update it as that will break "
                        "your installation.<br><br>"
                        "Instead, run the following commands in a "
                        "terminal:<br>"
                        "<code>conda update anaconda</code><br>"
                        "<code>conda install spyder={}</code><br><br>").format(
                            latest_release)
                else:
                    content = _("Click <a href=\"{}\">this link</a> to "
                                "download it.<br><br>").format(url_r)
                msg = header + content + footer
                box.setText(msg)
                box.set_check_visible(True)
                box.show()
                check_updates = box.is_checked()
            elif feedback:
                msg = _("Spyder is up to date.")
                box.setText(msg)
                box.set_check_visible(False)
                box.exec_()
                check_updates = box.is_checked()

        # Update checkbox based on user interaction
        self.set_conf(option, check_updates)

        # Enable check_updates_action after the thread has finished
        self.check_updates_action.setDisabled(False)

        # Provide feeback when clicking menu if check on startup is on
        self.give_updates_feedback = True

    @Slot()
    def check_updates(self, startup=False):
        """Check for spyder updates on github releases using a QThread."""
        # Disable check_updates_action while the thread is working
        self.check_updates_action.setDisabled(True)

        if self.thread_updates is not None:
            self.thread_updates.quit()
            self.thread_updates.wait()

        self.thread_updates = QThread(None)
        self.worker_updates = WorkerUpdates(self, startup=startup)
        self.worker_updates.sig_ready.connect(self._check_updates_ready)
        self.worker_updates.sig_ready.connect(self.thread_updates.quit)
        self.worker_updates.moveToThread(self.thread_updates)
        self.thread_updates.started.connect(self.worker_updates.start)

        # Delay starting this check to avoid blocking the main window
        # while loading.
        # Fixes spyder-ide/spyder#15839
        self.updates_timer = QTimer(self)
        self.updates_timer.setInterval(3000)
        self.updates_timer.setSingleShot(True)
        self.updates_timer.timeout.connect(self.thread_updates.start)
        self.updates_timer.start()

    # ---- Dependencies
    # -------------------------------------------------------------------------
    @Slot()
    def show_dependencies(self):
        """Show Spyder Dependencies dialog."""
        # This is here in case the user tries to display the dialog before
        # dependencies_thread has finished.
        if not dependencies.DEPENDENCIES:
            dependencies.declare_dependencies()

        dlg = DependenciesDialog(self)
        dlg.set_data(dependencies.DEPENDENCIES)
        dlg.show()

    def _compute_dependencies(self):
        """Compute dependencies without errors."""
        # Skip error when trying to register dependencies several times.
        # This can happen if the user tries to display the dependencies
        # dialog before dependencies_thread has finished.
        try:
            dependencies.declare_dependencies()
        except ValueError:
            pass

    def compute_dependencies(self):
        """Compute dependencies."""
        self.dependencies_thread.run = self._compute_dependencies
        self.dependencies_thread.finished.connect(
            self.report_missing_dependencies)

        # This avoids computing missing deps before the window is fully up
        dependencies_timer = QTimer(self)
        dependencies_timer.setInterval(10000)
        dependencies_timer.setSingleShot(True)
        dependencies_timer.timeout.connect(self.dependencies_thread.start)
        dependencies_timer.start()

    @Slot()
    def report_missing_dependencies(self):
        """Show a QMessageBox with a list of missing hard dependencies."""
        missing_deps = dependencies.missing_dependencies()

        if missing_deps:
            InstallerMissingDependencies(missing_deps)

            # We change '<br>' by '\n', in order to replace the '<'
            # that appear in our deps by '&lt' (to not break html
            # formatting) and finally we restore '<br>' again.
            missing_deps = (missing_deps.replace('<br>', '\n').replace(
                '<', '&lt;').replace('\n', '<br>'))

            message = (
                _("<b>You have missing dependencies!</b>"
                  "<br><br><tt>%s</tt><br>"
                  "<b>Please install them to avoid this message.</b>"
                  "<br><br>"
                  "<i>Note</i>: Spyder could work without some of these "
                  "dependencies, however to have a smooth experience when "
                  "using Spyder we <i>strongly</i> recommend you to install "
                  "all the listed missing dependencies.<br><br>"
                  "Failing to install these dependencies might result in bugs."
                  " Please be sure that any found bugs are not the direct "
                  "result of missing dependencies, prior to reporting a new "
                  "issue.") % missing_deps)

            message_box = QMessageBox(self)
            message_box.setIcon(QMessageBox.Critical)
            message_box.setAttribute(Qt.WA_DeleteOnClose)
            message_box.setAttribute(Qt.WA_ShowWithoutActivating)
            message_box.setStandardButtons(QMessageBox.Ok)
            message_box.setWindowModality(Qt.NonModal)
            message_box.setWindowTitle(_('Error'))
            message_box.setText(message)
            message_box.show()

    # ---- Restart
    # -------------------------------------------------------------------------
    @Slot()
    def restart_normal(self):
        """Restart in standard mode."""
        os.environ['SPYDER_DEBUG'] = ''
        self.sig_restart_requested.emit()

    @Slot()
    def restart_debug(self):
        """Restart in debug mode."""
        box = QMessageBox(self)
        box.setWindowTitle(_("Question"))
        box.setIcon(QMessageBox.Question)
        box.setText(_("Which debug mode do you want Spyder to restart in?"))

        button_verbose = QPushButton(_('Verbose'))
        button_minimal = QPushButton(_('Minimal'))
        box.addButton(button_verbose, QMessageBox.AcceptRole)
        box.addButton(button_minimal, QMessageBox.AcceptRole)
        box.setStandardButtons(QMessageBox.Cancel)
        box.exec_()

        if box.clickedButton() == button_minimal:
            os.environ['SPYDER_DEBUG'] = '2'
        elif box.clickedButton() == button_verbose:
            os.environ['SPYDER_DEBUG'] = '3'
        else:
            return

        self.sig_restart_requested.emit()

    # ---- Log files
    # -------------------------------------------------------------------------
    def create_debug_log_actions(self):
        """Create an action for each lsp and debug log file."""
        self.menu_debug_logs.clear_actions()

        files = [os.environ['SPYDER_DEBUG_FILE']]
        files += glob.glob(os.path.join(get_conf_path('lsp_logs'), '*.log'))

        debug_logs_actions = []
        for file in files:
            action = self.create_action(
                file,
                os.path.basename(file),
                tip=file,
                triggered=lambda _, file=file: self.load_log_file(file),
                overwrite=True,
                register_action=False)
            debug_logs_actions.append(action)

        # Add Spyder log on its own section
        self.add_item_to_menu(debug_logs_actions[0],
                              self.menu_debug_logs,
                              section=LogsMenuSections.SpyderLogSection)

        # Add LSP logs
        for action in debug_logs_actions[1:]:
            self.add_item_to_menu(action,
                                  self.menu_debug_logs,
                                  section=LogsMenuSections.LSPLogsSection)

        # Render menu
        self.menu_debug_logs._render()

    def load_log_file(self, file):
        """Load log file in editor"""
        self.sig_load_log_file.emit(file)

    # ---- DPI changes
    # -------------------------------------------------------------------------
    def set_window(self, window):
        """Set window property of main window."""
        self._window = window

    def handle_new_screen(self, new_screen):
        """Connect DPI signals for new screen."""
        if new_screen is not None:
            new_screen_dpi = new_screen.logicalDotsPerInch()
            if self.current_dpi != new_screen_dpi:
                self.show_dpi_change_message(new_screen_dpi)
            else:
                new_screen.logicalDotsPerInchChanged.connect(
                    self.show_dpi_change_message)

    def handle_dpi_change_response(self, result, dpi):
        """Handle dpi change message dialog result."""
        if self.dpi_messagebox.is_checked():
            self.set_conf('show_dpi_message', False)

        self.dpi_messagebox = None

        if result == 0:  # Restart button was clicked
            # Activate HDPI auto-scaling option since is needed for a
            # proper display when using OS scaling
            self.set_conf('normal_screen_resolution', False)
            self.set_conf('high_dpi_scaling', True)
            self.set_conf('high_dpi_custom_scale_factor', False)
            self.sig_restart_requested.emit()
        else:
            # Update current dpi for future checks
            self.current_dpi = dpi

    def show_dpi_change_message(self, dpi):
        """Show message to restart Spyder since the DPI scale changed."""
        if not self.get_conf('show_dpi_message'):
            return

        if self.current_dpi != dpi:
            # Check the window state to not show the message if the window
            # is in fullscreen mode.
            window = self._window.windowHandle()
            if (window.windowState() == Qt.WindowFullScreen
                    and sys.platform == 'darwin'):
                return

            if self.get_conf('high_dpi_scaling'):
                return

            if self.dpi_messagebox is not None:
                self.dpi_messagebox.activateWindow()
                self.dpi_messagebox.raise_()
                return

            self.dpi_messagebox = MessageCheckBox(icon=QMessageBox.Warning,
                                                  parent=self)

            self.dpi_messagebox.set_checkbox_text(_("Don't show again."))
            self.dpi_messagebox.set_checked(False)
            self.dpi_messagebox.set_check_visible(True)

            self.dpi_messagebox.setText(
                _("A monitor scale change was detected. <br><br>"
                  "We recommend restarting Spyder to ensure that it's properly "
                  "displayed. If you don't want to do that, please be sure to "
                  "activate the option<br><br><tt>Enable auto high DPI scaling"
                  "</tt><br><br>in <tt>Preferences > Application > "
                  "Interface</tt>, in case Spyder is not displayed "
                  "correctly.<br><br>"
                  "Do you want to restart Spyder?"))

            self.dpi_messagebox.addButton(_('Restart now'), QMessageBox.NoRole)
            dismiss_button = self.dpi_messagebox.addButton(
                _('Dismiss'), QMessageBox.NoRole)
            self.dpi_messagebox.setDefaultButton(dismiss_button)
            self.dpi_messagebox.finished.connect(
                lambda result: self.handle_dpi_change_response(result, dpi))
            self.dpi_messagebox.open()

            # Show dialog always in the primary screen to prevent not being
            # able to see it if a screen gets disconnected while
            # in suspended state. See spyder-ide/spyder#16390
            dpi_messagebox_width = self.dpi_messagebox.rect().width()
            dpi_messagebox_height = self.dpi_messagebox.rect().height()
            screen_geometry = QGuiApplication.primaryScreen().geometry()
            x = (screen_geometry.width() - dpi_messagebox_width) / 2
            y = (screen_geometry.height() - dpi_messagebox_height) / 2

            # Convert coordinates to int to avoid a TypeError in Python 3.10
            # Fixes spyder-ide/spyder#17677
            self.dpi_messagebox.move(int(x), int(y))
            self.dpi_messagebox.adjustSize()
Example #15
0
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        #self.verticalLayout_2 = QVBoxLayout()
        self.tableHeaders = ['VT', 'I:E', 'RR', 'FIO2']
        self.widget = uic.loadUi(_UI, self)
        window_title = "Rhythm"
        self.json = JsonSettings("settings.json")
        self.settings_dict = self.json.dict
        pprint.pprint(self.settings_dict)
        self.table = QTableWidget(self)
        self.table.setRowCount(2)
        self.table.setColumnCount(5)
        self.table.setHorizontalHeaderLabels(self.tableHeaders)
        self.table.setItem(0,0, QTableWidgetItem(self.settings_dict[r"vt"]))
        self.table.setItem(0,1, QTableWidgetItem(self.settings_dict[r"ie"]))
        self.table.setItem(0,2, QTableWidgetItem(self.settings_dict[r"rr"]))
        self.table.setItem(0,3, QTableWidgetItem(self.settings_dict[r"fio2"]))
        #self.table.itemChanged.connect(self.SaveSettings)
        self.vt = int(self.settings_dict[r"vt"])
        self.rr = int(self.settings_dict[r"rr"])
        self.ie = int(self.settings_dict[r"ie"])
        self.fio2 = int(self.settings_dict[r"fio2"])
        self.verticalLayout_2.addWidget(self.table)

        self.generator = GcodeGenerator(self.vt, self.rr, self.ie, self.fio2)

        self.motion_table = QTableWidget(self)
        self.motion_table_headers = ['variables', 'values']
        self.motion_table.setColumnCount(2)
        self.motion_table.setRowCount(10)
        self.motion_table.setHorizontalHeaderLabels(self.motion_table_headers)
        self.motion_table.hide()

        self.dat = deque()
        self.datpeak = deque()

        self.lungpressure_line_pen = pg.mkPen(200, 100, 0)
        self.plotter = PlotWidget()
        self.plotter.showGrid(x=True, y=True, alpha=None)
        self.curve1 = self.plotter.plot(0,0,"lungpressure", 'b')
        self.curve2 = self.plotter.plot(0,0,"peakpressure", pen = self.lungpressure_line_pen)
        
        #self.motion_table.setSizeAdjustPolicy(QtWidget.QAbstractScrollArea.AdjustToContents)
        self.CalculateSettings()
        self.verticalLayout_2.addWidget(self.motion_table)
        self.verticalLayout_2.addWidget(self.plotter)
        self.motion_table.hide()

        self.gcodetable = QTableWidget(self)
        self.gcodetable.setRowCount(1)
        self.gcodetable.setColumnCount(1)
        #self.verticalLayout_2.addWidget(self.gcodetable)

        self.hbox = QHBoxLayout()
        #self.verticalLayout_2.addChildLayout(self.hbox)
        self.verticalLayout_2.addLayout(self.hbox)
        self.hbox.addWidget(self.gcodetable)
        self.txrxtable = QTableWidget()
        self.txrxtable.setRowCount(1)
        self.txrxtable.setColumnCount(1)
        self.hbox.addWidget(self.txrxtable)
        #self.hbox.addLayout

        self.peepdial.valueChanged.connect(self.peepDialChanged)
        self.peeplcd.display(self.peepdial.value())
        self.peakdial.valueChanged.connect(self.peakDialChanged)
        self.peaklcd.display(self.peakdial.value())
        self.ipapdial.valueChanged.connect(self.ipapDialChanged)
        self.ipaplcd.display(self.ipapdial.value())
        self.vtdial.valueChanged.connect(self.vtDialChanged)
        self.vtlcd.display(self.vtdial.value())
        self.iedial.valueChanged.connect(self.ieDialChanged)
        self.ielcd.display(self.iedial.value())
        self.rrdial.valueChanged.connect(self.rrDialChanged)
        self.rrlcd.display(self.rrdial.value())
        self.fiodial.valueChanged.connect(self.fioDialChanged)
        self.fiolcd.display(self.fiodial.value())

        self.s = ""
        self.s2 = ""
        self.ports = list(port_list.comports())

        self.primaryThreadCreated = False
        self.workerThreadCreated = False
        self.sensorThreadCreated = False
        self.serialPortOpen = False
        self.serialSensorOpen = False

    def update_param_table(self):
        self.table.setItem(0,0, QTableWidgetItem(self.settings_dict[r"vt"]))
        self.table.setItem(0,1, QTableWidgetItem(self.settings_dict[r"ie"]))
        self.table.setItem(0,2, QTableWidgetItem(self.settings_dict[r"rr"]))
        self.table.setItem(0,3, QTableWidgetItem(self.settings_dict[r"fio2"]))

    def peepDialChanged(self):
        self.peeplcd.display(self.peepdial.value())

    def peakDialChanged(self):
        self.peaklcd.display(self.peakdial.value())

    def ipapDialChanged(self):
        self.ipaplcd.display(self.ipapdial.value())

    def vtDialChanged(self):
        self.vtlcd.display(self.vtdial.value())
        self.vt = self.vtdial.value()
        self.settings_dict[r"vt"] = str(self.vt)
        self.update_param_table()
        self.SaveSettings()

    def ieDialChanged(self):
        self.table.setItem(0,1, QTableWidgetItem(self.settings_dict[r"ie"]))
        self.ielcd.display(self.iedial.value())
        self.ie = self.iedial.value()
        self.settings_dict[r"ie"] = str(self.ie)
        self.update_param_table()
        self.SaveSettings()

    def rrDialChanged(self):
        self.rrlcd.display(self.rrdial.value())
        self.rr = self.rrdial.value()
        self.settings_dict[r"rr"] = str(self.rr)
        self.update_param_table()
        self.SaveSettings()

    def fioDialChanged(self):
        self.fiolcd.display(self.fiodial.value())
        self.fio2 = self.fiodial.value()
        self.settings_dict[r"fio2"] = str(self.fio2)
        self.update_param_table()
        self.SaveSettings()

    
    def ShowGcodeTable(self):
        codelist = self.generator.gcodestr.splitlines()
        rowcount = len(codelist)
        self.gcodetable.setRowCount(rowcount)
        self.gcodetable.setColumnCount(1)
        for i in range(rowcount):
            self.gcodetable.setItem(i, 0, QTableWidgetItem(codelist[i]))

    def CalculateSettings(self):
        del self.generator
        ###self.json = JsonSettings("settings.json")
        ###self.settings_dict = self.json.dict
        #self.vt = int(self.settings_dict[r"vt"])
        #self.rr = int(self.settings_dict[r"rr"])
        #self.ie = int(self.settings_dict[r"ie"])
        #self.fio2 = int(self.settings_dict[r"fio2"])
        self.generator = GcodeGenerator(self.vt, self.rr, self.ie, self.fio2)
        self.motion_table.setItem(0,0, QTableWidgetItem('Dp'))
        self.motion_table.setItem(0,1, QTableWidgetItem(str(self.generator.Dp)))
        self.motion_table.setItem(1,0, QTableWidgetItem('Dr'))
        self.motion_table.setItem(1,1, QTableWidgetItem(str(self.generator.Dr)))
        self.motion_table.setItem(2,0, QTableWidgetItem('Dt'))
        self.motion_table.setItem(2,1, QTableWidgetItem(str(self.generator.Dt)))
        self.motion_table.setItem(3,0, QTableWidgetItem('Ti'))
        self.motion_table.setItem(3,1, QTableWidgetItem(str(self.generator.Ti)))
        self.motion_table.setItem(4,0, QTableWidgetItem('Th'))
        self.motion_table.setItem(4,1, QTableWidgetItem(str(self.generator.Th)))
        self.motion_table.setItem(5,0, QTableWidgetItem('Vi'))
        self.motion_table.setItem(5,1, QTableWidgetItem(str(self.generator.Vi)))
        self.motion_table.setItem(6,0, QTableWidgetItem('Vh'))
        self.motion_table.setItem(6,1, QTableWidgetItem(str(self.generator.Vh)))

    def sensorData(self, data_stream):
        #print(data_stream.split(','))
        lst = data_stream.split(",")
        self.maxLen = 400  # max number of data points to show on graph
        if(len(lst) > 1):
            if len(self.dat) > self.maxLen:
                self.dat.popleft()  # remove oldest
            if len(self.datpeak) > self.maxLen:
                self.datpeak.popleft()
            self.datpeak.append(float(self.peakdial.value()))
            self.dat.append(float(lst[1]))
            self.curve1.setData(self.dat)
            self.curve2.setData(self.datpeak)

    def write_info(self, data_stream):
        rcount = self.txrxtable.rowCount()
        self.txrxtable.insertRow(rcount)
        self.txrxtable.setItem(rcount,0, QTableWidgetItem(data_stream))
        self.txrxtable.scrollToBottom()
        self.txrxtable.resizeColumnsToContents()
        self.txrxtable.resizeRowsToContents()
        if data_stream == "Stopped":
            if self.primaryThreadCreated:
                self.primaryThread.exit()
                self.primaryThread.wait()
                self.primaryThreadCreated = False
                del self.primaryThread

        #elif data_stream == "Loop":
        #    self.primaryThread.exit() ###
        #    self.worker = WorkerThread(self.s, self.generator)
        #    self.workerThread = QThread()
        #    self.workerThread.started.connect(self.worker.run)
        #    self.worker.signal.connect(self.write_info)
        #    self.worker.moveToThread(self.workerThread)
        #    self.workerThread.start()
        #    self.workerThreadCreated = True
        #    print("Starting Worker Thread")

        #if data_stream == "Stopped":
        #    self.worker = WorkerThread(self.s)
        #    self.workerThread = QThread()
        #    self.workerThread.started.connect(self.worker.run)
        #    self.worker.signal.connect(self.write_info)
        #    self.worker.moveToThread(self.workerThread)
        #    self.workerThread.start()
        #    print("Starting Thread")

    @Slot()
    def on_gengcode_clicked(self):
        self.CalculateSettings()
        self.generator.Generate()
        self.ShowGcodeTable()

    @Slot()
    def on_scanPorts_clicked(self):
        self.ports = list(port_list.comports())
        self.widget.portsList.clear()
        self.widget.monitoringPort.clear()
        print(len(self.ports))
        for p in self.ports:
            self.widget.portsList.addItem(p[0])
            self.widget.monitoringPort.addItem(p[0])

    @Slot()
    def on_btninit_clicked(self):
        if not self.workerThreadCreated:
            if not self.primaryThreadCreated:
                self.primary = PrimaryThread(self.s, self.generator)
                self.primaryThread = QThread()
                self.primaryThread.started.connect(self.primary.run)
                self.primary.signal.connect(self.write_info)
                self.primary.moveToThread(self.primaryThread)
                self.primaryThread.start()
                self.primaryThreadCreated = True
                print("Starting Primary Thread")
        if self.serialSensorOpen:
            if not self.sensorThreadCreated:
                self.sensor = SensorThread(self.s2)
                self.sensorThread = QThread()
                self.sensorThread.started.connect(self.sensor.run)
                self.sensor.signal.connect(self.sensorData)
                self.sensor.moveToThread(self.sensorThread)
                self.sensorThread.start()
                self.sensorThreadCreated = True
                print("Starting Sensor Thread ...")

    @Slot()
    def on_runloop_clicked(self):
        if not self.primaryThreadCreated:
            if not self.workerThreadCreated:
                self.worker = WorkerThread(self.s, self.generator)
                self.workerThread = QThread()
                self.workerThread.started.connect(self.worker.run)
                self.worker.signal.connect(self.write_info)
                self.worker.moveToThread(self.workerThread)
                self.workerThread.start()
                self.workerThreadCreated = True
                print("Starting Worker Thread")

    @Slot()
    def on_disconnect_clicked(self):
        if self.serialPortOpen:
            if self.workerThreadCreated:
                self.worker.Stop()
                self.workerThread.exit()
                self.workerThread.wait()
                self.workerThreadCreated = False
                del self.workerThread
            if self.primaryThreadCreated:
                self.primaryThread.exit()
                self.primaryThread.wait()
                self.primaryThreadCreated = False
                del self.primaryThread
            if self.sensorThreadCreated:
                self.sensor.Stop()
                self.sensorThread.exit()
                self.sensorThread.wait()
                self.sensorThreadCreated = False
            self.s.close()
            if self.serialSensorOpen:
                self.s2.close()
            self.serialPortOpen = False
            self.serialSensorOpen = False

    @Slot()
    def on_connect_clicked(self):
        try:
            if not self.serialPortOpen:
                print("Serial Port Name : " + self.portsList.currentText())
                self.s = serial.Serial(self.portsList.currentText(), baudrate=115200, timeout=1)
                #self.s.open()
                time.sleep(1)
                self.serialPortOpen = True
                #self.s.write("\r\n\r\n") # Hit enter a few times to wake the Printrbot
                #time.sleep(2)   # Wait for Printrbot to initialize
                while self.s.in_waiting:
                    self.s.readline()
                    #print(self.s.readline().decode("ascii"))
                #self.s.flushInput()  # Flush startup text in serial input
                #monitoringPort
            if not self.serialSensorOpen:
                self.s2 = serial.Serial(self.monitoringPort.currentText(), baudrate=115200, timeout=1)
                self.serialSensorOpen = True
            
        except serial.SerialException as ex:
            self.serialPortOpen = False
            print(ex.strerror)
            print("Error Opening Serial Port..........................................")
           
    @Slot()
    def on_btnCPAP_clicked(self):
        if self.btnCPAP.isChecked():
            #self.btnCPAP.toggle()
            self.btnCPAP.setStyleSheet('QPushButton {background-color: #7F7F7F}')
        else:
            self.btnCPAP.setStyleSheet('QPushButton {background-color: #404040}')

    def SaveSettings(self):
        ###self.json = JsonSettings("settings.json")
        ###self.settings_dict = self.json.dict
        self.json.dict[r'vt'] = str(self.vt)
        self.json.dict[r'ie'] = str(self.ie)
        self.json.dict[r'rr'] = str(self.rr)
        self.json.dict[r'fio2'] = str(self.fio2)
        self.generator = GcodeGenerator(self.vt, self.rr, self.ie, self.fio2)
        self.generator.Generate()
        if self.workerThreadCreated:
            self.worker.updateGcode(self.generator)
        pprint.pprint(self.generator.gcodestr)
        #self.json.dumptojson()
        ###self.vt = int(self.settings_dict[r"vt"])
        ###self.rr = int(self.settings_dict[r"rr"])
        ###self.ie = int(self.settings_dict[r"ie"])
        ###self.fio2 = int(self.settings_dict[r"fio2"])
        self.CalculateSettings()
Example #16
0
class QBaseServer(QObject):
    """Server base for QtPyNetwork."""
    connected = Signal(Device, str, int)
    disconnected = Signal(Device)
    message = Signal(Device, bytes)
    error = Signal(Device, str)

    server_error = Signal(str)
    closed = Signal()

    def __init__(self, loggerName=None):
        super(QBaseServer, self).__init__()
        if loggerName:
            self.__logger = logging.getLogger(loggerName)
        else:
            self.__logger = logging.getLogger(self.__class__.__name__)
        self.__ip = None
        self.__port = None

        self.__devices = []
        self.__deviceModel = Device

        self.__handler = None
        self.__handler_thread = None
        self.__handlerClass = None
        self.__server = None

    @Slot(str, int, bytes)
    def start(self, ip: str, port: int):
        """Start server."""
        if self.__handlerClass:
            ip = QHostAddress(ip)
            self.__ip = ip
            self.__port = port
            self.__handler = self.__handlerClass()
            self.__handler_thread = QThread()
            self.__handler.moveToThread(self.__handler_thread)

            self.__handler.connected.connect(
                self.__on_handler_successful_connection)
            self.__handler.message.connect(self.__on_handler_device_message)
            self.__handler.error.connect(self.__on_handler_device_error)
            self.__handler.disconnected.connect(
                self.__on_handler_device_disconnected)
            self.__handler.closed.connect(self.on_closed)

            self.__handler_thread.started.connect(self.__handler.start)
            self.__handler.started.connect(self.__setup_server)
            self.__handler_thread.start()
        else:
            raise Exception("Handler class not set!")

    @Slot()
    def __setup_server(self):
        """Create QTCPServer, start listening for connections."""
        self.__server = TCPServer()
        self.__server.connection.connect(self.__handler.on_incoming_connection)
        if self.__server.listen(self.__ip, self.__port):
            self.__logger.info("Started listening for connections")
        else:
            e = self.__server.errorString()
            self.__logger.error(e)
            self.server_error.emit(e)

    @Slot(int, str, int)
    def __on_handler_successful_connection(self, device_id, ip, port):
        """When client connects to server successfully."""
        device = self.__deviceModel(self, device_id, ip, port)
        self.__devices.append(device)
        self.__logger.info("Added new CLIENT-{} with address {}:{}".format(
            device_id, ip, port))
        self.on_connected(device, ip, port)

    @Slot(int, bytes)
    def __on_handler_device_message(self, device_id: int, message: bytes):
        """When server receives message from bot."""
        self.on_message(self.get_device_by_id(device_id), message)

    @Slot(int)
    def __on_handler_device_disconnected(self, device_id):
        """When bot disconnects from server."""
        device = self.get_device_by_id(device_id)
        device.set_connected(False)
        if device in self.__devices:
            self.__devices.remove(device)
        self.on_disconnected(device)

    @Slot(int, str)
    def __on_handler_device_error(self, device_id, error):
        self.on_error(self.get_device_by_id(device_id), error)

    @Slot(Device, str, int)
    def on_connected(self, device: Device, ip: str, port: int):
        """Called when new client connects to server.
        Emits connected signal.

        Args:
            device (Device): Device object.
            ip (str): Client ip address.
            port (int): Client port.
        """
        self.connected.emit(device, ip, port)

    @Slot(Device, bytes)
    def on_message(self, device: Device, message: bytes):
        """Called when server receives message from client.
        Emits message signal.

        Args:
            device (Device): Message sender.
            message (bytes): Message.
        """
        self.message.emit(device, message)

    @Slot(Device)
    def on_disconnected(self, device: Device):
        """Called when device disconnects from server.
        Emits disconnected signal.

        Args:
            device (Device): Disconnected device.
        """
        self.disconnected.emit(device)

    @Slot(Device, str)
    def on_error(self, device: Device, error: str):
        """Called when a socket error occurs.
        Emits error signal.

        Args:
            device (Device): Device object.
            error (str): Error string.
        """
        self.error.emit(device, error)

    @Slot()
    def on_closed(self):
        self.closed.emit()

    @Slot(Device, bytes)
    def write(self, device: Device, data: bytes):
        """Write data to device."""
        if not self.__server or not self.__handler:
            raise ServerNotRunning("Server is not running")
        if not device.is_connected():
            raise NotConnectedError("Client is not connected")
        self.__handler.write.emit(device.id(), data)

    @Slot(bytes)
    def write_all(self, data: bytes):
        """Write data to all devices."""
        if not self.__server or not self.__handler:
            raise ServerNotRunning("Server is not running")
        self.__handler.write_all.emit(data)

    @Slot()
    def kick(self, device: Device):
        """Disconnect device from server."""
        if not self.__server or not self.__handler:
            raise ServerNotRunning("Server is not running")
        if not device.is_connected():
            raise NotConnectedError("Client is not connected")
        self.__handler.kick.emit(device.id())

    @Slot()
    def close(self):
        """Disconnect clients and close server."""
        self.__logger.info("Closing server...")
        if self.__server:
            self.__server.close()
        if self.__handler:
            self.__handler.close()
            self.__handler_thread.quit()

    def set_device_model(self, model):
        """Set model to use for device when client connects.

        Note:
            Model should be subclassing Device.
        """
        if self.is_running():
            raise Exception("Set device model before starting server!")

        if not issubclass(model, Device):
            raise ValueError("Model should be subclassing Device class.")

        try:
            model(QBaseServer(), 0, "127.0.0.1", 5000)
        except TypeError as e:
            raise TypeError(
                "Model is not valid class! Exception: {}".format(e))

        self.__deviceModel = model

    def is_running(self):
        """Check if server is running."""
        if self.__handler_thread:
            return self.__handler_thread.isRunning()
        return False

    def wait(self):
        """Wait for server thread to close."""
        if self.__handler_thread:
            return self.__handler_thread.wait()
        return True

    @Slot(int)
    def get_device_by_id(self, device_id: int) -> Device:
        """Returns device with associated ID.

        Args:
            device_id (int): Device ID.
        """
        for device in self.__devices:
            if device.id() == device_id:
                return device
        raise Exception("CLIENT-{} not found".format(device_id))

    def get_devices(self):
        """Returns list with devices."""
        return self.__devices

    def set_handler_class(self, handler):
        """Set handler to use. This should not be used
        outside this library."""
        if self.is_running():
            raise Exception("Set socket handler before starting server!")
        try:
            handler()
        except TypeError as e:
            raise TypeError(
                "Handler is not valid class! Exception: {}".format(e))
        self.__handlerClass = handler
Example #17
0
class ProcessConsole(QTextEdit):
    signal_stop_qthread = Signal()
    signal_process_stopped = Signal()
    signal_process_started = Signal()
    signal_hyperlink_clicked = Signal(str)
    signal_goto_file = Signal(str, int)
    insert_mode = ''

    def __init__(self, args: list = None):
        super().__init__()
        self.anchor = None
        self._is_running = False
        self.auto_scroll = True
        self.args = args  #
        self.setContentsMargins(20, 20, 0, 0)
        self.monitor_thread: 'ProcessMonitorThread' = None
        self.out_thread: 'QThread' = None
        self.signal_hyperlink_clicked.connect(self.on_hyperlink_clicked)

    def set_args(self, args: list = None):
        self.args = args

    def is_running(self):
        if self.monitor_thread is not None:
            if self.monitor_thread.process.process.poll() is None:
                return True
        return False

    def start_process(self):
        if not self.is_running():
            self.out_thread = QThread(self)
            self.monitor_thread = ProcessMonitorThread()
            self.monitor_thread.args = self.args
            self.monitor_thread.moveToThread(self.out_thread)

            self.out_thread.started.connect(self.monitor_thread.run)
            self.out_thread.start()

            self.monitor_thread.on_out.connect(self.on_stdout)
            self.monitor_thread.on_err.connect(self.on_stderr)

            self.signal_stop_qthread.connect(self.monitor_thread.stop)

            self.out_thread.finished.connect(self.out_thread.deleteLater)
            self.out_thread.finished.connect(self.monitor_thread.deleteLater)

            self.monitor_thread.on_finished.connect(self.terminate_process)
            self.clear()
            self.insertHtml('<p style="color:#0063c5;">' +
                            ' '.join(self.args) + '<br/></p>')
            self.insertHtml('<p style="color:#aaaaaa;">' + '' + '</p>')

    def on_stdout(self, text):

        if self.insert_mode == 'error':
            self.insert_mode = 'stdout'

        self.insertHtml(
            self.insertHtml('<p style="color:#aaaaaa;">' + text + '<br/></p>'))
        if self.auto_scroll:
            self.ensureCursorVisible()

    def on_stderr(self, text):
        self.insert_mode = 'error'
        # result = re.search(r'(/|([a-zA-Z]:((\\)|/))).*:[0-9].*', text)
        result = re.search(r'(/|([a-zA-Z]:((\\)|/))).* line [0-9].*,', text)
        print(result)
        if result is None:
            self.insertHtml('<p style="color:red;">' + text + '<br/></p>')
        else:
            span = result.span()
            self.insertHtml('<p style="color:red;">' + text[:span[0]] + '</p>')
            print(result.group())
            self.insert_hyperlink(text[span[0]:span[1]])
            # self.insertHtml('<p style="color:red;">' + text[result[0]:result[1]] + '</p>')
            self.insertHtml('<p style="color:red;">' + text[span[1]:] +
                            '<br/></p>')
        if self.auto_scroll:
            self.ensureCursorVisible()

    def terminate_process(self):
        if self.monitor_thread is not None:
            self.monitor_thread.process_terminated = True
            self.monitor_thread.process.process.terminate()
            if self.out_thread.isRunning():
                self.signal_stop_qthread.emit()
                self.out_thread.quit()
                self.out_thread.wait(500)
        self.monitor_thread = None
        self.out_thread = None
        self.signal_process_stopped.emit()

    def keyPressEvent(self, e: 'QKeyEvent'):
        if e.key() == Qt.Key_Backspace or e.key() == Qt.Key_Delete:
            return
        print(e.key(), e.text())
        if e.key() == Qt.Key_Return:
            text = '\n'
        else:
            text = e.text()
        if text != '' and self.monitor_thread is not None:
            try:
                print('sent:', text)
                self.monitor_thread.process.process.stdin.write(
                    text.encode('utf8'))
                self.monitor_thread.process.process.stdin.flush()
            except:
                import traceback
                traceback.print_exc()
        super(ProcessConsole, self).keyPressEvent(e)

    def mousePressEvent(self, e):
        super(ProcessConsole, self).mousePressEvent(e)
        self.anchor = self.anchorAt(e.pos())
        # if self.anchor:
        #     QApplication.setOverrideCursor(Qt.PointingHandCursor)

    def mouseMoveEvent(self, e):
        super(ProcessConsole, self).mouseMoveEvent(e)
        self.anchor = self.anchorAt(e.pos())
        if not self.anchor:
            QApplication.setOverrideCursor(Qt.ArrowCursor)
        else:
            QApplication.setOverrideCursor(Qt.PointingHandCursor)

    def mouseReleaseEvent(self, e):
        super(ProcessConsole, self).mouseReleaseEvent(e)
        if self.anchor:
            # QDesktopServices.openUrl(QUrl(self.anchor))
            # QApplication.setOverrideCursor(Qt.ArrowCursor)
            self.signal_hyperlink_clicked.emit(self.anchor)
            self.anchor = None

    def insert_hyperlink(self, link: str, text=''):
        cursor = self.textCursor()
        fmt = cursor.charFormat()
        fmt.setForeground(QColor('#0063c5'))
        # address = 'http://example.com'
        fmt.setAnchor(True)
        fmt.setAnchorHref(link)
        fmt.setToolTip(link)
        if text == '':
            cursor.insertText(link, fmt)
        else:
            cursor.insertText(text, fmt)

    def on_hyperlink_clicked(self, hyperlink_text: str):
        if re.search(r'(/|([a-zA-Z]:((\\)|/))).* line [0-9].*,',
                     hyperlink_text) is not None:
            try:
                l = hyperlink_text.split('line')
                assert len(l) == 2, l
                for i in [0, 1]:
                    l[i] = l[i].strip(', \"\'')
                file_path, row_str = l
                row = int(row_str)
                if not os.path.exists(file_path):
                    QMessageBox.warning(self, self.tr('Warning'),
                                        self.tr('文件%s不存在!' % file_path))
                else:
                    self.signal_goto_file.emit(file_path, row)
                    logger.info('goto file %s, line %d' % (file_path, row))
            except:
                import traceback
                traceback.print_exc()