class MatcherUI(QDialog): thread_invoker = pyqtSignal() def __init__(self, argv, terminate=False): super(MatcherUI, self).__init__() self.btnStop = QPushButton('Stop') self.image_pane = QLabel() self.progress = QProgressBar(self) self.layout = QVBoxLayout() self.layout.addWidget(self.btnStop) self.layout.addWidget(self.progress) self.layout.addWidget(self.image_pane) self.setLayout(self.layout) self.thread = QThread() self.thread.start() self.worker = Worker(argv) self.worker.moveToThread(self.thread) self.thread_invoker.connect(self.worker.task) self.thread_invoker.emit() self.worker.frameRendered.connect(self.updatePixmap) self.worker.finished.connect(self.finishIt) self.worker.progress.connect(self.progress.setValue) self.terminate = terminate self.btnStop.clicked.connect(lambda: self.worker.stop()) self.btnStop.clicked.connect(self.terminateIt) self.terminated = False def updatePixmap(self, image): #it is called only when the pixmap is really updated by the thread. #resize image in advance. #w,h = image.width(), image.height() #scaled_image = image.scaled(int(w*self.preview_ratio), int(h*self.preview_ratio)) pixmap = QPixmap.fromImage(image) self.image_pane.setPixmap(pixmap) #is it ok here? self.update() def terminateIt(self): self.close() if self.terminate: sys.exit(1) #terminated self.terminated = True def finishIt(self): self.close() def closeEvent(self, event): self.stop_thread() def stop_thread(self): self.worker.stop() self.thread.quit() self.thread.wait()
class AsyncThreadController(_AsyncAbstractController): def __init__(self, # See parent_. parent=None): super().__init__(parent) # Create a worker and a thread it runs in. This approach was # inspired by the example given in the QThread_ docs. self._worker = _AsyncWorker() self._workerThread = QThread(self) # Attach the worker to the thread's event queue. self._worker.moveToThread(self._workerThread) # Hook up signals. self._worker.startSignal.connect(self._worker.onStart) # Everything is ready. Start the worker thread, so it will # be ready for functions to run. self._workerThread.start() # See `_start`_. def _start(self, future): self._worker.startSignal.emit(future) # See `_terminate`_. def _terminate(self): # Shut down the thread the worker runs in. self._workerThread.quit() self._workerThread.wait() # Delete the worker, since it doesn't have a parent and therefore # won't be deleted by the Qt object tree. sip.delete(self._worker) del self._worker
class MetaDataCollector(QObject): sig_metadata_ready = pyqtSignal(Location, dict) sig_request_metadata = pyqtSignal(Location) sig_delete_metadatas = pyqtSignal(list) def __init__(self, vfs: StdioFilesystem) -> None: super().__init__() self._worker = MetaDataCollectorWorker(vfs) self._thread = QThread() self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.init) self.sig_request_metadata.connect(self._worker.on_metadata_requested) self.sig_delete_metadatas.connect(self._worker.on_delete_metadata_requested) self._worker.sig_metadata_ready.connect(self._on_metadata_ready) self._thread.start() def close(self): self._worker._close = True self._thread.quit() self._thread.wait() def request_metadata(self, location: Location) -> None: self.sig_request_metadata.emit(location) def request_delete_metadatas(self, locations: List[Location]) -> None: self.sig_delete_metadatas.emit(locations) def _on_metadata_ready(self, location: Location, metadata: Any): self.sig_metadata_ready.emit(location, metadata)
class AsyncThreadController(AsyncAbstractController): def __init__(self, # |parent| parent=None): super(AsyncThreadController, self).__init__(parent) # Create a worker and a thread it runs in. This approach was # inspired by example given in the `QThread docs # <http://qt-project.org/doc/qt-4.8/qthread.html>`_. self._worker = _AsyncWorker() self._workerThread = QThread(parent) # Attach the worker to the thread's event queue. self._worker.moveToThread(self._workerThread) # Hook up signals. self._worker.startSignal.connect(self._worker.onStart) # Everything is ready. Start the worker thread, so it will # be ready for functions to run. self._workerThread.start() # |_start| def _start(self, future): self._worker.startSignal.emit(future) # |terminate| def _terminate(self): # Shut down the thread the Worker runs in. self._workerThread.quit() self._workerThread.wait() # Finally, detach (and probably garbage collect) the objects # used by this class. del self._worker del self._workerThread
class MeasurementThread(QObject): specSignal = pyqtSignal(np.ndarray) progressSignal = pyqtSignal(float, str) finishSignal = pyqtSignal(np.ndarray) def __init__(self, spectrometer, parent=None): if getattr(self.__class__, '_has_instance', False): RuntimeError('Cannot create another instance') self.__class__._has_instance = True try: super(MeasurementThread, self).__init__(parent) self.spectrometer = spectrometer self.abort = False self.thread = QThread(parent) self.moveToThread(self.thread) self.thread.started.connect(self.process) self.thread.finished.connect(self.stop) except: (type, value, traceback) = sys.exc_info() sys.excepthook(type, value, traceback) def start(self): self.thread.start(QThread.HighPriority) @pyqtSlot() def stop(self): self.abort = True self.thread.quit() self.thread.wait(5000) def __del__(self): self.__class__.has_instance = False #try: # self.specSignal.disconnect() # self.progressSignal.disconnect() # self.finishSignal.disconnect() #except: # (type, value, traceback) = sys.exc_info() # sys.excepthook(type, value, traceback) def work(self): self.specSignal.emit(self.spec) @pyqtSlot() def process(self): while not self.abort: try: self.spec = self.spectrometer.intensities() self.spec = self.spec[0:1024] self.work() except: (type, value, traceback) = sys.exc_info() print(type) print(value) print(traceback) sys.excepthook(type, value, traceback)
class HistogramWidget(QWidget): def __init__(self, parent=None): super(HistogramWidget, self).__init__(parent) self.m_levels = 128 self.m_isBusy = False self.m_histogram = [] self.m_processor = FrameProcessor() self.m_processorThread = QThread() self.m_processor.moveToThread(self.m_processorThread) self.m_processor.histogramReady.connect(self.setHistogram) def __del__(self): self.m_processorThread.quit() self.m_processorThread.wait(10000) def setLevels(self, levels): self.m_levels = levels def processFrame(self, frame): print("In processFrame()") if self.m_isBusy: return self.m_isBusy = True print("Invoking method") QMetaObject.invokeMethod(self.m_processor, 'processFrame', Qt.QueuedConnection, Q_ARG(QVideoFrame, frame), Q_ARG(int, self.m_levels)) @pyqtSlot(list) def setHistogram(self, histogram): self.m_isBusy = False self.m_histogram = list(histogram) self.update() def paintEvent(self, event): painter = QPainter(self) if len(self.m_histogram) == 0: painter.fillRect(0, 0, self.width(), self.height(), QColor.fromRgb(0, 0, 0)) return barWidth = self.width() / float(len(self.m_histogram)) for i, value in enumerate(self.m_histogram): h = value * height() # Draw the level. painter.fillRect(barWidth * i, height() - h, barWidth * (i + 1), height(), Qt.red) # Clear the rest of the control. painter.fillRect(barWidth * i, 0, barWidth * (i + 1), height() - h, Qt.black)
class WorkerThread(QObject): sig_close_requested = pyqtSignal() def __init__(self) -> None: super().__init__() self._worker: Optional[Worker] = None self._thread: Optional[QThread] = None def set_worker(self, worker: Worker): """Sets the Worker associated with this thread. Note this function has to be called before connecting any signals, otherwise the signals won't be associated with the right thread. """ assert self._worker is None self._worker = worker self._thread = QThread() self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.on_thread_started) # close() is a blocking connection so the thread is properly # done after the signal was emit'ed and we don't have to fuss # around with sig_finished() and other stuff self.sig_close_requested.connect(self._worker.close, type=Qt.BlockingQueuedConnection) def start(self) -> None: assert self._worker is not None assert self._thread is not None self._thread.start() def is_running(self) -> bool: assert self._thread is not None return bool(self._thread.isRunning()) def close(self) -> None: assert self._thread is not None assert self._worker is not None assert self._worker._close is False, "WorkerThread.close() was called twice" self._worker._close = True self.sig_close_requested.emit() self._thread.quit() self._thread.wait()
class SearchStream(QObject): sig_close_requested = pyqtSignal() def __init__(self, abspath: str, pattern: str) -> None: super().__init__() self._worker = SearchStreamWorker(abspath, pattern) self._thread = QThread(self) self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.init) # close() is a blocking connection so the thread is properly # done after the signal was emit'ed and we don't have to fuss # around with sig_finished() and other stuff self.sig_close_requested.connect(self._worker.close, type=Qt.BlockingQueuedConnection) def start(self) -> None: self._thread.start() def close(self) -> None: assert self._worker._close is False self._worker._close = True self.sig_close_requested.emit() self._thread.quit() self._thread.wait() @property def sig_file_added(self): return self._worker.sig_file_added @property def sig_finished(self): return self._worker.sig_finished @property def sig_message(self): return self._worker.sig_message @property def sig_error(self): return self._worker.sig_error
class WorkerFacade(QObject): sig_thing_requested = pyqtSignal(str, types.FunctionType) sig_quit_requested = pyqtSignal() def __init__(self, name): super().__init__() self.quit = False self.name = name self.worker = Worker("worker-" + name) self.thread = QThread() self.worker.moveToThread(self.thread) # startup and shutdown self.thread.started.connect(self.worker.init) self.sig_quit_requested.connect(self.worker.deinit) self.worker.sig_finished.connect(self.thread.quit) # requests and receive self.worker.sig_thing_finished.connect(self.on_thing_finished) self.sig_thing_requested.connect(self.worker.on_thing_requested) self.thread.start() def request_thing(self, thing, callback): self.worker.thing_requested(thing, callback) # self.sig_thing_requested.emit(self.name + "-" + thing, callback) def on_thing_finished(self, thing, callback): callback(thing) def on_quit(self): if self.worker.quit: return print(self.name, "WorkerFacade.on_quit()") # This signal won't be received until all work is done!!!! self.sig_quit_requested.emit() # Thus notify worker via sidechannel to stop doing it's job self.worker.quit = True # waiting for the thread to finish print(self.name, "evdi waiting") evdi = QAbstractEventDispatcher.instance() while self.thread.wait(10) is False: evdi.processEvents(QEventLoop.AllEvents) print(self.name, "evdi waiting: done")
class ServerGame(QWidget): def __init__(self, name, port, parent=None): super(ServerGame, self).__init__(parent) self.qBoard = QMacroBoard(self.onButtonClick) self.statusBar = QLabel() self.statusBar.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.statusBar.hide() self.titleBar = QLabel() self.titleBar.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.titleBar.hide() layout = QVBoxLayout() layout.addWidget(self.titleBar) layout.addWidget(self.qBoard) layout.addWidget(self.statusBar) self.setLayout(layout) self.server = game.players.human.ServerPlayer(name, port) self.opponentConnected = False self.board = None self.last_click = None self.qBoard.updateBoard(game.boards.Macroboard()) self.qBoard.setClickEnabled(False) self.requestThread = QThread() self.requestWorker = RequestHandler(self) self.requestWorker.waitingRequest.connect(self.waitingOpponentMessage) self.requestWorker.requestAccepted.connect(self.moveRequest) self.requestWorker.error.connect(self.serverError) self.requestWorker.moveToThread(self.requestThread) self.requestThread.started.connect(self.requestWorker.run) self.requestWorker.terminated.connect(self.requestThread.quit) self.requestThread.start() def __del__(self): self.requestWorker.terminate() self.requestThread.wait() def onButtonClick(self): self.qBoard.setClickEnabled(False) button = self.sender() px, py = button.id // 9, button.id % 9 try: self.board.make_move(px, py) except game.boards.IllegalMoveError as err: print(err) self.qBoard.setClickEnabled(True) return self.qBoard.updateBoard(self.board) self.last_click = (px, py) self.requestWorker.wakeOnClick() def moveRequest(self): if not self.opponentConnected: self.displayMessage(self.requestWorker.opponentName + ' connected to you! Game on!') self.titleBar.setText('Game against ' + self.requestWorker.opponentName) self.titleBar.show() self.opponentConnected = True else: self.displayMessage('Your turn!') self.board = self.requestWorker.macroboard if self.board.state == game.boards.State.IN_PROGRESS: self.qBoard.setClickEnabled(True) else: self.announceGameResult() self.requestWorker.terminate() self.qBoard.updateBoard(self.board) def serverError(self, err): print('Server error:', err) self.displayMessage('Server err: ' + str(err)) def displayMessage(self, msg): self.statusBar.setText(msg) self.statusBar.show() def waitingOpponentMessage(self): if not self.opponentConnected: self.displayMessage('Waiting opponent to connect.') else: self.displayMessage('Waiting opponent to play.') def announceGameResult(self): message = '' result = self.board.state if result == game.boards.State.O_WON: message = 'Congrats! You won the game!' elif result == game.boards.State.X_WON: message = 'Sorry! You lost the game!' elif result == game.boards.State.DRAW: message = 'The game ended in a draw!' self.displayMessage(message) def end(self): self.requestWorker.terminate()
class Debugger(QObject): """ Represents the networked debugger client. """ ETX = b'\x03' # End transmission token. def __init__(self, host, port, proc=None): """ Instantiate given a host, port and process for the debug runner. """ self.host = host self.port = port self.proc = proc self.view = None # Set after instantiation. super().__init__() def start(self): """ Start the debugger session. """ self.listener_thread = QThread(self.view.view) self.command_handler = CommandBufferHandler(self) self.command_handler.moveToThread(self.listener_thread) self.command_handler.on_command.connect(self.on_command) self.command_handler.on_fail.connect(self.on_fail) self.listener_thread.started.connect(self.command_handler.worker) self.listener_thread.start() def on_command(self, command): """ Handle a command emitted by the client thread. """ event, data = json.loads(command) if hasattr(self, 'on_{}'.format(event)): getattr(self, 'on_{}'.format(event))(**data) def on_fail(self, message): """ Handle if there's a connection failure with the debug runner. """ logger.error(message) self.view.debug_on_fail(message) def stop(self): """ Shut down the debugger session. """ self.command_handler.stopped = True self.listener_thread.quit() self.listener_thread.wait() if self.proc is not None: self.output('quit') self.socket.shutdown(socket.SHUT_WR) if self.proc is not None: # Wait for the runner process to die. self.proc.wait() def output(self, event, **data): """ Send a command to the debug runner. """ try: dumped = json.dumps((event, data)).encode('utf-8') self.socket.sendall(dumped + Debugger.ETX) except OSError as e: logger.debug('Debugger client error.') logger.debug(e) except AttributeError as e: logger.debug('Debugger client not connected to runner.') logger.debug(e) def breakpoint(self, breakpoint): """ Given a breakpoint number or (filename, line), return an object representing the referenced breakpoint. """ try: if isinstance(breakpoint, tuple): filename, line = breakpoint filename = os.path.normcase(os.path.abspath(filename)) return self.bp_index[filename][line] else: return self.bp_list[breakpoint] except KeyError: raise UnknownBreakpoint() def breakpoints(self, filename): """ Return all the breakpoints associated with the referenced file. """ normalised = os.path.normcase(os.path.abspath(filename)) return self.bp_index.get(normalised, {}) # Commands that can be passed to the debug runner. def create_breakpoint(self, filename, line, temporary=False): """ Create a new, enabled breakpoint at the specified line of the given file. """ self.output('break', filename=filename, line=line, temporary=temporary) def enable_breakpoint(self, breakpoint): """ Enable an existing breakpoint. """ self.output('enable', bpnum=breakpoint.bpnum) def disable_breakpoint(self, breakpoint): """ Disable an existing breakpoint. """ self.output('disable', bpnum=breakpoint.bpnum) def ignore_breakpoint(self, breakpoint, count): """ Ignore an existing breakpoint for "count" iterations. (N.B. Use a count of 0 to restore the breakpoint. """ self.output('ignore', bpnum=breakpoint.bpnum, count=count) def clear_breakpoint(self, breakpoint): """ Clear an existing breakpoint. """ self.output('clear', bpnum=breakpoint.bpnum) def do_run(self): """ Run the debugger until the next breakpoint. """ self.output('continue') def do_step(self): """ Step through one stack frame. """ self.output('step') def do_next(self): """ Go to the next line in the current stack frame. """ self.output('next') def do_return(self): """ Return to the previous stack frame. """ self.output('return') # Handlers for events raised by the debug runner. These generally follow # the pattern of updating state in the client object to reflect that of # the debug runner, then calling a method in the UI layer to update the # GUI to reflect the changed state. def on_bootstrap(self, breakpoints): """ The runner has finished setting up. """ self.bp_index = {} self.bp_list = list([ True, ]) # Breakpoints count from 1 for bp_data in breakpoints: self.on_breakpoint_create(**bp_data) self.view.debug_on_bootstrap() def on_breakpoint_create(self, **bp_data): """ The runner has created a breakpoint. """ bp = Breakpoint(**bp_data) filename = os.path.normcase(os.path.abspath(bp.filename)) self.bp_index.setdefault(filename, {}).setdefault(bp.line, bp) self.bp_list.append(bp) if bp.enabled: self.view.debug_on_breakpoint_enable(bp) else: self.view.debug_on_breakpoint_disable(bp) def on_breakpoint_enable(self, bpnum): """ The runner has enabled the breakpoint referenced by breakpoint number. """ bp = self.bp_list[bpnum] bp.enabled = True self.view.debug_on_breakpoint_enable(bp) def on_breakpoint_disable(self, bpnum): """ The runner has disabled a breakpoint referenced by breakpoint number. """ bp = self.bp_list[bpnum] bp.enabled = False self.view.debug_on_breakpoint_disable(bp) def on_breakpoint_ignore(self, bpnum, count): """ The runner will ignore the referenced breakpoint "count" iterations. """ bp = self.bp_list[bpnum] bp.ignore = count self.view.debug_on_breakpoint_ignore(bp, count) def on_breakpoint_clear(self, bpnum): """ The runner has cleared the referenced breakpoint. """ bp = self.bp_list[bpnum] self.view.debug_on_breakpoint_clear(bp) def on_stack(self, stack): """ The runner has sent an update to the stack. """ self.stack = stack self.view.debug_on_stack(stack) def on_restart(self): """ The runner has restarted. """ self.view.debug_on_restart() def on_finished(self): """ The debug runner has finished running the script to be debugged. """ self.view.debug_on_finished() def on_call(self, args): """ The runner has called a function with the specified arguments. """ self.view.debug_on_call(args) def on_return(self, retval): """ The runner has returned from a function with the specified return value. """ self.view.debug_on_return(retval) def on_line(self, filename, line): """ The runner has moved to the specified line in the referenced file. """ self.view.debug_on_line(filename, line) def on_exception(self, name, value): """ The runner has encountered a named exception with an associated value. """ msg = "Exception encountered in user's code: {} - {}" logger.info(msg.format(name, value)) self.view.debug_on_exception(name, value) def on_postmortem(self, *args, **kwargs): """ The runner encountered a fatal error and has died. """ self.view.debug_on_postmortem(args, kwargs) def on_info(self, message): """ The runner has sent an informative message. """ logger.info('Debug runner says: {}'.format(message)) self.view.debug_on_info(message) def on_warning(self, message): """ The runner has sent a warning message. """ logger.warning('Debug runner says: {}'.format(message)) self.view.debug_on_warning(message) def on_error(self, message): """ The runner has sent an error message. """ logger.error('Debug runner says: {}'.format(message)) self.view.debug_on_error(message)
class BAONQtApplication(QApplication): BACKUP_DIALOG_CAPTION = 'Rename Plan Backup Detected' BACKUP_DIALOG_ERROR_CAPTION = 'Error' BACKUP_INTRO_TEXT = 'BAON has detected a backed up rename plan from a previous run of the '\ 'application. This suggests that the application crashed partway through executing a rename operation. The '\ 'files may have been left in an inconsistent state.' BACKUP_PROMPT_TEXT = 'What do you want to do?' REVERT_BACKUP_PROGRESS_TEXT = 'Reverting the rename operation' SUCCESS_DIALOG_CAPTION = 'Success' WARNING_DIALOG_CAPTION = 'Warning' BACKUP_DELETED_DIALOG_TEXT =\ 'The backed up plan has been deleted. Further runs of the application will proceed normally.' BACKUP_REVERTED_SUCCESS_DIALOG_TEXT = 'The rename operation has been reverted successfully. The directory state '\ 'should now have been completely restored.' BACKUP_REVERTED_WARNING_DIALOG_TEXT = 'There were inconsistencies while trying to revert the previous rename '\ 'operation. The directory state may not have been fully restored.' REVERT_BACKUP_BUTTON_TEXT = 'Revert the rename (recommended)' DELETE_BACKUP_BUTTON_TEXT = 'Delete the backup file' EXAMINE_BACKUP_BUTTON_TEXT = 'Examine the plan in a text editor' QUIT_BUTTON_TEXT = 'Quit BAON' request_revert_backup = pyqtSignal() request_delete_backup = pyqtSignal() _main_window = None _core = None _progress_dialog = None _core_thread = None def __init__(self, args): super().__init__(sys.argv) # Actually we do quit when the last window is closed, but we need to do this in a more controlled way self.setQuitOnLastWindowClosed(False) self._init_threads() self._init_main_objects(args) self._connect_main_objects() self._start_core() def event(self, evt): if isinstance(evt, QFileOpenEvent): path = evt.file() if not os.path.isdir(path): path, _ = os.path.split(path) self._main_window.set_base_path(path) return True return super().event(evt) def _init_threads(self): self._core_thread = QThread() self._core_thread.start() def _init_main_objects(self, args): self._main_window = MainWindow(args) self._core = BAONQtCore(args) self._core.moveToThread(self._core_thread) def _connect_main_objects(self): self.aboutToQuit.connect(self._on_quit) # Core vs. application self._core.request_backup_decision.connect(self.backup_decision_requested) self._core.reverted_backup.connect(self.notify_backup_reverted) self._core.revert_backup_error.connect(self.handle_backup_op_error) self._core.deleted_backup.connect(self.notify_backup_deleted) self._core.delete_backup_error.connect(self.handle_backup_op_error) self.request_revert_backup.connect(self._core.revert_backup) self.request_delete_backup.connect(self._core.delete_backup) # Core vs. main window self._core.prologue_finished.connect(self._main_window.show_first_time) self._core.status_changed.connect(self._main_window.report_status) self._core.scanned_files_updated.connect(self._main_window.update_scanned_files) self._core.renamed_files_updated.connect(self._main_window.update_renamed_files) self._core.has_shutdown.connect(self.quit) self._main_window.base_path_edited.connect(self._core.update_base_path) self._main_window.scan_recursive_changed.connect(self._core.update_scan_recursive) self._main_window.rules_text_changed.connect(self._core.update_rules_text) self._main_window.use_path_changed.connect(self._core.update_use_path) self._main_window.use_extension_changed.connect(self._core.update_use_extension) self._main_window.request_add_override.connect(self._core.add_override) self._main_window.request_remove_override.connect(self._core.remove_override) self._main_window.request_do_rename.connect(self._core.do_rename) self._main_window.request_rescan.connect(self._core.rescan) self._main_window.rejected.connect(self._core.shutdown) def _start_core(self): QMetaObject.invokeMethod(self._core, 'start', Qt.QueuedConnection) def _on_quit(self): self._core_thread.quit() self._core_thread.wait() @pyqtSlot() def backup_decision_requested(self): self._show_backup_decision() @pyqtSlot(Exception) def handle_backup_op_error(self, error): self._close_progress_dialog() self._show_backup_decision(error=error) @pyqtSlot() def notify_backup_deleted(self): QMessageBox.information( None, self.SUCCESS_DIALOG_CAPTION, self.BACKUP_DELETED_DIALOG_TEXT, ) @pyqtSlot(bool) def notify_backup_reverted(self, complete_success): self._close_progress_dialog() if complete_success: QMessageBox.information( None, self.SUCCESS_DIALOG_CAPTION, self.BACKUP_REVERTED_SUCCESS_DIALOG_TEXT, ) else: QMessageBox.warning( None, self.WARNING_DIALOG_CAPTION, self.BACKUP_REVERTED_WARNING_DIALOG_TEXT, ) def _show_backup_decision(self, error=None): text = '<p>{0}</p><p>{1}</p>'.format( self.BACKUP_INTRO_TEXT if error is None else error, self.BACKUP_PROMPT_TEXT, ) dialog = QMessageBox( QMessageBox.Question if error is None else QMessageBox.Critical, self.BACKUP_DIALOG_CAPTION if error is None else self.BACKUP_DIALOG_ERROR_CAPTION, text, ) revert_button = dialog.addButton(self.REVERT_BACKUP_BUTTON_TEXT, QMessageBox.AcceptRole) delete_button = dialog.addButton(self.DELETE_BACKUP_BUTTON_TEXT, QMessageBox.DestructiveRole) examine_button = dialog.addButton(self.EXAMINE_BACKUP_BUTTON_TEXT, QMessageBox.ActionRole) dialog.addButton(self.QUIT_BUTTON_TEXT, QMessageBox.RejectRole) dialog.exec() clicked_button = dialog.clickedButton() if clicked_button == examine_button: QMetaObject.invokeMethod(self, '_examine_backup', Qt.QueuedConnection) elif clicked_button == revert_button: self._progress_dialog = QProgressDialog(None) self._progress_dialog.setLabelText(self.REVERT_BACKUP_PROGRESS_TEXT) self._progress_dialog.setCancelButton(None) self._progress_dialog.setRange(0, 0) self._progress_dialog.forceShow() self.request_revert_backup.emit() elif clicked_button == delete_button: self.request_delete_backup.emit() else: self.quit() @pyqtSlot() def _examine_backup(self): error = None try: filename = get_rename_plan_backup_filename() QDesktopServices.openUrl(QUrl.fromLocalFile(filename)) except Exception as err: error = err finally: self._show_backup_decision(error) def _close_progress_dialog(self): if self._progress_dialog is not None: self._progress_dialog.close() self._progress_dialog = None
class mainWindow(QMainWindow, Ui_MainWindow): """ The main class for the GUI window """ def __init__(self): """ The constructor and initiator. :return: """ # initial setup super(mainWindow, self).__init__() self.setupUi(self) # text, ok = QInputDialog.getText(self, 'Settings', 'Enter the host address:', QLineEdit.Normal, '140.181.97.133') # if ok: # host = str(text) # text, ok = QInputDialog.getText(self, 'Settings', 'Enter the port number:', QLineEdit.Normal, '10000') # if ok: # port = int(text) # text, ok = QInputDialog.getText(self, 'Settings', 'Enter the topic number for Dump-Pressure:', QLineEdit.Normal, '10001') # if ok: # topic = str(text) host = '140.181.97.133' port = 10000 topic = '10001' self.thread = QThread() self.zeromq_listener = ZMQListener(host, port) self.zeromq_listener.moveToThread(self.thread) self.thread.started.connect(self.zeromq_listener.loop) self.gas = Wasserstoff() # self.gasart_label.setText(self.gas.label) # Connect signals self.connect_signals() QTimer.singleShot(0, self.thread.start) self.show_message('Connected to server: {}:{}'.format(host, port)) def connect_signals(self): """ Connects signals. :return: """ # Action about and Action quit will be shown differently in OSX self.actionAbout.triggered.connect(self.show_about_dialog) self.actionQuit.triggered.connect(QCoreApplication.instance().quit) self.zeromq_listener.message.connect(self.signal_received) # combo box self.comboBox.currentTextChanged.connect(self.schaffe_passendes_gas_objekt) @staticmethod def eformat(f, prec, exp_digits): s = "%.*e" % (prec, f) mantissa, exp = s.split('e') # add 1 to digits as 1 is taken by sign +/- return "%se%+0*d" % (mantissa, exp_digits + 1, int(exp)) def signal_received(self, message): l = len(message) l = l - 1 message = message[2:l] dichte, temperatur = self.gas.rechne_dichte_aus(message) label_temperatur = 'Düsentemperatur: ' + str(round(temperatur, 2)) + 'K' self.label_temperatur.setText(label_temperatur) self.lcdNumber.setDigitCount(8) self.lcdNumber.display(dichte) def closeEvent(self, event): self.zeromq_listener.running = False self.thread.quit() self.thread.wait() def show_message(self, message): """ Implementation of an abstract method: Show text in status bar :param message: :return: """ self.statusbar.showMessage(message) def schaffe_passendes_gas_objekt(self, gasart): if gasart == 'Helium': self.gas = Helium() elif gasart == 'Neon': self.gas = Neon() elif gasart == 'Argon': self.gas = Argon() elif gasart == 'Krypton': self.gas = Krypton() elif gasart == 'Xenon': self.gas = Xenon() elif gasart == 'Wasserstoff': self.gas = Wasserstoff() elif gasart == 'Deuterium': self.gas = Deuterium() elif gasart == 'Stickstoff': self.gas = Stickstoff() def show_about_dialog(self): """ Show about dialog :return: """ about_dialog = QDialog() about_dialog.ui = Ui_AbooutDialog() about_dialog.ui.setupUi(about_dialog) about_dialog.ui.labelVersion.setText('Version: {}'.format(__version__)) about_dialog.exec_() about_dialog.show()
class StreamScope( QWidget ): resize_signal = pyqtSignal(int) resolutions = [ '320x200', # CGA '320x240', # QVGA '480x320', # HVGA '640x480', # VGA '720x480', # 480p '800x480', # WVGA '854x480', # WVGA (NTSC+) '1024x576', # PAL+ '1024x768', # XGA '1280x720', # HD '1280x768', # WXGA 'Elastic' ] def __init__( self ): super().__init__() self.title_bar_h = self.style().pixelMetric( QStyle.PM_TitleBarHeight ) self.devices = get_video_devices() self.device = '/dev/{0}'.format( self.devices[0] ) res = self.resolutions[0].split( 'x' ) self.res_w = int( res[0]) self.res_h = int( res[1]) self.stream_thread = QThread(parent=self) self.streamer = DeskStreamer() self.streamer.moveToThread( self.stream_thread ) self.streamer.finished.connect( self.stream_thread.quit ) self.streamer.finished.connect( self.streamer.deleteLater ) self.stream_thread.finished.connect( self.stream_thread.deleteLater ) self.stream_thread.start() self.stream_thread.started.connect( self.streamer.long_running ) self.setWindowTitle( self.__class__.__name__ ) self.setGeometry( 0, 0, self.res_w, self.res_h ) self.init_layout() self.setWindowFlags( self.windowFlags() # Keep existing flags | Qt.WindowStaysOnTopHint ) # Show the window self.show() def init_layout( self ): layout1 = QVBoxLayout() layout2 = QHBoxLayout() # Get video devices combo = QComboBox( self ) for i, device in enumerate( self.devices ): combo.addItem( device ) if device == 'video20': combo.setCurrentIndex( i ) self.device = '/dev/{0}'.format( device ) combo.activated[str].connect( self.comboChanged ) resolution = QComboBox( self ) for res in self.resolutions: resolution.addItem( res ) resolution.activated[str].connect( self.resolutionChanged ) # Buttons self.stream_btn = QPushButton( self, objectName='stream_btn' ) self.stream_btn.setText( 'Stream' ) self.stream_btn.clicked.connect( self.stream ) self.stop_btn = QPushButton( self, objectName='stop_btn' ) self.stop_btn.setText( 'Stop' ) self.stop_btn.clicked.connect( self.stop ) self.frame = QFrame(self ) style=''' QPushButton{ text-align:center; } QPushButton#stream_btn{ background-color: orange; } QPushButton#stop_btn{ background-color: white; } QFrame{ background-color: #3E3E3E; border-bottom-right-radius: 10px; border-bottom-left-radius: 10px; } ''' self.frame.setStyleSheet( style ) layout2.addWidget( self.stream_btn ) layout2.addWidget( self.stop_btn ) layout2.addWidget( resolution ) layout2.addWidget( combo ) self.frame.setLayout( layout2 ) self.frame.setFixedHeight( self.frame.height()*1.5 ) self.viewfinder = QLabel(self, objectName='view_finder') style=''' QLabel{ background-color: cyan; } QLabel#view_finder{ border:5px solid orange; background:transparent; padding:0px; } ''' self.viewfinder.setStyleSheet( style ) self.viewfinder.setMinimumWidth( 0 ) self.viewfinder.setMinimumHeight( 0 ) self.viewfinder.setGeometry( QRect( self.frame.pos().x(), 0, self.res_w, self.res_h ) ) layout1.addWidget( self.viewfinder ) layout1.addWidget( self.frame ) layout1.setSpacing(0) layout1.setContentsMargins( 0 ,0 ,0, 0 ) self.setAttribute( Qt.WA_TranslucentBackground ) self.setLayout( layout1 ) self.update_frustum() def stream( self ): if not self.streamer.isStreaming(): self.stream_btn.setStyleSheet( 'background-color: grey;' ) self.stop_btn.setStyleSheet( 'background-color: red;' ) self.viewfinder.setStyleSheet( 'background-color: cyan; border:1px hidden red; background:transparent; ' ) self.update_frustum() self.streamer.stream() def stop( self ): if self.streamer.isStreaming(): self.stream_btn.setStyleSheet( 'background-color: orange;') self.stop_btn.setStyleSheet( 'background-color: white;') self.viewfinder.setStyleSheet( 'background-color: cyan; border:5px solid orange; background:transparent; padding:0;' ) self.streamer.stop() def comboChanged( self, text ): self.stop() self.device = '/dev/{0}'.format( text ) def resolutionChanged( self, text ): self.stop() if text == 'Elastic': min_w = 0 min_h = 0 win_w = self.viewfinder.width() win_h = self.viewfinder.height() else: res = text.split( 'x' ) self.res_w = int( res[0] ) self.res_h = int( res[1] ) min_w = self.res_w min_h = self.res_h win_w = self.res_w win_h = self.res_h self.viewfinder.setMinimumWidth( min_w ) self.viewfinder.setMinimumHeight( min_h ) self.viewfinder.setGeometry( QRect( self.frame.pos().x(), 0, win_w, win_h ) ) self.setGeometry( QRect( self.pos().x(), self.pos().y(), win_w, win_h ) ) self.adjustSize() def debug_frustum( self ): print( 'Window: {0}, {1}x{2}'.format( self.pos(), self.width(), self.height() ) ) print( 'Status: {0}'.format( self.title_bar_h ) ) print( 'Scope: {0}, {1}x{2}'.format( self.viewfinder.pos(),self.viewfinder.width(), self.viewfinder.height() ) ) print( 'Device: {0}'.format( self.device ) ) def update_frustum( self ): #self.debug_frustum() self.streamer.x = self.pos().x() + self.viewfinder.pos().x() + 0 self.streamer.y = self.pos().y() + self.viewfinder.pos().y() + self.title_bar_h + 5 self.streamer.width = self.viewfinder.width() self.streamer.height = self.viewfinder.height() self.streamer.device = self.device def moveEvent( self, event ): self.stop() self.update_frustum() super().moveEvent(event) def resizeEvent(self, event = None): self.stop() self.update_frustum() self.resize_signal.emit( 1 ) def closeEvent( self, event ): self.thread_clean_up() super().closeEvent( event ) def thread_clean_up( self ): print( 'Cleaning up thread' ) self.streamer.exit() self.stream_thread.quit() self.stream_thread.wait()
class StitcherUI(QDialog): thread_invoker = pyqtSignal() def __init__(self, argv, terminate, parent=None): super(StitcherUI, self).__init__(parent) self.setWindowTitle("Stitcher Preview") st = stitch.Stitcher(argv=argv) self.st = st #determine the shrink ratio to avoid too huge preview preview_ratio = 1.0 if st.image.shape[1] > 10000: preview_ratio = 10000.0 / st.image.shape[1] if st.image.shape[0]*preview_ratio > 500: preview_ratio = 500.0 / st.image.shape[0] self.terminate = terminate self.thread = QThread() self.thread.start() self.worker = Renderer(st=st, preview_ratio=preview_ratio) #it might be too early. #determine the window size height,width = st.image.shape[0:2] height = int(height*preview_ratio) #determine the preview area size width = int(width*preview_ratio) self.scrollArea = QScrollArea() #self.scrollArea.setMaximumHeight(1000) self.largecanvas = ExtensibleCanvasWidget(width, height) #print(width,height) self.worker.frameRendered.connect(self.largecanvas.updatePixmap) #Do not close the window when finished. #self.worker.finished.connect(self.finishIt) self.worker.moveToThread(self.thread) self.thread_invoker.connect(self.worker.task) self.thread_invoker.emit() self.scrollArea.setWidget(self.largecanvas) self.scrollArea.setMinimumHeight(500) #self.largecanvas.sizeHint().height()) self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.btnStop = QPushButton('Stop') self.btnStop.clicked.connect(lambda: self.worker.stop()) self.btnStop.clicked.connect(self.terminateIt) self.progress = QProgressBar(self) self.worker.progress.connect(self.progress.setValue) self.layout = QVBoxLayout() self.layout.addWidget(self.btnStop) self.layout.addWidget(self.progress) self.layout.addWidget(self.scrollArea) self.layout.addStretch(1) self.setLayout(self.layout) def terminateIt(self): self.close() if self.terminate: sys.exit(1) #terminated def finishIt(self): self.close() def closeEvent(self, event): self.stop_thread() def stop_thread(self): self.worker.stop() self.thread.quit() self.thread.wait()
class Windows(QDialog, mainUI.Ui_Dialog): isRunning = False def __init__(self, parent=None): super(Windows, self).__init__(parent) self.selectedDict = None self.currentConfig = dict() self.localWords = [] self.selectedGroups = [] self.workerThread = QThread(self) self.workerThread.start() self.updateCheckThead = QThread(self) self.updateCheckThead.start() self.audioDownloadThread = QThread(self) self.updateCheckWork = None self.loginWorker = None self.queryWorker = None self.pullWorker = None self.audioDownloadWorker = None self.setupUi(self) self.setWindowTitle(MODEL_NAME) self.setupLogger() self.initCore() self.checkUpdate() # self.__dev() # 以备调试时使用 def __dev(self): def on_dev(): logger.debug('whatever') self.devBtn = QPushButton('Magic Button', self.mainTab) self.devBtn.clicked.connect(on_dev) self.gridLayout_4.addWidget(self.devBtn, 4, 3, 1, 1) def closeEvent(self, event): # 插件关闭时退出所有线程 if self.workerThread.isRunning(): self.workerThread.requestInterruption() self.workerThread.quit() self.workerThread.wait() if self.updateCheckThead.isRunning(): self.updateCheckThead.quit() self.updateCheckThead.wait() if self.audioDownloadThread.isRunning(): self.audioDownloadThread.requestInterruption() self.workerThread.quit() self.workerThread.wait() event.accept() def setupLogger(self): """初始化 Logger """ def onDestroyed(): logger.removeHandler(QtHandler) # 防止 debug 信息写入stdout/stderr 导致 Anki 崩溃 logging.basicConfig(handlers=[logging.FileHandler('dict2anki.log', 'w', 'utf-8')], level=logging.DEBUG, format='[%(asctime)s][%(levelname)8s] -- %(message)s - (%(name)s)') logTextBox = QPlainTextEdit(self) logTextBox.setLineWrapMode(QPlainTextEdit.NoWrap) layout = QVBoxLayout() layout.addWidget(logTextBox) self.logTab.setLayout(layout) QtHandler = Handler(self) logger.addHandler(QtHandler) QtHandler.newRecord.connect(logTextBox.appendPlainText) # 日志Widget销毁时移除 Handlers logTextBox.destroyed.connect(onDestroyed) def setupGUIByConfig(self): config = mw.addonManager.getConfig(__name__) self.deckComboBox.setCurrentText(config['deck']) self.dictionaryComboBox.setCurrentIndex(config['selectedDict']) self.apiComboBox.setCurrentIndex(config['selectedApi']) self.usernameLineEdit.setText(config['credential'][config['selectedDict']]['username']) self.passwordLineEdit.setText(config['credential'][config['selectedDict']]['password']) self.cookieLineEdit.setText(config['credential'][config['selectedDict']]['cookie']) self.definitionCheckBox.setChecked(config['definition']) self.imageCheckBox.setChecked(config['image']) self.sentenceCheckBox.setChecked(config['sentence']) self.phraseCheckBox.setChecked(config['phrase']) self.AmEPhoneticCheckBox.setChecked(config['AmEPhonetic']) self.BrEPhoneticCheckBox.setChecked(config['BrEPhonetic']) self.BrEPronRadioButton.setChecked(config['BrEPron']) self.AmEPronRadioButton.setChecked(config['AmEPron']) self.noPronRadioButton.setChecked(config['noPron']) self.selectedGroups = config['selectedGroup'] def initCore(self): self.usernameLineEdit.hide() self.usernameLabel.hide() self.passwordLabel.hide() self.passwordLineEdit.hide() self.dictionaryComboBox.addItems([d.name for d in dictionaries]) self.apiComboBox.addItems([d.name for d in apis]) self.deckComboBox.addItems(getDeckList()) self.setupGUIByConfig() def getAndSaveCurrentConfig(self) -> dict: """获取当前设置""" currentConfig = dict( selectedDict=self.dictionaryComboBox.currentIndex(), selectedApi=self.apiComboBox.currentIndex(), selectedGroup=self.selectedGroups, deck=self.deckComboBox.currentText(), username=self.usernameLineEdit.text(), password=Mask(self.passwordLineEdit.text()), cookie=Mask(self.cookieLineEdit.text()), definition=self.definitionCheckBox.isChecked(), sentence=self.sentenceCheckBox.isChecked(), image=self.imageCheckBox.isChecked(), phrase=self.phraseCheckBox.isChecked(), AmEPhonetic=self.AmEPhoneticCheckBox.isChecked(), BrEPhonetic=self.BrEPhoneticCheckBox.isChecked(), BrEPron=self.BrEPronRadioButton.isChecked(), AmEPron=self.AmEPronRadioButton.isChecked(), noPron=self.noPronRadioButton.isChecked(), ) logger.info(f'当前设置:{currentConfig}') self._saveConfig(currentConfig) self.currentConfig = currentConfig return currentConfig @staticmethod def _saveConfig(config): _config = deepcopy(config) _config['credential'] = [dict(username='', password='', cookie='')] * len(dictionaries) _config['credential'][_config['selectedDict']] = dict( username=_config.pop('username'), password=str(_config.pop('password')), cookie=str(_config.pop('cookie')) ) maskedConfig = deepcopy(_config) maskedCredential = [ dict( username=c['username'], password=Mask(c['password']), cookie=Mask(c['cookie'])) for c in maskedConfig['credential'] ] maskedConfig['credential'] = maskedCredential logger.info(f'保存配置项:{maskedConfig}') mw.addonManager.writeConfig(__name__, _config) def checkUpdate(self): @pyqtSlot(str, str) return def on_haveNewVersion(version, changeLog): if askUser(f'有新版本:{version}是否更新?\n\n{changeLog.strip()}'): openLink(RELEASE_URL) self.updateCheckWork = VersionCheckWorker() self.updateCheckWork.moveToThread(self.updateCheckThead) self.updateCheckWork.haveNewVersion.connect(on_haveNewVersion) self.updateCheckWork.finished.connect(self.updateCheckThead.quit) self.updateCheckWork.start.connect(self.updateCheckWork.run) self.updateCheckWork.start.emit() @pyqtSlot(int) def on_dictionaryComboBox_currentIndexChanged(self, index): """词典候选框改变事件""" self.currentDictionaryLabel.setText(f'当前选择词典: {self.dictionaryComboBox.currentText()}') config = mw.addonManager.getConfig(__name__) self.cookieLineEdit.setText(config['credential'][index]['cookie']) @pyqtSlot() def on_pullRemoteWordsBtn_clicked(self): """获取单词按钮点击事件""" if not self.deckComboBox.currentText(): showInfo('\n请选择或输入要同步的牌组') return self.mainTab.setEnabled(False) self.progressBar.setValue(0) self.progressBar.setMaximum(0) currentConfig = self.getAndSaveCurrentConfig() self.selectedDict = dictionaries[currentConfig['selectedDict']]() # 登陆线程 self.loginWorker = LoginStateCheckWorker(self.selectedDict.checkCookie, json.loads(self.cookieLineEdit.text() or '{}')) self.loginWorker.moveToThread(self.workerThread) self.loginWorker.start.connect(self.loginWorker.run) self.loginWorker.logSuccess.connect(self.onLogSuccess) self.loginWorker.logFailed.connect(self.onLoginFailed) self.loginWorker.start.emit() @pyqtSlot() def onLoginFailed(self): showCritical('第一次登录或cookie失效!请重新登录') self.progressBar.setValue(0) self.progressBar.setMaximum(1) self.mainTab.setEnabled(True) self.cookieLineEdit.clear() self.loginDialog = LoginDialog( loginUrl=self.selectedDict.loginUrl, loginCheckCallbackFn=self.selectedDict.loginCheckCallbackFn, parent=self ) self.loginDialog.loginSucceed.connect(self.onLogSuccess) self.loginDialog.show() @pyqtSlot(str) def onLogSuccess(self, cookie): self.cookieLineEdit.setText(cookie) self.getAndSaveCurrentConfig() self.selectedDict.checkCookie(json.loads(cookie)) self.selectedDict.getGroups() container = QDialog(self) group = wordGroup.Ui_Dialog() group.setupUi(container) for groupName in [str(group_name) for group_name, _ in self.selectedDict.groups]: item = QListWidgetItem() item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setText(groupName) item.setCheckState(Qt.Unchecked) group.wordGroupListWidget.addItem(item) # 恢复上次选择的单词本分组 if self.selectedGroups: for groupName in self.selectedGroups[self.currentConfig['selectedDict']]: items = group.wordGroupListWidget.findItems(groupName, Qt.MatchExactly) for item in items: item.setCheckState(Qt.Checked) else: self.selectedGroups = [list()] * len(dictionaries) def onAccepted(): """选择单词本弹窗确定事件""" # 清空 listWidget self.newWordListWidget.clear() self.needDeleteWordListWidget.clear() self.mainTab.setEnabled(False) selectedGroups = [group.wordGroupListWidget.item(index).text() for index in range(group.wordGroupListWidget.count()) if group.wordGroupListWidget.item(index).checkState() == Qt.Checked] # 保存分组记录 self.selectedGroups[self.currentConfig['selectedDict']] = selectedGroups self.progressBar.setValue(0) self.progressBar.setMaximum(1) logger.info(f'选中单词本{selectedGroups}') self.getRemoteWordList(selectedGroups) def onRejected(): """选择单词本弹窗取消事件""" self.progressBar.setValue(0) self.progressBar.setMaximum(1) self.mainTab.setEnabled(True) group.buttonBox.accepted.connect(onAccepted) group.buttonBox.rejected.connect(onRejected) container.exec() def getRemoteWordList(self, selected_groups: [str]): """根据选中到分组获取分组下到全部单词,并添加到 newWordListWidget""" group_map = dict(self.selectedDict.groups) self.localWords = getWordsByDeck(self.deckComboBox.currentText()) # 启动单词获取线程 self.pullWorker = RemoteWordFetchingWorker(self.selectedDict, [(group_name, group_map[group_name],) for group_name in selected_groups]) self.pullWorker.moveToThread(self.workerThread) self.pullWorker.start.connect(self.pullWorker.run) self.pullWorker.tick.connect(lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.pullWorker.setProgress.connect(self.progressBar.setMaximum) self.pullWorker.doneThisGroup.connect(self.insertWordToListWidget) self.pullWorker.done.connect(self.on_allPullWork_done) self.pullWorker.start.emit() @pyqtSlot(list) def insertWordToListWidget(self, words: list): """一个分组获取完毕事件""" for word in words: wordItem = QListWidgetItem(word, self.newWordListWidget) wordItem.setData(Qt.UserRole, None) self.newWordListWidget.clearSelection() @pyqtSlot() def on_allPullWork_done(self): """全部分组获取完毕事件""" localWordList = set(getWordsByDeck(self.deckComboBox.currentText())) remoteWordList = set([self.newWordListWidget.item(row).text() for row in range(self.newWordListWidget.count())]) newWords = remoteWordList - localWordList # 新单词 needToDeleteWords = localWordList - remoteWordList # 需要删除的单词 logger.info(f'本地: {localWordList}') logger.info(f'远程: {remoteWordList}') logger.info(f'待查: {newWords}') logger.info(f'待删: {needToDeleteWords}') waitIcon = QIcon(':/icons/wait.png') delIcon = QIcon(':/icons/delete.png') self.newWordListWidget.clear() self.needDeleteWordListWidget.clear() for word in needToDeleteWords: item = QListWidgetItem(word) item.setCheckState(Qt.Checked) item.setIcon(delIcon) self.needDeleteWordListWidget.addItem(item) for word in newWords: item = QListWidgetItem(word) item.setIcon(waitIcon) self.newWordListWidget.addItem(item) self.newWordListWidget.clearSelection() self.dictionaryComboBox.setEnabled(True) self.apiComboBox.setEnabled(True) self.deckComboBox.setEnabled(True) self.pullRemoteWordsBtn.setEnabled(True) self.queryBtn.setEnabled(self.newWordListWidget.count() > 0) self.syncBtn.setEnabled(self.newWordListWidget.count() == 0 and self.needDeleteWordListWidget.count() > 0) if self.needDeleteWordListWidget.count() == self.newWordListWidget.count() == 0: logger.info('无需同步') tooltip('无需同步') self.mainTab.setEnabled(True) @pyqtSlot() def on_queryBtn_clicked(self): logger.info('点击查询按钮') currentConfig = self.getAndSaveCurrentConfig() self.queryBtn.setEnabled(False) self.pullRemoteWordsBtn.setEnabled(False) self.syncBtn.setEnabled(False) wordList = [] selectedWords = self.newWordListWidget.selectedItems() if selectedWords: # 如果选中单词则只查询选中的单词 for wordItem in selectedWords: wordBundle = dict() row = self.newWordListWidget.row(wordItem) wordBundle['term'] = wordItem.text() for configName in BASIC_OPTION + EXTRA_OPTION: wordBundle[configName] = currentConfig[configName] wordBundle['row'] = row wordList.append(wordBundle) else: # 没有选择则查询全部 for row in range(self.newWordListWidget.count()): wordBundle = dict() wordItem = self.newWordListWidget.item(row) wordBundle['term'] = wordItem.text() for configName in BASIC_OPTION + EXTRA_OPTION: wordBundle[configName] = currentConfig[configName] wordBundle['row'] = row wordList.append(wordBundle) logger.info(f'待查询单词{wordList}') # 查询线程 self.progressBar.setMaximum(len(wordList)) self.queryWorker = QueryWorker(wordList, apis[currentConfig['selectedApi']]) self.queryWorker.moveToThread(self.workerThread) self.queryWorker.thisRowDone.connect(self.on_thisRowDone) self.queryWorker.thisRowFailed.connect(self.on_thisRowFailed) self.queryWorker.tick.connect(lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.queryWorker.allQueryDone.connect(self.on_allQueryDone) self.queryWorker.start.connect(self.queryWorker.run) self.queryWorker.start.emit() @pyqtSlot(int, dict) def on_thisRowDone(self, row, result): """该行单词查询完毕""" doneIcon = QIcon(':/icons/done.png') wordItem = self.newWordListWidget.item(row) wordItem.setIcon(doneIcon) wordItem.setData(Qt.UserRole, result) @pyqtSlot(int) def on_thisRowFailed(self, row): failedIcon = QIcon(':/icons/failed.png') failedWordItem = self.newWordListWidget.item(row) failedWordItem.setIcon(failedIcon) @pyqtSlot() def on_allQueryDone(self): failed = [] for i in range(self.newWordListWidget.count()): wordItem = self.newWordListWidget.item(i) if not wordItem.data(Qt.UserRole): failed.append(wordItem.text()) if failed: logger.warning(f'查询失败或未查询:{failed}') self.pullRemoteWordsBtn.setEnabled(True) self.queryBtn.setEnabled(True) self.syncBtn.setEnabled(True) @pyqtSlot() def on_syncBtn_clicked(self): failedGenerator = (self.newWordListWidget.item(row).data(Qt.UserRole) is None for row in range(self.newWordListWidget.count())) if any(failedGenerator): if not askUser('存在未查询或失败的单词,确定要加入单词本吗?\n 你可以选择失败的单词点击 "查询按钮" 来重试。'): return currentConfig = self.getAndSaveCurrentConfig() model = getOrCreateModel(MODEL_NAME) getOrCreateModelCardTemplate(model, 'default') deck = getOrCreateDeck(self.deckComboBox.currentText()) logger.info('同步点击') audiosDownloadTasks = [] newWordCount = self.newWordListWidget.count() # 判断是否需要下载发音 if currentConfig['noPron']: logger.info('不下载发音') whichPron = None else: whichPron = 'AmEPron' if self.AmEPronRadioButton.isChecked() else 'BrEPron' logger.info(f'下载发音{whichPron}') added = 0 for row in range(newWordCount): wordItem = self.newWordListWidget.item(row) wordItemData = wordItem.data(Qt.UserRole) if wordItemData: addNoteToDeck(deck, model, currentConfig, wordItemData) added += 1 # 添加发音任务 if whichPron and wordItemData.get(whichPron): audiosDownloadTasks.append((f"{whichPron}_{wordItemData['term']}.mp3", wordItemData[whichPron],)) mw.reset() logger.info(f'发音下载任务:{audiosDownloadTasks}') if audiosDownloadTasks: self.syncBtn.setEnabled(False) self.progressBar.setValue(0) self.progressBar.setMaximum(len(audiosDownloadTasks)) if self.audioDownloadThread is not None: self.audioDownloadThread.requestInterruption() self.audioDownloadThread.quit() self.audioDownloadThread.wait() self.audioDownloadThread = QThread(self) self.audioDownloadThread.start() self.audioDownloadWorker = AudioDownloadWorker(audiosDownloadTasks) self.audioDownloadWorker.moveToThread(self.audioDownloadThread) self.audioDownloadWorker.tick.connect(lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.audioDownloadWorker.start.connect(self.audioDownloadWorker.run) self.audioDownloadWorker.done.connect(lambda: tooltip(f'发音下载完成')) self.audioDownloadWorker.done.connect(self.audioDownloadThread.quit) self.audioDownloadWorker.start.emit() self.newWordListWidget.clear() needToDeleteWordItems = [ self.needDeleteWordListWidget.item(row) for row in range(self.needDeleteWordListWidget.count()) if self.needDeleteWordListWidget.item(row).checkState() == Qt.Checked ] needToDeleteWords = [i.text() for i in needToDeleteWordItems] deleted = 0 if needToDeleteWords and askUser(f'确定要删除这些单词吗:{needToDeleteWords[:3]}...({len(needToDeleteWords)}个)', title='Dict2Anki', parent=self): needToDeleteWordNoteIds = getNotes(needToDeleteWords, currentConfig['deck']) mw.col.remNotes(needToDeleteWordNoteIds) deleted += 1 mw.col.reset() mw.reset() for item in needToDeleteWordItems: self.needDeleteWordListWidget.takeItem(self.needDeleteWordListWidget.row(item)) logger.info('删除完成') logger.info('完成') if not audiosDownloadTasks: tooltip(f'添加{added}个笔记\n删除{deleted}个笔记')
class MpvTemplatePyQt(QObject, AbstractTemplate, mpv.Mpv): """Bases: :obj:`QObject <PyQt5.QtCore.QObject>`, :obj:`AbstractTemplate<mpv.templates.AbstractTemplate>`, :obj:`Mpv <mpv.Mpv>`. A Template that can be subclassed for a PyQt5 application. It uses a :obj:`PyQt5.QtCore.QThread` for the event loop. see ``demo/pyqt5.py`` for an example. Args: options (:obj:`dict`, optional): dictionary of options to set with mpv_set_option(). observe (:obj:`list` of :obj:`str`): a list of properties to be observed. log_level (:obj:`mpv.LogLevel`): the log level for mpv to use. log_handler (:obj:`callable`): a function that will be called with the log message as its only argument. parent (:obj:`QObject <PyQt5.QtCore.QObject>`): the Qt parent. **kwargs (optional): options to set with mpv_set_option(). Raises: mpv.ApiVersionError: if the loaded libmpv doesn't meet the minimum requirement. Attributes: shutdown (:obj:`pyqtSignal <PyQt5.QtCore.pyqtSignal>`): Emitted when mpv has finished shutting down after :obj:`quit() <mpv.templates.MpvTemplatePyQt.quit>` has been called. """ _wakeup = pyqtSignal(mpv.Mpv) shutdown = pyqtSignal() def __init__(self, options=None, observe=None, log_level=mpv.LogLevel.INFO, log_handler=None, parent=None, **kwargs): QObject.__init__(self, parent) AbstractTemplate.__init__(self) mpv.Mpv.__init__(self, options=options, **kwargs) if observe is not None: for prop in observe: self.observe_property(prop) if log_handler is not None: self.request_log_messages(log_level) self.log_handler = log_handler self._event_thread = QThread(self) self._event_worker = EventWorker() self._event_worker.moveToThread(self._event_thread) self._event_worker.mpv_event.connect(self._handle_event) self._event_worker.finished.connect(self._event_worker.deleteLater) self._event_thread.finished.connect(self._event_thread.deleteLater) self._wakeup.connect(self._event_worker.wait_event) self.before_initialize() self.initialize() self._event_thread.start() self._wakeup.emit(self) def quit(self): """Make mpv quit. """ if self.handle: self.command('quit') # trigger a SHUTDOWN event. self._event_thread.quit() # end the event thread self._event_thread.wait() self.terminate_destroy() # destroy mpv self.shutdown.emit() @pyqtSlot(int) def seek_absolute(self, ms): """ Args: ms (int): seek to the absolute position in milliseconds. """ if not self.handle: return self.seek(ms / 1000.0, 'absolute+exact') @pyqtSlot(int) def seek_relative(self, ms): """ Args: ms (int): seek relative to the current position in milliseconds. """ if not self.handle: return self.seek(ms / 1000.0, 'relative+exact') @pyqtSlot(float) def set_volume(self, val): """ Args: val (float) [0, 100]: volume percentage as a float. """ if not self.handle: return if val < 0 or val > 100: raise ValueError('Must be in range [0, 100]') self.volume = val
class Dialog(QDialog, Ui_Dialog): sendUser = pyqtSignal(object) sendCreated = pyqtSignal(object) def __init__(self, app, parent=None): super(Dialog, self).__init__(parent) self.ui = Ui_Dialog() self.ui.setupUi(self) self.setWindowTitle("Process Video") self.dir = os.path.dirname(os.path.realpath(__file__)) self.created = {} self.user = None self.thread = None self.progressBar = None self.progressAllBar = None self.progressLabel = None self.img_exist = False self.params_dict = None self.of_exist = False self.back_of_exist = False self.depth_exist = False self.gt_exist = False self.create_super_pixel_label = False self.no_error = True self.object_detection_dir_exist = False self.vid_name = None self.all_run = 1 self.run_count = 1 self.fps = 30 self.fps_limit = 60 self.low = 0.0 self.high = 1.0 self.run_dict = {} self.super_pixel_method = "" self.app = app self.ui.b_info.setIconSize(QSize(50, 50)) self.ui.b_info.setIcon(QApplication.style().standardIcon( QStyle.SP_MessageBoxInformation)) homedir = os.path.expanduser("~") if platform.system() == "Windows": datadir = os.sep.join([homedir, "Analyser"]) else: datadir = os.sep.join([homedir, ".analyser"]) if not os.path.exists(datadir): os.makedirs(datadir) self.user_file = os.path.join(datadir, ".userInfo.json") self.signalSetup() self.loadUser() self.userSetup() def signalSetup(self): """ Setup for signal connections """ self.ui.b_info.clicked.connect(self.showInfo) self.ui.b_save.clicked.connect(self.openSave) self.ui.b_vid.clicked.connect(self.openVideo) self.ui.b_run.clicked.connect(self.startRun) self.ui.b_colour.clicked.connect(self.pickColour) self.ui.b_ground_truth.clicked.connect(self.openGroundTruth) self.ui.t_fps.textChanged.connect(self.changeFps) self.ui.t_low.editingFinished.connect(self.changeLow) self.ui.t_high.editingFinished.connect(self.changeHigh) self.ui.c_error_plot.stateChanged.connect(self.checkFiles) self.ui.c_speed_plot.stateChanged.connect(self.checkFiles) self.ui.c_crash_plot.stateChanged.connect(self.checkFiles) self.ui.combo_superpixel.currentIndexChanged.connect( self.changeSuperPixelMethod) self.ui.c_optimize.stateChanged.connect(self.checkFiles) self.ui.c_draw.stateChanged.connect(self.checkFiles) self.ui.c_velocity.stateChanged.connect(self.checkFiles) self.ui.c_object_detection.stateChanged.connect(self.checkFiles) def showInfo(self): """Show information dialog """ widget = DialogInfo(app=self.app, parent=self) widget.exec_() def changeLow(self): """Change the value of the low drop based on the text inside t_low """ self.changeLowHigh(self.ui.t_low, t_type="low") def changeHigh(self): """Change the value of the high drop based on the text inside t_high """ self.changeLowHigh(self.ui.t_high, t_type="high") def changeLowHigh(self, text_widget, t_type="low"): """Change the value of low/high drop Arguments: text_widget {QLineEdit} -- t_low or t_high Keyword Arguments: t_type {str} -- which line edit was given (default: {"low"}) """ check = re.search("(0[.][0-9]+|1)", text_widget.text()) if check and self.ui.t_low.text() != self.ui.t_high.text(): num = check.group() i_num = float(num) if t_type == "low": self.low = i_num else: self.high = i_num text_widget.setText(str(i_num)) else: logging.info("Wrong Input For low or high") if t_type == "low": text_widget.setText("0.0") self.low = 0.0 else: text_widget.setText("1") self.high = 1 def changeSuperPixelMethod(self, index): """Change the super pixel method Arguments: index {int} -- index of the selected super pixel method """ if index == 0: self.super_pixel_method = "" elif index == 1: self.super_pixel_method = "Felzenszwalb" elif index == 2: self.super_pixel_method = "Quickshift" elif index == 3: self.super_pixel_method = "Slic" elif index == 4: self.super_pixel_method = "Watershed" self.checkFiles() def openGroundTruth(self): """Open file with ground truth values for speed """ gt_dir = self.openFile( self.user["GT"], title="Load Ground Truth Data", file_filter="Numpy Files (*.npy)", ) if gt_dir != "": self.user["GT"] = gt_dir self.ui.l_ground_truth.setText("Load: " + self.splitPath(gt_dir)[-1]) self.checkFiles() def changeFps(self): """ Change fps for the created videos """ check = re.search("[1-9][0-9]*", self.ui.t_fps.text()) if check: num = check.group() fps = int(num) if fps > self.fps_limit: logging.warning( "Too big number for fps. Falling back to {0} fps.".format( self.fps_limit)) fps = self.fps_limit self.fps = fps self.ui.t_fps.setText(str(fps)) else: logging.info("Wrong Input For Fps") self.ui.t_fps.setText("30") self.fps = 30 def splitPath(self, path): """Split the given path based on filesystem Arguments: path {str} -- path you want to split Returns: path splitted on / or \ -- only uses \ on windows """ return os.path.split(path) def openSave(self): """Open directory to save results """ save_dir = QFileDialog.getExistingDirectory(self, "Select a folder", self.user["Save"], QFileDialog.ShowDirsOnly) if save_dir != "": self.user["Save"] = save_dir name_split = self.splitPath(save_dir)[-1] name = name_split.split(".")[0] self.ui.l_save.setText("Save to: " + name) self.checkFiles() def checkFiles(self): """Checks if there are folders from previous runs, so we don't need to create them. Also, checks condotions for checkboxes and buttons """ if self.user["Save"] != "": self.of_exist = os.path.exists( os.path.join(self.user["Save"], "Of")) self.back_of_exist = os.path.exists( os.path.join(self.user["Save"], "Back_Of")) self.img_exist = os.path.exists( os.path.join(self.user["Save"], "Images")) self.depth_exist = os.path.exists( os.path.join(self.user["Save"], "Depth")) self.object_detection_dir_exist = os.path.exists( os.path.join(self.user["Save"], "ObjectDetection")) self.gt_exist = self.user["GT"] != "" self.create_super_pixel_label = ( self.super_pixel_method != "" and not os.path.exists( os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method))) self.ui.c_crash_plot_video.setEnabled(self.ui.c_crash_plot.isChecked()) self.ui.t_low.setEnabled(not self.ui.c_optimize.isChecked()) self.ui.t_high.setEnabled(not self.ui.c_optimize.isChecked()) self.ui.c_optimize.setEnabled(self.gt_exist) self.ui.c_error_plot.setEnabled(self.gt_exist) self.ui.c_error_plot_video.setEnabled(self.ui.c_error_plot.isChecked()) self.ui.c_speed_plot_video.setEnabled(self.ui.c_speed_plot.isChecked()) self.ui.c_super_pixel_video.setEnabled( self.ui.combo_superpixel.currentIndex() != 0) self.ui.c_csv.setEnabled(self.ui.c_error_plot.isChecked()) if self.runRequirements(): self.ui.b_run.setEnabled(True) else: self.ui.b_run.setEnabled(False) def runRequirements(self): """Basic requirements to start run Returns: bool -- returns true if the requirements are satisfied """ ready = (self.user["Save"] != "" and self.user["Video"] != "") or self.img_exist return ready def openVideo(self): """Open video to analyze """ fname = self.openFile(self.user["Video"]) if fname != "": error_opening_video = False cam = cv2.VideoCapture(fname) logging.info("Opening video Check: {0}".format(fname)) currentframe = 0 ret, frame = cam.read() if ret is False: error_opening_video = True # Release all space and windows once done cam.release() cv2.destroyAllWindows() if error_opening_video: msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText("Error on opening the video: {0}".format(fname)) msg.setWindowTitle("Information") msg.setStandardButtons(QMessageBox.Ok) msg.exec_() else: self.user["Video"] = fname name = self.splitPath(fname)[-1] self.vid_name = name.split(".")[0] self.ui.l_vid.setText("Load: " + self.vid_name) self.checkFiles() def openFile(self, folder, title="Open Video", file_filter="Video Files (*.mp4 *.avi *.mkv)"): """Open QFileDialog with the given parameters, returns selected file Arguments: folder {str} -- path where the file dialog should start Keyword Arguments: title {str} -- title of the file dialog (default: {"Open Video"}) file_filter {str} -- filter for file extensions (default: {"Video Files (*.mp4 *.avi *.mkv)"}) Returns: str -- path to file """ fname = QFileDialog.getOpenFileName(self, title, folder, file_filter) return fname[0] def userSetup(self): """Label text setup if the user json is not empty """ if self.user["Save"] == "": self.ui.b_run.setEnabled(False) else: name_split = self.splitPath(self.user["Save"])[-1] name = name_split.split(".")[0] self.ui.l_save.setText("Save to: " + name) if self.user["GT"] != "": self.ui.l_ground_truth.setText( self.splitPath(self.user["GT"])[-1]) self.ui.l_colour.setText(self.user["Colour"]) def loadUser(self): """Load user file if exists, create empty otherwise """ if os.path.isfile(self.user_file): logging.info("Found User File") with open(self.user_file, "r") as json_file: self.user = json.load(json_file) self.checkFiles() else: self.user = { "Save": "", "Of": "", "Depth": "", "Video": "", "Colour": "#1a1a1b", "GT": "", } self.saveUser() def saveUser(self): """Saves user data to json file """ self.user["Video"] = "" with open(self.user_file, "w+") as json_file: json.dump(self.user, json_file, indent=4) def errorChecks(self): """Check for errors and inform the user in a QMessageBox if found any Returns: bool -- if true then the execution stops """ stop_calculation = False found_error = False errors = {"Info": [], "Critical": []} error_types = [] ori_images = 0 of_images = 0 depth_images = 0 back_of_images = 0 if os.path.exists(self.savePathJoin("Images")): ori_images = len( listDirectory(self.savePathJoin("Images"), extension="png")) # Check image folder if self.img_exist and not os.path.exists(self.savePathJoin("Images")): if os.path.exists(self.user["Video"]): errors["Info"].append( "Images folder {0} doesn't exist -> Recreate it and recalculate optical flow and depth estimations" .format(self.savePathJoin("Images"))) error_types.append("NoImages") else: stop_calculation = True errors["Critical"].append(( "Images folder {0} and video file {1} don't exist -> Stopping run" .format(self.savePathJoin("Images"), self.user["Video"]))) elif self.img_exist and os.path.exists(self.user["Video"]): errors["Info"].append( "Both the video {0} and Images folder {1} exist -> using Images folder by default" .format(self.user["Video"], self.savePathJoin("Images"))) elif not self.img_exist and not os.path.isfile(self.user["Video"]): stop_calculation = True errors["Critical"].append(( "Images folder {0} and video file {1} don't exist -> Stopping run" .format(self.savePathJoin("Images"), self.user["Video"]))) # Check video file if self.user["Video"] != "" and not os.path.isfile(self.user["Video"]): if os.path.exists(self.savePathJoin("Images")): errors["Info"].append(( "Video file {0} doesn't exist -> Using images in the Images folder instead" .format(self.user["Video"]))) else: stop_calculation = True errors["Critical"].append(( "Images folder {0} and video file {1} don't exist -> Stopping run" .format(self.savePathJoin("Images"), self.user["Video"]))) elif os.path.isfile(self.user["Video"]) and os.path.exists( self.savePathJoin("Images")): pass # Check optical flow if self.of_exist and not os.path.exists(self.savePathJoin("Of")): errors["Info"].append(( "Optical flow folder {0} doesn't exist -> Recalculating optical flow" .format(self.savePathJoin("Of")))) error_types.append("NoOf") elif self.of_exist: of_images = len( listDirectory(self.savePathJoin("Of"), extension="png")) if of_images != ori_images - 1 and ori_images != 0: errors["Info"].append(( "Optical flow image number {0} doesn't match video image number {1} - 1 -> Recalculating optical flow" .format(of_images, ori_images))) error_types.append("NoOf") # Check backward optical flow if self.back_of_exist and not os.path.exists( self.savePathJoin("Back_Of")): errors["Info"].append(( "Backward optical flow folder {0} doesn't exist -> Recalculating backward optical flow" .format(self.savePathJoin("Back_Of")))) error_types.append("NoOf") elif self.back_of_exist: back_of_images = len( listDirectory(self.savePathJoin("Back_Of"), extension="png")) if back_of_images != of_images: errors["Info"].append(( "Backward optical flow image number {0} doesn't match optical flow image number {1} -> Recalculating backward optical flow" .format(back_of_images, of_images))) error_types.append("NoOf") # Check depth estimation if self.depth_exist and not os.path.exists(self.savePathJoin("Depth")): errors["Info"].append(( "Depth folder {0} doesn't exist -> Recalculating depth".format( self.savePathJoin("Depth")))) error_types.append("NoDepth") elif self.depth_exist: depth_images = len( listDirectory(self.savePathJoin("Depth"), extension="png")) if depth_images != ori_images and ori_images != 0: errors["Info"].append(( "Depth image number {0} doesn't match video image number {1} -> Recalculating depth" .format(depth_images, ori_images))) error_types.append("NoDepth") # Check ground truth if self.gt_exist and not os.path.isfile(self.user["GT"]): errors["Info"].append( ("Ground Truth file {0} doesn't exist -> File won't be used". format(self.user["GT"]))) error_types.append("NoGT") # Check super pixel labels if (self.super_pixel_method != "" and os.path.exists( os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method)) and ori_images != 0 and len( listDirectory( os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method), extension=".npy", )) != ori_images): errors["Info"].append(( "Super pixel label number {0} doesn't match image number {1} -> Recalculating super pixel labels" .format( len( listDirectory( os.path.join( self.savePathJoin("Super_Pixel"), self.super_pixel_method, ), extension=".npy", )), ori_images, ))) error_types.append("LabelError") # Check object detection if self.ui.c_object_detection.isChecked() and os.path.exists( self.savePathJoin("ObjectDetection")): if (len( listDirectory(self.savePathJoin("ObjectDetection"), extension=".png")) != ori_images): errors["Info"].append( "Object Detection image number {0} doesn't match image number of video {1} -> Recalculating object detection" .format( len( listDirectory(self.savePathJoin("ObjectDetection"), extension=".png")), ori_images, )) error_types.append("ObDetError") elif (len( listDirectory(self.savePathJoin("ObjectDetection"), extension=".npy")) != ori_images): errors["Info"].append( "Object Detection numpy array number {0} doesn't match image number of video {1} -> Recalculating object detection" .format( len( listDirectory(self.savePathJoin("ObjectDetection"), extension=".npy")), ori_images, )) error_types.append("ObDetError") answer = "" if len(errors["Info"]) > 0 and len(errors["Critical"]) == 0: msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText( "Some calculations might not run the way you expect them.\nIn show details check the right side of the arrows to see what will happen." ) msg.setWindowTitle("Information") all_info = "" for info in errors["Info"]: all_info += info + "\n\n" msg.setDetailedText(all_info) msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Abort) answer = msg.exec_() elif len(errors["Critical"]) > 0: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText( "Found critical error\nCouldn't start run, see show details for more information" ) msg.setWindowTitle("Critical Error") all_info = "" for info in errors["Critical"]: all_info += info + "\n" msg.setDetailedText(all_info) msg.setStandardButtons(QMessageBox.Abort) answer = msg.exec_() if answer != int("0x00040000", 16): for ty in error_types: logging.info("Solve error: {0}".format(ty)) if ty == "NoImage": self.img_exist = False self.of_exist = False self.back_of_exist = False self.depth_exist = False elif ty == "NoOf": self.of_exist = False self.back_of_exist = False elif ty == "NoDepth": self.depth_exist = False elif ty == "NoGT": self.gt_exist = False self.user["GT"] = "" elif ty == "LabelError": self.create_super_pixel_label = True shutil.rmtree( os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method)) elif ty == "ObDetError": self.object_detection_dir_exist = False shutil.rmtree(self.savePathJoin("ObjectDetection")) return answer == int("0x00040000", 16) or stop_calculation def calculateError(self, errorMessage): """Called if there were errors while calculating the results, creates QMessageBox Arguments: errorMessage {str} -- error message to be shown in the QMessageBox """ self.no_error = False QMessageBox.warning(self, "Found Error", errorMessage, QMessageBox.Ok, QMessageBox.Ok) def buildParamsDict(self): """Build parameter dictionary to forward parameters for the calcRunner class """ self.params_dict = { "img_dir": self.savePathJoin("Images"), "depth_dir": self.savePathJoin("Depth"), "back_of_dir": self.savePathJoin("Back_Of"), "of_dir": self.savePathJoin("Of"), "save_dir": self.user["Save"], "high": self.high, "low": self.low, "run_dict": self.run_dict, "of_model": self.app.get_resource( os.path.join("of_models", "network-default.pytorch")), "depth_model": self.app.get_resource( os.path.join("depth_models", "model_city2kitti.meta")), "yolo_weights": self.app.get_resource(os.path.join("yolo", "yolov3.weights")), "yolo_v": self.app.get_resource(os.path.join("yolo", "yolov3.cfg")), "coco_names": self.app.get_resource(os.path.join("yolo", "coco.names")), "object_detection_dir": self.savePathJoin("ObjectDetection"), "plot_speed_dir": PLOT_SPEED_DIR, "plot_crash_dir": PLOT_CRASH_DIR, "numbers_dir": NP_DIR, "plot_error_dir": PLOT_ERROR_DIR, "speed_gt": self.user["GT"], "vid_path": self.user["Video"], "super_pixel_method": self.super_pixel_method, "super_pixel_dir": SUPER_PIXEL_DIR, "send_video_frame": False, "create_csv": self.ui.c_csv.isChecked(), "create_draw": self.ui.c_draw.isChecked(), "create_velocity": self.ui.c_velocity.isChecked(), "create_video_fps": int(self.ui.t_fps.text()), "optimize_params": self.ui.c_optimize.isChecked(), "super_pixel_label_dir": os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method), } def startRun(self): """Check for errors and start calculations """ self.checkFiles() if self.errorChecks(): logging.info("Error Check Failed") return self.disableButtons() self.sendUser.emit(self.user) logging.info("Start Run Class") self.createDirs() self.buildRunDict() def startCalcThread(self): """Starting calculations on another thread """ # 1 - create Worker and Thread inside the Form self.worker = calcRunner.CalculationRunner(self.params_dict) self.thread = QThread() # no parent! self.worker.labelUpdate.connect(self.labelUpdate) self.worker.update.connect(self.progressUpdate) self.worker.error.connect(self.calculateError) self.worker.finished.connect(self.finishThread) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.startThread) # 6 - Start the thread self.thread.start() def progressUpdate(self, value): """Updating both progressbar Arguments: value {int} -- current progress """ self.progressBar.setValue(value) logging.info("Update progressbar to: {0}".format(self.run_count)) if self.progressAllBar is not None: self.run_count += 1 self.progressAllBar.setValue(self.run_count) def finishThread(self): """Clean up after calculations thread finished """ logging.info("Fin Thread") self.buildCreatedDict() self.cleanThread() self.accept() def cleanThread(self): """Cleans the active thread """ logging.info("Clean Thread") self.thread.quit() self.thread.wait() def labelUpdate(self, run_dict): """Update progressbar label's text to show the current calculation Arguments: run_dict {dictionary} -- dictionary of the current process """ self.progressBar.reset() self.progressBar.setMinimum(1) self.progressBar.setMaximum(run_dict["Progress"]) self.progressLabel.setText(run_dict["Text"]) def showProgressBar(self): """Creates two progressbars to show how the calculation progresses """ logging.info("Show progress bar") self.progressLabel = QLabel(self) font = QFont() font.setFamily("GE Inspira") font.setPointSize(20) self.progressLabel.setFont(font) self.progressLabel.setAlignment(Qt.AlignCenter) self.progressLabel.setText("Hello") self.ui.layout_v.addWidget(self.progressLabel) self.progressBar = QProgressBar(self) # Progress bar created self.progressBar.setRange(0, 0) self.ui.layout_v.addWidget(self.progressBar) def addAllProgressBar(self): """Adds the progress bar which tracks the progress of all calculations """ all_run = sum([ self.run_dict[key]["Progress"] for key in self.run_dict if self.run_dict[key]["Run"] ]) logging.info("All run: {0}".format(all_run)) self.progressAllBar = QProgressBar(self) # Progress bar created self.progressAllBar.setMinimum(1) self.progressAllBar.setMaximum(all_run) self.ui.layout_v.addWidget(self.progressAllBar) self.progressAllBar.setValue(1) def buildCreatedDict(self): """Build dictionary containing the created plots (if there's any). Send signal with this dictionary """ self.created = {} if self.ui.c_speed_plot.isChecked(): self.created["Speed_Plot"] = self.savePathJoin(PLOT_SPEED_DIR) if self.ui.c_error_plot.isChecked() and self.no_error: self.created["Error_Plot"] = self.savePathJoin(PLOT_ERROR_DIR) if self.ui.c_crash_plot.isChecked(): self.created["Crash_Plot"] = self.savePathJoin(PLOT_CRASH_DIR) self.sendCreated.emit(self.created) def buildRunDict(self): """Create images from video in another thread, loads the image number otherwise """ self.showProgressBar() ori_images = 0 if self.img_exist: ori_images = len(listDirectory(self.savePathJoin("Images"))) self.buildRunDictMain(ori_images) else: self.run_dict["Video"] = { "Run": True, "Progress": ori_images, "Text": "Preparing video", } self.buildParamsDict() self.params_dict["send_video_frame"] = True self.progressLabel.setText("Create images from video") self.worker = calcRunner.CalculationRunner( self.params_dict) # no parent! self.thread = QThread() # no parent! self.worker.labelUpdate.connect(self.labelUpdate) self.worker.update.connect(self.progressUpdate) self.worker.videoFrame.connect(self.setVidFrame) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.startThread) self.thread.start() def setVidFrame(self, ori_images): """Gets called when the image creation from video finishes Arguments: ori_images {int} -- number of images in the video """ self.cleanThread() if ori_images == 0: logging.critical("Video Image number 0") else: self.buildRunDictMain(ori_images) def buildRunDictMain(self, ori_images): """Build dictionary with all calculations Dictionary fields: -Run: {bool} true if needs to be calculated, false otherwise -Progress: {int} the amount of steps to complete the calculation (used to update the progressbar) -Text: {str} the text to be showed in the progressbar label's when the calculation is running """ self.run_dict["Of"] = { "Run": not self.of_exist, "Progress": ori_images, "Text": "Running optical flow", } self.run_dict["Back_Of"] = { "Run": not self.back_of_exist, "Progress": ori_images, "Text": "Running back optical flow", } self.run_dict["Depth"] = { "Run": not self.depth_exist, "Progress": ori_images, "Text": "Running depth estimation", } self.run_dict["Speed"] = { "Run": True, "Progress": ori_images, "Text": "Running speed estimation", } self.run_dict["Optimization"] = { "Run": self.ui.c_optimize.isChecked(), "Progress": ori_images * 9, "Text": "Running parameter optimization", } self.run_dict["Of_Vid"] = { "Run": self.ui.c_of.isChecked(), "Progress": ori_images, "Text": "Creating optical flow video", } self.run_dict["Back_Of_Vid"] = { "Run": self.ui.c_back_of.isChecked(), "Progress": ori_images, "Text": "Creating backward optical flow video", } self.run_dict["Depth_Vid"] = { "Run": self.ui.c_depth.isChecked(), "Progress": ori_images, "Text": "Creating depth estimation video", } self.run_dict["Speed_Plot"] = { "Run": self.ui.c_speed_plot.isChecked(), "Progress": ori_images, "Text": "Creating plot for speed values", } self.run_dict["Crash_Plot"] = { "Run": self.ui.c_crash_plot.isChecked(), "Progress": ori_images, "Text": "Creating plot for time to crash", } self.run_dict["Error_Plot"] = { "Run": self.ui.c_error_plot.isChecked() and self.gt_exist, "Progress": ori_images, "Text": "Creating plot for speed error", } self.run_dict["Speed_Plot_Video"] = { "Run": self.ui.c_speed_plot_video.isChecked(), "Progress": ori_images, "Text": "Creating speed plot video", } self.run_dict["Error_Plot_Video"] = { "Run": self.ui.c_error_plot_video.isChecked() and self.gt_exist, "Progress": ori_images, "Text": "Creating error plot video", } self.run_dict["Crash_Plot_Video"] = { "Run": self.ui.c_crash_plot_video.isChecked(), "Progress": ori_images, "Text": "Creating time to crash plot video", } self.run_dict["Super_Pixel_Video"] = { "Run": self.ui.combo_superpixel.currentIndex() != 0 and self.ui.c_super_pixel_video.isChecked(), "Progress": ori_images, "Text": "Creating super pixel video", } self.run_dict["Super_Pixel_Label"] = { "Run": self.create_super_pixel_label, "Progress": ori_images, "Text": "Creating {0} superpixel labels".format(self.super_pixel_method), } self.run_dict["Object_Detection"] = { "Run": (self.ui.c_object_detection.isChecked() or self.ui.c_crash_plot.isChecked()) and not self.object_detection_dir_exist, "Progress": ori_images, "Text": "Running Object Detection", } self.addAllProgressBar() self.buildParamsDict() self.saveUser() self.startCalcThread() def disableButtons(self): """Disable widgets while the calculation is running """ self.ui.b_run.setEnabled(False) self.ui.b_colour.setEnabled(False) self.ui.b_ground_truth.setEnabled(False) self.ui.b_vid.setEnabled(False) self.ui.b_save.setEnabled(False) self.ui.t_low.setEnabled(False) self.ui.t_high.setEnabled(False) self.ui.t_fps.setEnabled(False) self.ui.combo_superpixel.setEnabled(False) self.ui.c_super_pixel_video.setEnabled(False) self.ui.c_csv.setEnabled(False) self.ui.c_draw.setEnabled(False) self.ui.c_velocity.setEnabled(False) self.ui.c_of.setEnabled(False) self.ui.c_back_of.setEnabled(False) self.ui.c_depth.setEnabled(False) self.ui.c_speed_plot.setEnabled(False) self.ui.c_error_plot.setEnabled(False) self.ui.c_crash_plot.setEnabled(False) self.ui.c_error_plot_video.setEnabled(False) self.ui.c_speed_plot_video.setEnabled(False) self.ui.c_crash_plot_video.setEnabled(False) self.ui.c_optimize.setEnabled(False) self.ui.c_object_detection.setEnabled(False) def savePathJoin(self, path): """Join the given path with the save path (stored in user info) Arguments: path {str} -- path to join after the save path Returns: str -- joined path """ return os.path.join(self.user["Save"], path) def createDir(self, dir_name): """Create directory with the given name in the save path (stored in user info) Arguments: dir_name {str} -- path to the newly created dir """ os.mkdir(os.path.join(self.user["Save"], dir_name)) def createDirs(self): """Create or recreate (destroy and create) directories for the calculations """ logging.info("Creating Directories") if not self.img_exist: self.reCreateDir(self.savePathJoin("Images")) if not self.of_exist: self.reCreateDir(self.savePathJoin("Of")) if not self.back_of_exist: self.reCreateDir(self.savePathJoin("Back_Of")) if not self.depth_exist: self.reCreateDir(self.savePathJoin("Depth")) if not self.object_detection_dir_exist and ( self.ui.c_object_detection.isChecked() or self.ui.c_crash_plot.isChecked()): self.reCreateDir(self.savePathJoin("ObjectDetection")) if self.super_pixel_method != "" and not os.path.exists( os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method)): os.makedirs( os.path.join(self.savePathJoin("Super_Pixel"), self.super_pixel_method)) self.reCreateDir(RESULTS) self.reCreateDir(NP_DIR) self.reCreateDir(MASK_DIR) if self.ui.c_crash_plot.isChecked(): self.reCreateDir(PLOT_CRASH_DIR) if self.ui.c_draw.isChecked(): self.reCreateDir(DRAW_DIR) if self.ui.c_velocity.isChecked(): self.reCreateDir(VL_DIR) if self.ui.c_speed_plot.isChecked(): self.reCreateDir(PLOT_SPEED_DIR) if self.super_pixel_method != "": self.reCreateDir(SUPER_PIXEL_DIR) if self.user["GT"] != "" and self.ui.c_error_plot.isChecked(): self.reCreateDir(PLOT_ERROR_DIR) def reCreateDir(self, name): """Create directory with the given name in save dir (stored in user info). If it already exists, then delete it first Arguments: name {str} -- name of the new directory """ path = self.savePathJoin(name) if os.path.exists(path): shutil.rmtree(path) os.makedirs(path) def pickColour(self): """Opens a QColorDialog to choose a colour """ colour = QColorDialog.getColor() if colour.isValid(): self.user["Colour"] = colour.name() self.ui.l_colour.setText(self.user["Colour"])
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 pyqtSignal *move_done_signal* instance of pyqtSignal *update_settings_signal* instance of pyqtSignal *status_signal* instance of pyqtSignal *bounds_signal* instance of pyqtSignal *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, pyqtSignal, QStatusBar, ParameterTree """ init_signal = pyqtSignal(bool) command_stage = pyqtSignal(ThreadCommand) command_tcpip = pyqtSignal(ThreadCommand) move_done_signal = pyqtSignal( 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 = pyqtSignal(edict) status_signal = pyqtSignal(str) bounds_signal = pyqtSignal(bool) params = daq_move_params def __init__(self, parent, title="pymodaq Move", preset=None, init=False, controller_ID=-1): # 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 # controller_ID is a unique random integer generated by the parent caller. To differenciate various instance of this class self.logger = utils.set_logger(f'{logger.name}.{title}') self.logger.info(f'Initializing DAQ_Move: {title}') QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates)) super(DAQ_Move, self).__init__() 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 = DAQ_Move_Stage_type.names('daq_move') 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() self.settings.child('main_settings', 'controller_ID').setValue(controller_ID) 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())) # set managers options if preset is not None: for preset_dict in preset: # fo instance preset_dict=dict(object='Stage_type_combo',method='setCurrentIndex',value=1) if hasattr(self.ui, preset_dict['object']): obj = getattr(self.ui, preset_dict['object']) if hasattr(obj, preset_dict['method']): setattr(obj, preset_dict['method'], preset_dict['value']) QtWidgets.QApplication.processEvents() #initialize the controller if init=True if init: 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_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 path is not None: if 'main_settings' not in path: self.update_settings_signal.emit( edict(path=['detector_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() @pyqtSlot(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 = custom_tree.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.exception(str(e)) 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() @pyqtSlot() def raise_timeout(self): """ Update status with "Timeout occured" statement. See Also -------- update_status """ self.update_status("Timeout occured", wait_time=self.wait_time, log_type="log") 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 != 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) @pyqtSlot(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() class_ = getattr(getattr(plugins, '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) @pyqtSlot(int) def stage_changed(self, index=0): """ Deprecated the main interface should not be dependant of the plugin type, especially because it may not be installed | Update the User Interface from the DAQ_Move_Stage_Type given by the position of index parameter. | | In case of Kinesis_Flipper hardware, update the Move_abs values to adapt the programm to the hardware, else re-init the Move_abs to default value. =============== =========== ==================================================================== **Parameters** **Type** **Description** *index* enum list DAQ_Move_Stage_Type to be checked (corresponding to hardware type) =============== =========== ==================================================================== See Also -------- move_Abs """ pass # if index == DAQ_Move_Stage_type['Kinesis_Flipper']: #Kinesis_Flipper # self.ui.Moveto_pb_bis_2.setVisible(True) # self.ui.Moveto_pb_bis.clicked.disconnect() # self.ui.Moveto_pb_bis.clicked.connect(lambda: self.move_Abs(1)) # self.ui.Moveto_pb_bis_2.clicked.connect(lambda: self.move_Abs(2)) # # else: # self.ui.Moveto_pb_bis_2.setVisible(False) # self.ui.Moveto_pb_bis.clicked.disconnect() # self.ui.Moveto_pb_bis.clicked.connect(lambda: self.move_Abs(self.ui.Abs_position_sb_bis.value())) 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)) @pyqtSlot(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: 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) 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)
class FlashingDialog(QDialog): def __init__(self, parent): super().__init__() self.setWindowTitle("Flashing...") esptool.sw.read_start.connect(self.read_start) esptool.sw.read_progress.connect(self.read_progress) esptool.sw.read_finished.connect(self.read_finished) esptool.sw.erase_start.connect(self.erase_start) esptool.sw.erase_finished.connect(self.erase_finished) esptool.sw.write_start.connect(self.write_start) esptool.sw.write_progress.connect(self.write_progress) esptool.sw.write_finished.connect(self.write_finished) self.setFixedWidth(400) self.nrBinFile = QNetworkRequest() self.parent = parent vl = VLayout(10, 10) self.setLayout(vl) self.bin_data = b"" self.error_msg = None self.progress_task = QProgressBar() self.progress_task.setFixedHeight(45) self.task = QLabel() self.erase_timer = QTimer() self.erase_timer.setSingleShot(False) self.erase_timer.timeout.connect(self.erase_progress) self.btns = QDialogButtonBox(QDialogButtonBox.Abort) self.dlgText = QLabel( "Press the Boot button for a few seconds to start the flashing process" ) vl.addWidgets([self.dlgText, self.task, self.progress_task, self.btns]) self.btns.rejected.connect(self.abort) # process starts self.bin_file = parent.bin_file self.run_esptool() def updateBinProgress(self, recv, total): self.progress_task.setValue(recv // total * 100) def read_start(self): self.progress_task.setValue(0) self.task.setText("Saving image backup...") def read_progress(self, value): self.progress_task.setValue(value) def read_finished(self): self.progress_task.setValue(100) self.task.setText("Writing done.") def erase_start(self): self.btns.setEnabled(False) self.progress_task.setValue(0) self.task.setText("Erasing flash... (this may take a while)") self.erase_timer.start(1000) def erase_progress(self): self.progress_task.setValue(self.progress_task.value() + 5) def erase_finished(self): self.progress_task.setValue(100) self.task.setText("Erasing done.") self.erase_timer.stop() self.btns.setEnabled(True) def write_start(self): self.dlgText.setText("Flashing in progress...") self.progress_task.setValue(0) self.task.setText("Writing image...") def write_progress(self, value): self.progress_task.setValue(value) def write_finished(self): self.progress_task.setValue(100) self.task.setText("Writing done.") self.accept() def run_esptool(self): self.espthread = QThread() self.espworker = ESPWorker(self.parent.cbxPort.currentData(), self.bin_file) self.espworker.port_error.connect(self.error) self.espworker.moveToThread(self.espthread) self.espthread.started.connect(self.espworker.execute) self.espthread.start() def abort(self): self.espworker.stop() self.espthread.quit() self.espthread.wait(2000) self.reject() def error(self, e): self.error_msg = e self.reject() def accept(self): self.espworker.stop() self.espthread.quit() self.espthread.wait(2000) self.done(QDialog.Accepted)
class MDTGUISingleModel(QMainWindow, Ui_MainWindow): def __init__(self, shared_state, computations_thread): super().__init__() self.setupUi(self) self._shared_state = shared_state self._computations_thread = computations_thread self._computations_thread.signal_starting.connect( self.computations_started) self._computations_thread.signal_finished.connect( self.computations_finished) self._stdout_old = sys.stdout self._stderr_old = sys.stderr self._logging_update_queue = Queue() self._logging_update_thread = QThread() self._message_receiver = MessageReceiver(self._logging_update_queue) self._message_receiver.text_message_signal.connect(self.update_log) self._message_receiver.moveToThread(self._logging_update_thread) self._logging_update_thread.started.connect(self._message_receiver.run) self._logging_update_thread.start() sys.stdout = ForwardingListener(self._logging_update_queue) sys.stderr = ForwardingListener(self._logging_update_queue) LogDispatchHandler.add_listener( ForwardingListener(self._logging_update_queue)) print_welcome_message() self.actionExit.setShortcuts(['Ctrl+q', 'Ctrl+w']) self.action_RuntimeSettings.triggered.connect( lambda: RuntimeSettingsDialog(self).exec_()) self.action_MapsVisualizer.triggered.connect( lambda: mdt.gui.maps_visualizer.main.start_gui(app_exec=False)) self.actionAbout.triggered.connect(lambda: AboutDialog(self).exec_()) self.action_GetExampleData.triggered.connect( lambda: GetExampleDataDialog(self, shared_state).exec_()) self.executionStatusLabel.setText('Idle') self.executionStatusIcon.setPixmap( QtGui.QPixmap(":/main_gui/icon_status_red.png")) self.fit_model_tab = FitModelTab(shared_state, self._computations_thread) self.fit_model_tab.setupUi(self.fitModelTab) self.generate_mask_tab = GenerateBrainMaskTab( shared_state, self._computations_thread) self.generate_mask_tab.setupUi(self.generateBrainMaskTab) self.generate_roi_mask_tab = GenerateROIMaskTab( shared_state, self._computations_thread) self.generate_roi_mask_tab.setupUi(self.generateROIMaskTab) self.generate_protocol_tab = GenerateProtocolTab( shared_state, self._computations_thread) self.generate_protocol_tab.setupUi(self.generateProtocolTab) self.tabs = [ self.fit_model_tab, self.generate_mask_tab, self.generate_roi_mask_tab, self.generate_protocol_tab ] self.MainTabs.currentChanged.connect( lambda index: self.tabs[index].tab_opened()) def closeEvent(self, event): sys.stdout = self._stdout_old sys.stderr = self._stderr_old self._message_receiver.is_running = False self._logging_update_thread.quit() self._logging_update_thread.wait(10) super().closeEvent(event) def send_sigint(self, *args): self.close() @pyqtSlot() def computations_started(self): self.executionStatusLabel.setText('Computing') self.executionStatusIcon.setPixmap( QtGui.QPixmap(":/main_gui/icon_status_green.png")) @pyqtSlot() def computations_finished(self): self.executionStatusLabel.setText('Idle') self.executionStatusIcon.setPixmap( QtGui.QPixmap(":/main_gui/icon_status_red.png")) @pyqtSlot(str) def update_log(self, string): sb = self.loggingTextBox.verticalScrollBar() scrollbar_position = sb.value() autoscroll = scrollbar_position == sb.maximum() self.loggingTextBox.moveCursor(QtGui.QTextCursor.End) self.loggingTextBox.insertPlainText(string) if autoscroll: sb.setValue(sb.maximum()) else: sb.setValue(scrollbar_position)
class ThreadlinkUtility(QMainWindow): """Main class for the PCBA Test Utility. Creates main window for the program, the file menu, status bar, and the settings/configuration window. """ def __init__(self): super().__init__() self.system_font = QApplication.font().family() self.label_font = QFont(self.system_font, 12) self.config_font = QFont(self.system_font, 12) self.config_path_font = QFont(self.system_font, 12) self.settings = QSettings("BeadedStream", "Threadlink TestUtility") settings_defaults = { "user_id": "", "port1_tac_id": "", "hex_files_path": "/path/to/hex/files", "report_dir_path": "/path/to/report/folder", "atprogram_file_path": "/path/to/atprogram.exe" } for key in settings_defaults: if not self.settings.value(key): self.settings.setValue(key, settings_defaults[key]) self.sm = serialmanager.SerialManager() self.serial_thread = QThread() self.sm.moveToThread(self.serial_thread) self.serial_thread.start() self.m = model.Model() self.r = report.Report() self.sm.port_unavailable_signal.connect(self.port_unavailable) # Part number : [serial prefix, procedure class] self.product_data = { "45211-01": ["THL", threadlink.Threadlink], } # Create program actions. self.config = QAction("Settings", self) self.config.setShortcut("Ctrl+E") self.config.setStatusTip("Program Settings") self.config.triggered.connect(self.configuration) self.quit = QAction("Quit", self) self.quit.setShortcut("Ctrl+Q") self.quit.setStatusTip("Exit Program") self.quit.triggered.connect(self.close) self.about_tu = QAction("About Threadlink Utility", self) self.about_tu.setShortcut("Ctrl+U") self.about_tu.setStatusTip("About Program") self.about_tu.triggered.connect(self.about_program) self.aboutqt = QAction("About Qt", self) self.aboutqt.setShortcut("Ctrl+I") self.aboutqt.setStatusTip("About Qt") self.aboutqt.triggered.connect(self.about_qt) # Create menubar self.menubar = self.menuBar() self.file_menu = self.menubar.addMenu("&File") self.file_menu.addAction(self.config) self.file_menu.addAction(self.quit) self.serial_menu = self.menubar.addMenu("&Serial") self.serial_menu.installEventFilter(self) self.ports_menu = QMenu("&Ports", self) self.serial_menu.addMenu(self.ports_menu) self.ports_menu.aboutToShow.connect(self.populate_ports) self.ports_group = QActionGroup(self) self.ports_group.triggered.connect(self.connect_port) self.help_menu = self.menubar.addMenu("&Help") self.help_menu.addAction(self.about_tu) self.help_menu.addAction(self.aboutqt) self.initUI() self.center() def center(self): """Centers the application on the screen the mouse pointer is currently on.""" frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) def resource_path(self, relative_path): """Gets the path of the application relative root path to allow us to find the logo.""" if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.abspath("."), relative_path) def initUI(self): """"Sets up the UI.""" RIGHT_SPACING = 350 LINE_EDIT_WIDTH = 200 self.central_widget = QWidget() self.tester_id_lbl = QLabel("Please enter tester ID: ") self.tester_id_lbl.setFont(self.label_font) self.pcba_pn_lbl = QLabel("Please select PCBA part number: ") self.pcba_pn_lbl.setFont(self.label_font) self.pcba_sn_lbl = QLabel("Please enter or scan DUT serial number: ") self.pcba_sn_lbl.setFont(self.label_font) self.tester_id_input = QLineEdit() self.tester_id_input.setText(self.settings.value("user_id")) self.pcba_sn_input = QLineEdit() self.tester_id_input.setFixedWidth(LINE_EDIT_WIDTH) self.pcba_sn_input.setFixedWidth(LINE_EDIT_WIDTH) self.pcba_pn_input = QComboBox() self.pcba_pn_input.addItem("45211-01") self.pcba_pn_input.setFixedWidth(LINE_EDIT_WIDTH) self.start_btn = QPushButton("Start") self.start_btn.setFixedWidth(200) self.start_btn.setAutoDefault(True) self.start_btn.clicked.connect(self.parse_values) self.logo_img = QPixmap(self.resource_path("h_logo.png")) self.logo_img = self.logo_img.scaledToWidth(600) self.logo = QLabel() self.logo.setPixmap(self.logo_img) hbox_logo = QHBoxLayout() hbox_logo.addStretch() hbox_logo.addWidget(self.logo) hbox_logo.addStretch() hbox_test_id = QHBoxLayout() hbox_test_id.addStretch() hbox_test_id.addWidget(self.tester_id_lbl) hbox_test_id.addWidget(self.tester_id_input) hbox_test_id.addSpacing(RIGHT_SPACING) hbox_pn = QHBoxLayout() hbox_pn.addStretch() hbox_pn.addWidget(self.pcba_pn_lbl) hbox_pn.addWidget(self.pcba_pn_input) hbox_pn.addSpacing(RIGHT_SPACING) hbox_sn = QHBoxLayout() hbox_sn.addStretch() hbox_sn.addWidget(self.pcba_sn_lbl) hbox_sn.addWidget(self.pcba_sn_input) hbox_sn.addSpacing(RIGHT_SPACING) hbox_start_btn = QHBoxLayout() hbox_start_btn.addStretch() hbox_start_btn.addWidget(self.start_btn) hbox_start_btn.addSpacing(RIGHT_SPACING) vbox = QVBoxLayout() vbox.addStretch() vbox.addLayout(hbox_logo) vbox.addSpacing(100) vbox.addLayout(hbox_test_id) vbox.addSpacing(50) vbox.addLayout(hbox_pn) vbox.addSpacing(50) vbox.addLayout(hbox_sn) vbox.addSpacing(50) vbox.addLayout(hbox_start_btn) vbox.addStretch() self.central_widget.setLayout(vbox) self.setCentralWidget(self.central_widget) self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT) self.setWindowTitle( "BeadedStream Manufacturing Threadlink Test Utility") def create_messagebox(self, type, title, text, info_text): """A helper method for creating message boxes.""" msgbox = QMessageBox(self) msgbox.setWindowTitle(title) msgbox.setText(text) msgbox.setInformativeText(info_text) if type == "Warning": msgbox.setIcon(QMessageBox.Warning) elif type == "Error": msgbox.setIcon(QMessageBox.Error) elif type == "Information": msgbox.setIcon(QMessageBox.Information) else: raise InvalidMsgType return msgbox def about_program(self): """Displays information about the program.""" QMessageBox.about(self, "About Threadlink Test Utility", ABOUT_TEXT) def about_qt(self): """Displays information about Qt.""" QMessageBox.aboutQt(self, "About Qt") def populate_ports(self): """Populates ports menu from connected COM ports.""" ports = serialmanager.SerialManager.scan_ports() self.ports_menu.clear() if not ports: self.ports_menu.addAction("None") self.sm.close_port() for port in ports: port_description = port.description action = self.ports_menu.addAction(port_description) port_name = port.device if self.sm.is_connected(port_name): action.setCheckable(True) action.setChecked(True) self.ports_group.addAction(action) def connect_port(self, action: QAction): """Connects to a COM port by parsing the text from a clicked QAction menu object.""" p = "COM[0-9]+" m = re.search(p, action.text()) if m: port_name = m.group() self.sm.open_port(port_name) else: QMessageBox.warning(self, "Warning", "Invalid port selection!") def port_unavailable(self): """Displays warning message about unavailable port.""" QMessageBox.warning(self, "Warning", "Port unavailable!") def parse_values(self): """Parses and validates input values from the start page.""" self.tester_id = self.tester_id_input.text().upper() self.settings.setValue("user_id", self.tester_id) self.pcba_pn = self.pcba_pn_input.currentText() self.pcba_sn = self.pcba_sn_input.text().upper() if (self.tester_id and self.pcba_pn and self.pcba_sn): # The serial number should be seven characters long and start with # the specific prefix for the given product. if (self.pcba_sn[0:3] == self.product_data[self.pcba_pn][0] and len(self.pcba_sn) == 7): self.r.write_data("tester_id", self.tester_id, "PASS") self.r.write_data("pcba_sn", self.pcba_sn, "PASS") self.r.write_data("pcba_pn", self.pcba_pn, "PASS") else: self.err_msg = self.create_messagebox("Warning", "Error", "Error", "Bad serial number!") self.err_msg.show() return else: self.err_msg = self.create_messagebox("Warning", "Error", "Error", "Missing value!") self.err_msg.show() return self.start_procedure() def start_procedure(self): """Sets up procedure layout by creating test statuses and initializing the appropriate board class (currently D505, potentially others in the future).""" central_widget = QWidget() status_lbl_stylesheet = ("QLabel {border: 2px solid grey;" "color: black; font-size: 20px}") status_style_pass = """QLabel {background: #8cff66; border: 2px solid grey; font-size: 20px}""" # ______Labels______ self.tester_id_status = QLabel(f"Tester ID: {self.tester_id}") self.pcba_pn_status = QLabel(f"PCBA PN: {self.pcba_pn}") self.pcba_sn_status = QLabel(f"PCBA SN: {self.pcba_sn}") self.input_i_status = QLabel(f"Input Current: _____ mA") self.supply_5v_status = QLabel("5V Supply: _____V") self.output_2p5v_status = QLabel("2.5V Output: _____V") self.supply_1p8v_status = QLabel("1.8V Supply: _____V") self.xmega_prog_status = QLabel("XMega Programming: _____") self.one_wire_prog_status = QLabel("1-Wire Programming:_____") self.internal_5v_status = QLabel("Internal 5V: _____V") self.tac_id_status = QLabel("TAC ID: _____") self.hall_effect_status = QLabel("Hall Effect Sensor Test:_____") self.led_test_status = QLabel("LED Test:_____") self.tester_id_status.setStyleSheet(status_style_pass) self.pcba_pn_status.setStyleSheet(status_style_pass) self.pcba_sn_status.setStyleSheet(status_style_pass) self.input_i_status.setStyleSheet(status_lbl_stylesheet) self.supply_5v_status.setStyleSheet(status_lbl_stylesheet) self.output_2p5v_status.setStyleSheet(status_lbl_stylesheet) self.supply_1p8v_status.setStyleSheet(status_lbl_stylesheet) self.xmega_prog_status.setStyleSheet(status_lbl_stylesheet) self.one_wire_prog_status.setStyleSheet(status_lbl_stylesheet) self.internal_5v_status.setStyleSheet(status_lbl_stylesheet) self.tac_id_status.setStyleSheet(status_lbl_stylesheet) self.hall_effect_status.setStyleSheet(status_lbl_stylesheet) self.led_test_status.setStyleSheet(status_lbl_stylesheet) # ______Layout______ status_vbox1 = QVBoxLayout() status_vbox1.setSpacing(10) status_vbox1.addWidget(self.tester_id_status) status_vbox1.addWidget(self.pcba_pn_status) status_vbox1.addWidget(self.pcba_sn_status) status_vbox1.addWidget(self.input_i_status) status_vbox1.addWidget(self.supply_5v_status) status_vbox1.addWidget(self.output_2p5v_status) status_vbox1.addWidget(self.supply_1p8v_status) status_vbox1.addWidget(self.xmega_prog_status) status_vbox1.addWidget(self.one_wire_prog_status) status_vbox1.addWidget(self.internal_5v_status) status_vbox1.addWidget(self.tac_id_status) status_vbox1.addWidget(self.hall_effect_status) status_vbox1.addWidget(self.led_test_status) status_vbox1.addStretch() status_group = QGroupBox("Test Statuses") status_group.setFont(self.label_font) status_group.setLayout(status_vbox1) # Use the product data dictionary to call the procdure class that # corresponds to the part number. Create an instance of it passing it # the instances of test_utility, model, serial_manager and report. self.procedure = self.product_data[self.pcba_pn][1](self, self.m, self.sm, self.r) grid = QGridLayout() grid.setColumnStretch(0, 5) grid.setColumnStretch(1, 15) grid.addWidget(status_group, 0, 0, Qt.AlignTop) grid.addWidget(self.procedure, 0, 1) # layout = QHBoxLayout() # layout.addWidget(self.procedure) central_widget.setLayout(grid) self.setCentralWidget(central_widget) def configuration(self): """Sets up configuration/settings window elements.""" FILE_BTN_WIDTH = 30 self.settings_widget = QDialog(self) port1_lbl = QLabel("Port 1 TAC ID:") port1_lbl.setFont(self.config_font) self.port1_tac_id = QLineEdit(self.settings.value("port1_tac_id")) port_layout = QGridLayout() port_layout.addWidget(port1_lbl, 0, 0) port_layout.addWidget(self.port1_tac_id, 0, 1) port_group = QGroupBox("TAC IDs") port_group.setLayout(port_layout) self.hex_btn = QPushButton("[...]") self.hex_btn.setFixedWidth(FILE_BTN_WIDTH) self.hex_btn.clicked.connect(self.set_hex_dir) self.hex_lbl = QLabel("Choose the location of hex files: ") self.hex_lbl.setFont(self.config_font) self.hex_path_lbl = QLabel(self.settings.value("hex_files_path")) self.hex_path_lbl.setFont(self.config_path_font) self.hex_path_lbl.setStyleSheet("QLabel {color: blue}") self.report_btn = QPushButton("[...]") self.report_btn.setFixedWidth(FILE_BTN_WIDTH) self.report_btn.clicked.connect(self.set_report_location) self.report_lbl = QLabel("Set report save location: ") self.report_lbl.setFont(self.config_font) self.report_path_lbl = QLabel(self.settings.value("report_dir_path")) self.report_path_lbl.setFont(self.config_path_font) self.report_path_lbl.setStyleSheet("QLabel {color: blue}") self.atprogram_btn = QPushButton("[...]") self.atprogram_btn.setFixedWidth(FILE_BTN_WIDTH) self.atprogram_btn.clicked.connect(self.choose_atprogram_file) self.atprogram_lbl = QLabel("Select atprogram.exe.") self.atprogram_lbl.setFont(self.config_font) self.atprogram_path_lbl = QLabel( self.settings.value("atprogram_file_path")) self.atprogram_path_lbl.setFont(self.config_path_font) self.atprogram_path_lbl.setStyleSheet("QLabel {color: blue}") save_loc_layout = QGridLayout() save_loc_layout.addWidget(self.hex_lbl, 0, 0) save_loc_layout.addWidget(self.hex_btn, 0, 1) save_loc_layout.addWidget(self.hex_path_lbl, 1, 0) save_loc_layout.addWidget(self.report_lbl, 2, 0) save_loc_layout.addWidget(self.report_btn, 2, 1) save_loc_layout.addWidget(self.report_path_lbl, 3, 0) save_loc_layout.addWidget(self.atprogram_lbl, 4, 0) save_loc_layout.addWidget(self.atprogram_btn, 4, 1) save_loc_layout.addWidget(self.atprogram_path_lbl, 5, 0) save_loc_group = QGroupBox("Save Locations") save_loc_group.setLayout(save_loc_layout) apply_btn = QPushButton("Apply Settings") apply_btn.clicked.connect(self.apply_settings) cancel_btn = QPushButton("Cancel") cancel_btn.clicked.connect(self.cancel_settings) button_layout = QHBoxLayout() button_layout.addWidget(cancel_btn) button_layout.addStretch() button_layout.addWidget(apply_btn) hbox_top = QHBoxLayout() hbox_top.addWidget(port_group) hbox_bottom = QHBoxLayout() # hbox_bottom.addStretch() hbox_bottom.addWidget(save_loc_group) # hbox_bottom.addStretch() grid = QGridLayout() grid.addLayout(hbox_top, 0, 0) grid.addLayout(hbox_bottom, 1, 0) grid.addLayout(button_layout, 2, 0) grid.setHorizontalSpacing(100) self.settings_widget.setLayout(grid) self.settings_widget.setWindowTitle( "Threadlink Configuration Settings") self.settings_widget.show() # self.settings_widget.resize(800, 600) # frameGm = self.frameGeometry() # screen = QApplication.desktop().screenNumber( # QApplication.desktop().cursor().pos()) # centerPoint = QApplication.desktop().screenGeometry(screen).center() # frameGm.moveCenter(centerPoint) # self.settings_widget.move(frameGm.topLeft()) def set_hex_dir(self): """Opens file dialog for selecting the hex files directory.""" hex_files_path = QFileDialog.getExistingDirectory( self, "Select hex files directory.") self.hex_path_lbl.setText(hex_files_path) def set_report_location(self): """Opens file dialog for setting the save location for the report.""" report_dir = QFileDialog.getExistingDirectory( self, "Select report save location.") self.report_path_lbl.setText(report_dir) def choose_atprogram_file(self): """Opens file dialog for selecting the atprogram executable.""" atprogram_file_path = QFileDialog.getOpenFileName( self, "Select atprogram.exe.", "", "Application (*.exe)")[0] self.atprogram_path_lbl.setText(atprogram_file_path) def cancel_settings(self): """Close the settings widget without applying changes.""" self.settings_widget.close() def apply_settings(self): """Read user inputs and apply settings.""" p = r"([a-fA-F0-9]){8}" port1_value = self.port1_tac_id.text() if re.fullmatch(p, port1_value): self.settings.setValue("port1_tac_id", port1_value) else: QMessageBox.warning( self.settings_widget, "Warning!", f"Bad TAC ID!\n" "IDs are 8 digit hex values.\n" "E.g.: 000a5296") return self.settings.setValue("hex_files_path", self.hex_path_lbl.text()) self.settings.setValue("report_dir_path", self.report_path_lbl.text()) self.settings.setValue("atprogram_file_path", self.atprogram_path_lbl.text()) QMessageBox.information(self.settings_widget, "Information", "Settings applied!") self.settings_widget.close() def closeEvent(self, event): """Override QWidget closeEvent to provide user with confirmation dialog and ensure threads are terminated appropriately.""" event.accept() quit_msg = "Are you sure you want to exit the program?" confirmation = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) if confirmation == QMessageBox.Yes: self.serial_thread.quit() self.serial_thread.wait() event.accept() else: event.ignore()
class ConsoleWidget(RichJupyterWidget, PMDockObject): def __init__(self, *args, **kwargs): super(ConsoleWidget, self).__init__(*args, **kwargs) self.is_first_execution = True self.confirm_restart = False # print(cmd) self.commands_pool = [] # [('import sys',True,''),(cmd,False,'')] # print(self.commands_pool) def change_ui_theme(self, style: str): 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) _ = lambda s: s self.context_menu = QMenu() restart_action = self.context_menu.addAction(_('Restart')) restart_action.triggered.connect(self._restart_kernel) save_action = self.context_menu.addAction(_('Save as ')) cancel_action = self.context_menu.addAction(_('Undo')) redo_action = self.context_menu.addAction(_('Redo')) delete_action = self.context_menu.addAction(_('Delete')) style = self.lib.Program.get_settings()['theme'] self.change_ui_theme(style) def _custom_context_menu_requested(self, pos): self.context_menu.exec_(self.mapToGlobal(pos)) 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 connect_to_datamanager(self, data_manager): self.data_manager = data_manager self.lib = self.data_manager 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): from pmgwidgets import get_parent_path path = get_parent_path(__file__, 5) cmd = 'import sys;sys.path.append(r\'%s\')' % path self.execute_command(cmd, True, '') ini_py = os.path.join(self.lib.get_main_program_dir(), 'extensions', 'packages', 'ipython_console', 'initialisation.py') self.execute_file(ini_py, hidden=True) for source, hidden, hint_text in self.commands_pool: self.execute_command(source, hidden, hint_text) def _update_list(self): try: super(ConsoleWidget, self)._update_list() except BaseException: import traceback traceback.print_exc() def _handle_complete_reply(self, msg): """ 重写,加上trycatch,直接禁用了没有其他的变化,故不做类型标注。 :param msg: :return: """ try: super()._handle_complete_reply(msg) except BaseException: import traceback traceback.print_exc() def _banner_default(self): """ 自定义控制台开始的文字 Returns: """ return 'Welcome To PyMiner!\n' def closeEvent(self, event): if self.init_thread.isRunning(): self.console_object.stop() self.init_thread.quit() self.init_thread.wait(500) super(ConsoleWidget, 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 = ''): """ :param source: :param hidden: :param hint_text: 运行代码前显示的提示 :return: """ 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)) else: self._execute(source, hidden) def _handle_stream(self, msg): cursor: QTextCursor = self._prompt_cursor cursor.movePosition(QTextCursor.End) print(msg['content']['text']) msg_lines = msg['content']['text'].strip().split('\n') data_b64 = msg_lines[-1] try: data_b64_dic = pickle.loads(base64.b64decode(data_b64)) # got data except BaseException: pass else: msg['content']['text'] = '\n'.join(msg_lines[:-1]) data = {} for key in data_b64_dic: var = data_b64_dic[key] try: data[key] = pickle.loads( base64.b64decode(data_b64_dic[key])) except BaseException: import traceback traceback.print_exc() self.data = self.data_manager.get_all_var() data = { k: v for k, v in data.items() if k not in self.data or str(self.data[k]) != str(v) } try: self.data_manager.set_var_dict(data, 'ipython') except Exception as e: msg['content']['text'] += f'\n{type(e).__name__}: {e}' import traceback traceback.print_exc() super()._handle_stream(msg) def append_stream(self, text): """重写的方法。原本before_prompt属性是False。""" self._append_plain_text(text, before_prompt=False) def _execute(self, source: str, hidden: bool = False): if not self.is_source_code_legal(source): QMessageBox.warning(self, '警告', '命令\n\"%s\"\n为无效命令。' % source) return # if self.is_first_execution: # self.is_first_execution = False # else: # self.data = self.data_manager.get_all_var() # send data # data_b64_dic = {} # for key in self.data: # var = self.data[key] # try: # data_b64_dic[key] = base64.b64encode( # pickle.dumps(var)).decode('ascii') # except BaseException: # import traceback # traceback.print_exc() # data_b64 = base64.b64encode( # pickle.dumps(data_b64_dic)).decode('ascii') # source = f'__inject("{data_b64}")\n{source}' super()._execute(source, hidden) def is_source_code_legal(self, source_code: str) -> bool: """ 判断注入到shell的命令是否合法,不合法的话,就避免执行这个函数。 :param source_code: :return: """ s = source_code.strip() s = s.split('(')[0] # if s in self.illegal_commands: # return False return True
class TrayIconUpdates(QSystemTrayIcon): """ Tray Icon to show Updates of new versions """ # Signals closeTrayIcon = pyqtSignal() def __init__(self, parent): super(TrayIconUpdates, self).__init__(parent) icon = QIcon(":img/iconUpdate") self.setIcon(icon) self.setup_menu() self.ide_version = '0' self.download_link = '' notify = parent.ninja_settings().value( 'preferences/general/notifyUpdates', True, type=bool) if notify: self.thread = QThread() self.worker_updates = WorkerUpdates() self.worker_updates.moveToThread(self.thread) self.worker_updates.versionReceived['QString', 'QString'].connect( self._show_messages) self.thread.started.connect(self.worker_updates.check_version) self.worker_updates.finished.connect(self.__on_worker_finished) self.thread.start() def __on_worker_finished(self): self.thread.quit() self.thread.wait() def setup_menu(self, show_downloads=False): self.menu = QMenu() if show_downloads: self.download = QAction(self.tr("Download Version: {}!".format( self.ide_version)), self, triggered=self._show_download) self.menu.addAction(self.download) self.menu.addSeparator() self.quit_action = QAction(self.tr("Close Update Notifications"), self, triggered=self._close) self.menu.addAction(self.quit_action) self.setContextMenu(self.menu) def _show_messages(self, ide_version, download): if not ide_version: return self.ide_version = str(ide_version) self.download_link = str(download) try: local_version = version.LooseVersion(ninja_ide.__version__) web_version = version.LooseVersion(self.ide_version) if local_version < web_version: if self.supportsMessages(): self.setup_menu(True) self.showMessage(self.tr("NINJA-IDE Updates"), self.tr("New Version of NINJA-IDE" "\nAvailable: ") + self.ide_version + self.tr("\n\nCheck the Update Menu in " "the NINJA-IDE " "System Tray icon to Download!"), QSystemTrayIcon.Information, 10000) else: button = QMessageBox.information( self.parent(), self.tr("NINJA-IDE Updates"), self.tr("New Version of NINJA-IDE\nAvailable: ") + self.ide_version) if button == QMessageBox.Ok: self._show_download() else: logger.info("There is no new version") self._close() except Exception as reason: logger.warning('Versions can not be compared: %r', reason) self._close() def _close(self): self.closeTrayIcon.emit() def _show_download(self): webbrowser.open(self.download_link) self._close()
class CoreUI(QWidget): ''' Core Ui class ''' status_message = pyqtSignal(str) assets_path = "interface/rembot/assets/" def __init__(self, parent): super().__init__(parent) # Log class self.log = Log(self) self.log.log_data[str].connect(self.to_log) # Setup process self.bot_thread = QThread() self.linebot = LineBot() self.linebot.moveToThread(self.bot_thread) self.linebot.message[str].connect( self.linebot.log.info_log) # linebot stderr log self.linebot.log.log_data[str].connect( self.to_log) # display ability logger in ui self.linebot.end_FLAG.connect(self.bot_thread.quit) self.bot_thread.started.connect(self.linebot.run) self.bot_thread.finished.connect(self.bot_process_done) self.init_ui() def init_ui(self): ''' Rembot UI ''' self.setObjectName("CoreUI") # UI Contaier self.ui_container = QVBoxLayout(self) self.ui_container.setObjectName("ui_container") ## Header ### Box self.header_box = QHBoxLayout() self.header_box.setObjectName("header_box") self.header_box.setContentsMargins(10, 10, 10, 10) ### title font = QFont() font.setFamily("OCR A Extended") font.setPointSize(40) font.setBold(True) font.setItalic(False) font.setWeight(75) self.header_title = QLabel() self.header_title.setFont(font) self.header_title.setCursor(QCursor(Qt.ArrowCursor)) self.header_title.setLayoutDirection(Qt.LeftToRight) self.header_title.setStyleSheet("color: rgb(108, 204, 227);") self.header_title.setScaledContents(False) self.header_title.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.header_title.setContentsMargins(0, 0, 0, 0) self.header_title.setObjectName("header_title") self.header_box.addWidget(self.header_title) # add title to header ### version font = QFont() font.setFamily("Tahoma") font.setPointSize(10) font.setBold(True) font.setWeight(75) self.version_number = QLabel() self.version_number.setFont(font) self.version_number.setStyleSheet("color: rgb(85, 85, 85);") self.version_number.setAlignment(Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter) self.version_number.setObjectName("version_number") self.header_box.addWidget( self.version_number) # add version number to header # Add to UI Container self.ui_container.addLayout( self.header_box) # add header box to layout # Content self.content_box = QHBoxLayout() self.content_box.setSizeConstraint(QLayout.SetDefaultConstraint) self.content_box.setContentsMargins(10, 10, 10, 10) self.content_box.setObjectName("content_box") ## Left Box self.left_box = QVBoxLayout() self.left_box.setSizeConstraint(QLayout.SetFixedSize) self.left_box.setContentsMargins(10, 10, 10, 10) self.left_box.setObjectName("left_box") ### File box self.file_box = QHBoxLayout() self.file_box.setContentsMargins(10, 10, 10, 10) self.file_box.setObjectName("file_box") #### File label font = QFont() font.setPointSize(10) self.file_label = QLabel() self.file_label.setFont(font) size_policy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) size_policy.setHeightForWidth( self.file_label.sizePolicy().hasHeightForWidth()) self.file_label.setSizePolicy(size_policy) self.file_label.setObjectName("file_label") ### Add label to File box self.file_box.addWidget(self.file_label) #### File Input font = QFont() font.setPointSize(20) self.file_input = QLineEdit() self.file_input.setFont(font) self.file_input.setMinimumSize(QSize(0, 0)) self.file_input.setAcceptDrops(True) self.file_input.setLayoutDirection(Qt.LeftToRight) self.file_input.setText("") self.file_input.setFrame(True) self.file_input.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.file_input.setObjectName("file_input") #### Add File input to File box self.file_box.addWidget(self.file_input) ### Add File box to Left Box self.left_box.addLayout(self.file_box) #### Add Button box self.button_box = QGridLayout() self.button_box.setContentsMargins(10, 10, 10, 10) self.button_box.setObjectName("button_box") ##### Start Button self.start_button = QPushButton() self.start_button.setObjectName("start_button") ##### Stop Button self.stop_button = QPushButton() self.stop_button.setEnabled(False) self.stop_button.setObjectName("stop_button") ##### Test Button self.abort_button = QPushButton() self.abort_button.setEnabled(False) self.abort_button.setObjectName("abort_button") ##### Quit Button self.quit_button = QPushButton() self.quit_button.setObjectName("quit_button") #### Add Buttons to Button Box self.button_box.addWidget(self.start_button, 0, 0, 1, 1) self.button_box.addWidget(self.stop_button, 0, 1, 1, 1) self.button_box.addWidget(self.abort_button, 1, 0, 1, 1) self.button_box.addWidget(self.quit_button, 1, 1, 1, 1) ### Add Button box to Left box self.left_box.addLayout(self.button_box) #### Log Box and Layout self.log_box = QGroupBox() self.log_box.setFlat(True) self.log_box.setObjectName("log_box") self.log_layout = QVBoxLayout(self.log_box) self.log_layout.setContentsMargins(0, 10, 0, 0) self.log_layout.setObjectName("log_layout") ##### Log output self.log_output = QTextEdit(self.log_box) self.log_output.setMinimumSize(QSize(720, 600)) self.log_output.setReadOnly(True) self.log_output.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.log_output.setObjectName("log_output") #### Add Log Output to Log box | Log layout self.log_layout.addWidget(self.log_output) ### Add Log box to Left box self.left_box.addWidget(self.log_box) #### Left spacer self.spacer_item = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding) ### Add spacer to Log box self.left_box.addItem(self.spacer_item) ## Add Left box to Content box self.content_box.addLayout(self.left_box) ### Right Box self.right_box = QVBoxLayout() self.right_box.setSizeConstraint(QLayout.SetMinAndMaxSize) self.right_box.setContentsMargins(10, 10, 10, 10) self.right_box.setObjectName("right_box") #### Origninal image box self.original_img_box = QGroupBox() self.original_img_box.setFlat(True) self.original_img_box.setObjectName("original_img_box") self.original_img_layout = QVBoxLayout(self.original_img_box) self.original_img_layout.setContentsMargins(0, 0, 0, 0) self.original_img_layout.setObjectName("original_img_layout") ##### Original image self.original_img = QLabel(self.original_img_box) self.original_img.setMinimumSize(QSize(720, 400)) self.original_img.setText("") self.default_img = self.assets_path + "default.jpg" self.original_img.setPixmap(QPixmap(self.default_img)) # self.original_img.setScaledContents(True) self.original_img.setObjectName("original_img") #### Add Original image to Original image Layout self.original_img_layout.addWidget(self.original_img) ### Add Original image box to Right box self.right_box.addWidget(self.original_img_box) #### Output image box self.output_img_box = QGroupBox() self.output_img_box.setFlat(True) self.output_img_box.setObjectName("output_img_box") self.output_img_layout = QHBoxLayout(self.output_img_box) self.output_img_layout.setContentsMargins(0, 0, 0, 0) self.output_img_layout.setObjectName("output_img_layout") ##### Output image self.output_img = QLabel(self.output_img_box) # self.output_img.setMinimumSize(QSize(720, 400)) self.output_img.setText("") self.output_img.setPixmap(QPixmap(self.default_img)) self.output_img.setObjectName("output_img") #### Add Output img to output image layout self.output_img_layout.addWidget(self.output_img) ### Add Output image box to Right box self.right_box.addWidget(self.output_img_box) ## Add Right box to Content box self.content_box.addLayout(self.right_box) # Add Content box to UI Container self.ui_container.addLayout( self.content_box) # add content box to layout # Labelling self._translate = QCoreApplication.translate self.retranslate_ui() QMetaObject.connectSlotsByName(self) # Attach signals self.attach_events() def retranslate_ui(self): ''' UI Text ''' self.header_title.setText(self._translate("CoreUI", "REMBOT")) self.version_number.setText(self._translate("CoreUI", "")) self.file_label.setText(self._translate("CoreUI", "File name")) self.file_input.setPlaceholderText( self._translate("CoreUI", "image.ext")) self.start_button.setText(self._translate("CoreUI", "START")) self.stop_button.setText(self._translate("CoreUI", "STOP")) self.abort_button.setText(self._translate("CoreUI", "ABORT")) self.quit_button.setText(self._translate("CoreUI", "QUIT")) self.log_box.setTitle(self._translate("CoreUI", "Log")) self.original_img_box.setTitle( self._translate("CoreUI", "Original Image")) self.output_img_box.setTitle(self._translate("CoreUI", "Output Image")) # Default images self.default_img = self.assets_path + "default.jpg" self.original_img.setPixmap(QPixmap(self.default_img)) self.output_img.setPixmap(QPixmap(self.default_img)) def attach_events(self): ''' Attach signals to events ''' self.start_button.clicked.connect(self.start) self.stop_button.clicked.connect(self.stop) def update_status(self, msg): ''' Update the program statusbar string and log ''' self.status_message.emit(msg) def to_log(self, msg): ''' Output message in ui window ''' self.log_output.append(msg) # Abilities def start(self): ''' Start program ''' self.line_bot() def stop(self): ''' Stop Any running process ''' self.linebot.process_done(1) self.bot_thread.stop() self.bot_thread.wait() def bot_process_done(self): ''' Reset ui when a process is done ''' self.stop_button.setEnabled(False) # disable Stop self.abort_button.setEnabled(False) # disable abort self.start_button.setEnabled(True) # enable start # Reset images self.original_img.setPixmap(QPixmap(self.default_img)) self.original_img.setPixmap(QPixmap(self.default_img)) self.log.info_log("Process done!") # log self.update_status("Ready") def line_bot(self): ''' Start linebot program ''' # Get file from assets_path using user input file_path = self.assets_path + self.file_input.text( ) # specify filepath if os.path.exists(file_path) and file_path[-1] != '/': self.log.info_log("Loading File") # log self.original_img.setPixmap( QPixmap(file_path)) # update display image reply = QMessageBox.question(self, 'Run linebot program ?', "Contine to process ?", \ QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.stop_button.setEnabled(False) # enable stop button self.start_button.setEnabled(False) # disable start button # Run process self.linebot.imgpath = file_path self.bot_thread.start() self.update_status("Running ...") else: self.log.info_log("Aborting.") # log else: self.log.warning_log("File does not exist") # log
class ProcessDialog(QDialog): def __init__(self, port, **kwargs): super().__init__() self.setWindowTitle('Tasmotizing...') self.setFixedWidth(400) self.exception = None esptool.sw.progress.connect(self.update_progress) self.nam = QNetworkAccessManager() self.nrBinFile = QNetworkRequest() self.bin_data = b'' self.setLayout(VLayout(5, 5)) self.actions_layout = QFormLayout() self.actions_layout.setSpacing(5) self.layout().addLayout(self.actions_layout) self._actions = [] self._action_widgets = {} self.port = port self.auto_reset = kwargs.get('auto_reset', False) self.file_path = kwargs.get('file_path') if self.file_path and self.file_path.startswith('http'): self._actions.append('download') self.backup = kwargs.get('backup') if self.backup: self._actions.append('backup') self.backup_size = kwargs.get('backup_size') self.erase = kwargs.get('erase') if self.erase: self._actions.append('erase') if self.file_path: self._actions.append('write') self.create_ui() self.start_process() def create_ui(self): for action in self._actions: pb = QProgressBar() pb.setFixedHeight(35) self._action_widgets[action] = pb self.actions_layout.addRow(action.capitalize(), pb) self.btns = QDialogButtonBox(QDialogButtonBox.Abort) self.btns.rejected.connect(self.abort) self.layout().addWidget(self.btns) self.sb = QStatusBar() self.layout().addWidget(self.sb) def appendBinFile(self): self.bin_data += self.bin_reply.readAll() def saveBinFile(self): if self.bin_reply.error() == QNetworkReply.NoError: self.file_path = self.file_path.split('/')[-1] with open(self.file_path, 'wb') as f: f.write(self.bin_data) self.run_esp() else: raise NetworkError def updateBinProgress(self, recv, total): self._action_widgets['download'].setValue(recv // total * 100) def download_bin(self): self.nrBinFile.setUrl(QUrl(self.file_path)) self.bin_reply = self.nam.get(self.nrBinFile) self.bin_reply.readyRead.connect(self.appendBinFile) self.bin_reply.downloadProgress.connect(self.updateBinProgress) self.bin_reply.finished.connect(self.saveBinFile) def show_connection_state(self, state): self.sb.showMessage(state, 0) def run_esp(self): params = { 'file_path': self.file_path, 'auto_reset': self.auto_reset, 'erase': self.erase } if self.backup: backup_size = f'0x{2 ** self.backup_size}00000' params['backup_size'] = backup_size self.esp_thread = QThread() self.esp = ESPWorker(self.port, self._actions, **params) esptool.sw.connection_state.connect(self.show_connection_state) self.esp.waiting.connect(self.wait_for_user) self.esp.done.connect(self.accept) self.esp.error.connect(self.error) self.esp.moveToThread(self.esp_thread) self.esp_thread.started.connect(self.esp.run) self.esp_thread.start() def start_process(self): if 'download' in self._actions: self.download_bin() self._actions = self._actions[1:] else: self.run_esp() def update_progress(self, action, value): self._action_widgets[action].setValue(value) @pyqtSlot() def wait_for_user(self): dlg = QMessageBox.information( self, 'User action required', 'Please power cycle the device, wait a moment and press OK', QMessageBox.Ok | QMessageBox.Cancel) if dlg == QMessageBox.Ok: self.esp.continue_ok() elif dlg == QMessageBox.Cancel: self.esp.abort() self.esp.continue_ok() self.abort() def stop_thread(self): self.esp_thread.wait(2000) self.esp_thread.exit() def accept(self): self.stop_thread() self.done(QDialog.Accepted) def abort(self): self.sb.showMessage('Aborting...', 0) QApplication.processEvents() self.esp.abort() self.stop_thread() self.reject() def error(self, e): self.exception = e self.abort() def closeEvent(self, e): self.stop_thread()
class EditorGUI(QWidget): thread_invoker = pyqtSignal() def __init__(self, settings, parent = None, filename=None, params=None): super(EditorGUI, self).__init__(parent) # options #self.skip = 0 logger = logging.getLogger() self.perspective = [0,0,1000,1000] self.angle_degree = 0 self.focus = [333,666,333,666] self.croptop = 0 self.cropbottom = 1000 self.slitpos = 250 if params is not None: logger.debug("EditorGUI params {0}".format(params)) self.angle_degree = params["rotate"] if params["perspective"] is not None: self.perspective = params["perspective"] self.focus = params["focus"] self.slitpos = params["slitpos"] self.croptop, self.cropbottom = params["crop"] #self.skip = params["skip"] #no use filename = params["filename"] #private self.preview_size = 500 self.frame = 0 self.settings = settings #make the threaded loader self.thread = QThread() self.thread.start() self.lastupdatethumbs = 0 #from epoch self.asyncimageloader = AsyncImageLoader(filename=filename, size=self.preview_size) self.asyncimageloader.moveToThread(self.thread) self.thread_invoker.connect(self.asyncimageloader.task) self.thread_invoker.emit() #self.destroyed.connect(self.stop_thread) #does not work self.asyncimageloader.frameIncreased.connect(self.updateTimeLine) #close on quit #http://stackoverflow.com/questions/27420338/how-to-clear-child-window-reference-stored-in-parent-application-when-child-wind #self.setAttribute(Qt.WA_DeleteOnClose) layout = self.make_layout() self.imageselector2 = ImageSelector2() self.imageselector2.slider.startValueChanged.connect(self.frameChanged) self.imageselector2.slider.endValueChanged.connect(self.frameChanged) imageselector_layout = QHBoxLayout() imageselector_layout.addWidget(self.imageselector2) imageselector_gbox = QGroupBox(self.tr('1. Seek the first video frame')) imageselector_gbox.setLayout(imageselector_layout) glayout = QVBoxLayout() glayout.addWidget(imageselector_gbox) glayout.addLayout(layout) self.setLayout(glayout) self.setWindowTitle("Editor") self.show_snapshots() def thumbtransformer(self, cv2image): rotated,warped,cropped = self.transform.process_image(cv2image) h,w = cropped.shape[0:2] thumbh = 100 thumbw = w*thumbh//h thumb = cv2.resize(cropped,(thumbw,thumbh),interpolation = cv2.INTER_CUBIC) return self.cv2toQImage(thumb) def updateTimeLine(self, cv2thumbs): #count time and limit update now = time.time() if now - self.lastupdatethumbs < 0.2: return #transformation filter self.imageselector2.imagebar.setTransformer(self.thumbtransformer) self.imageselector2.setThumbs(cv2thumbs) self.lastupdatethumbs = time.time() def make_layout(self): # layout layout = QHBoxLayout() #second left panel for image rotation rotation_layout = QHBoxLayout() self.btn = QPushButton(self.tr("-90")) self.btn.clicked.connect(self.angle_sub90) rotation_layout.addWidget(self.btn) self.btn = QPushButton(self.tr("-1")) self.btn.clicked.connect(self.angle_dec) rotation_layout.addWidget(self.btn) rotation_layout.addWidget(QLabel(self.tr('rotation'))) self.angle_label = QLabel("0 "+self.tr("degrees")) rotation_layout.addWidget(self.angle_label) self.btn = QPushButton(self.tr("+1")) self.btn.clicked.connect(self.angle_inc) rotation_layout.addWidget(self.btn) self.btn = QPushButton(self.tr("+90")) self.btn.clicked.connect(self.angle_add90) rotation_layout.addWidget(self.btn) # crop_layout = QVBoxLayout() self.crop_slider = rs.QRangeSlider(splitterWidth=10, vertical=True) # スライダの向き self.crop_slider.setFixedWidth(15) self.crop_slider.setStyleSheet(cropCSS) self.crop_slider.setDrawValues(False) self.crop_slider.startValueChanged.connect(self.croptop_slider_on_draw) self.crop_slider.endValueChanged.connect(self.cropbottom_slider_on_draw) self.crop_slider.setMinimumHeight(500) crop_layout.addWidget(self.crop_slider) self.sliderL = rs.QRangeSlider(splitterWidth=10, vertical=True) # スライダの向き self.sliderL.setFixedWidth(15) self.sliderL.setStyleSheet(perspectiveCSS) self.sliderL.setDrawValues(False) self.sliderL.startValueChanged.connect(self.sliderTL_on_draw) self.sliderL.endValueChanged.connect(self.sliderBL_on_draw) self.sliderL.setMinimumHeight(500) self.sliderR = rs.QRangeSlider(splitterWidth=10, vertical=True) # スライダの向き self.sliderR.setFixedWidth(15) self.sliderR.setStyleSheet(perspectiveCSS) self.sliderR.setDrawValues(False) self.sliderR.startValueChanged.connect(self.sliderTR_on_draw) self.sliderR.endValueChanged.connect(self.sliderBR_on_draw) self.sliderR.setMinimumHeight(500) raw_image_layout = QVBoxLayout() self.raw_image_pane = DrawableLabel() self.raw_image_pane.setAlignment(Qt.AlignCenter) self.raw_image_pane.setFixedWidth(self.preview_size) self.raw_image_pane.setFixedHeight(self.preview_size) #raw_image_layout.setAlignment(self.raw_image_pane, Qt.AlignCenter) raw_image_layout.addWidget(self.raw_image_pane) raw_image_layout.setAlignment(self.raw_image_pane, Qt.AlignHCenter) raw_image_layout.setAlignment(self.raw_image_pane, Qt.AlignTop) processed_edit_gbox_layout = QVBoxLayout() processed_edit_gbox = QGroupBox(self.tr('3. Motion Detection and Slit')) box = QVBoxLayout() processed_image_layout = QVBoxLayout() self.processed_pane = MyLabel(func=self.show_snapshots) self.processed_pane.setAlignment(Qt.AlignCenter) self.processed_pane.setFixedWidth(self.preview_size) self.processed_pane.setFixedHeight(self.preview_size) processed_image_layout.addWidget(self.processed_pane) processed_image_layout.setAlignment(self.processed_pane, Qt.AlignTop) hbox = QHBoxLayout() hbox.addLayout(processed_image_layout) hbox.addLayout(crop_layout) box.addLayout(hbox) processed_edit_gbox.setLayout(box) processed_edit_gbox_layout.addWidget(processed_edit_gbox) slit_slider_label = QLabel(self.tr('Slit position')) self.slit_slider = QSlider(Qt.Horizontal) # スライダの向き self.slit_slider.setRange(-500, 500) # スライダの範囲 #スライダの目盛りを両方に出す self.slit_slider.setTickPosition(QSlider.TicksBelow) self.slit_slider.valueChanged.connect(self.slit_slider_on_draw) slit_slider_layout = QHBoxLayout() slit_slider_layout.addWidget(slit_slider_label) slit_slider_layout.addWidget(self.slit_slider) box.addLayout(slit_slider_layout) box.setAlignment(slit_slider_layout, Qt.AlignTop) #combine panels topleft_layout = QHBoxLayout() topleft_layout.addWidget(self.sliderL) topleft_layout.addLayout(raw_image_layout) topleft_layout.addWidget(self.sliderR) left_layout = QVBoxLayout() left_layout.addLayout(topleft_layout) left_layout.addLayout(rotation_layout) left_layout.setAlignment(rotation_layout, Qt.AlignTop) raw_edit_gbox = QGroupBox(self.tr('2. Repair deformation')) raw_edit_gbox.setLayout(left_layout) raw_edit_gbox_layout = QVBoxLayout() raw_edit_gbox_layout.addWidget(raw_edit_gbox) layout.addLayout(raw_edit_gbox_layout) layout.addLayout(processed_edit_gbox_layout) return layout def stop_thread(self): self.asyncimageloader.stop() self.thread.quit() self.thread.wait() def angle_inc(self): self.angle_degree += 1 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def angle_dec(self): self.angle_degree -= 1 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def angle_add90(self): self.angle_degree += 90 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def angle_sub90(self): self.angle_degree -= 90 self.angle_degree %= 360 self.angle_label.setText("{0} ".format(self.angle_degree)+self.tr("degrees")) self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def frameChanged(self, value): self.frame = value self.show_snapshots() def sliderTL_on_draw(self): self.perspective[0] = self.sliderL.start() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def sliderBL_on_draw(self): self.perspective[2] = self.sliderL.end() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def sliderTR_on_draw(self): self.perspective[1] = self.sliderR.start() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def sliderBR_on_draw(self): self.perspective[3] = self.sliderR.end() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def cv2toQImage(self,cv2image): height,width = cv2image.shape[:2] return QImage(cv2image[:,:,::-1].copy().data, width, height, width*3, QImage.Format_RGB888) def show_snapshots(self, region=None): """ put the snapshots in the preview panes """ if self.frame < 0: return logger = logging.getLogger() image = self.asyncimageloader.snapshots[self.frame] self.transform = trainscanner.transformation(self.angle_degree, self.perspective, [self.croptop, self.cropbottom]) rotated, warped, cropped = self.transform.process_first_image(image) self.put_cv2_image(rotated, self.raw_image_pane) if region is not None: logger.debug("show_snapshot region {0}".format(region)) #assume the QLabel size is square preview_size x preview_size top, left, bottom, right = region.top(), region.left(), region.bottom(), region.right() if top < 0: top = 0 if left < 0: left = 0 if right > self.preview_size: right = self.preview_size if bottom > self.preview_size: bottom = self.preview_size #and also assume that the cropped image is centered and sometimes shrinked. top -= self.preview_size//2 bottom -= self.preview_size//2 left -= self.preview_size//2 right -= self.preview_size//2 #expected image size in the window height, width = cropped.shape[0:2] if height > width: if height > self.preview_size: width = width * self.preview_size // height height = self.preview_size else: if width > self.preview_size: height = height * self.preview_size // width width = self.preview_size #indicate the region size relative to the image size top = top * 1000 // height + 500 bottom = bottom * 1000 // height + 500 left = left * 1000 // width + 500 right = right * 1000 // width + 500 if top < 0: top = 0 if top > 1000: top = 1000 if bottom < 0: bottom = 0 if bottom > 1000: bottom = 1000 if left < 0: left = 0 if left > 1000: left = 1000 if right < 0: right = 0 if right > 1000: right = 1000 self.focus = left,right,top,bottom self.put_cv2_image(cropped, self.processed_pane) def put_cv2_image(self, image, widget): height, width = image.shape[0:2] qImg = self.cv2toQImage(image) pixmap = QPixmap(qImg) if height > width: if height > self.preview_size: pixmap = pixmap.scaledToHeight(self.preview_size) else: if width > self.preview_size: pixmap = pixmap.scaledToWidth(self.preview_size) widget.setPixmap(pixmap) #give hints to DrawableLabel() and MyLabel() widget.perspective = self.perspective widget.focus = self.focus widget.slitpos = self.slitpos w = pixmap.width() h = pixmap.height() x = ( self.preview_size - w ) // 2 y = ( self.preview_size - h ) // 2 widget.geometry = x,y,w,h def slit_slider_on_draw(self): self.slitpos = self.slit_slider.value() self.show_snapshots() def croptop_slider_on_draw(self): self.croptop = self.crop_slider.start() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def cropbottom_slider_on_draw(self): self.cropbottom = self.crop_slider.end() self.updateTimeLine(self.asyncimageloader.snapshots) self.show_snapshots() def closeEvent(self,event): self.settings.reset_input() self.stop_thread()
class Client(QObject): sgnOutput = pyqtSignal(str) def __init__(self, parent=None, *args, **kwargs): QObject.__init__(self, parent) self.setFormData(*args, **kwargs) self._thread = None self._worker = None def setFormData(self, form): self.form = form self.outputField = form.outputTxtInput @pyqtSlot() def onOutputMessage(self, info): self.outputField.append(str(info)) def toggle(self, enable, *args, **kwargs): if enable: if not self._thread: self._thread = QThread() formOptions = main.MainDialog.getFormValues(self.form) self.form.outputTxtInput.clear() if formOptions['valid'] == False: errorMsg = 'Errors:\n\n' if formOptions['formStatus'][ 'formSearchCriteriaValid'] == False: errorMsg += 'Fill at least one search criteria.\n' if formOptions['formStatus'][ 'formExportOptionsValid'] == False: errorMsg += 'Use a valid export filename and select at least one export option.\n' if formOptions['formStatus']['formProxyOptionsValid'] == False: errorMsg += 'Use a valid Proxy URL - Unable to connect.\n' self.form.outputTxtInput.append(errorMsg) return self.form.formOptions = formOptions['formData'] self.form.searchBtn.setEnabled(False) self.form.cancelBtn.setEnabled(True) self._worker = Worker(None, self.form) self.sgnOutput.connect(self._worker.on_message_output) self._worker.moveToThread(self._thread) self._worker.sgnOutput.connect(partial(self.onOutputMessage)) self._worker.sgnFinished.connect(partial(self.on_worker_done)) self._thread.started.connect(self._worker.run) self._thread.start() else: self._worker.sgnOutput.emit('Cancelling search, please wait ...') self._worker.stop() self.on_worker_done(True) @pyqtSlot() def on_worker_done(self, userInterruption=False): self.form.searchBtn.setEnabled(True) self.form.cancelBtn.setEnabled(False) self._thread.quit() self._thread.wait()
class mainWindow(QMainWindow, Ui_MainWindow): """ The main class for the GUI window """ def __init__(self): """ The constructor and initiator. :return: """ # initial setup super(mainWindow, self).__init__() self.setupUi(self) self.thread = QThread() self.connected = False # Connect signals self.connect_signals() def on_push_button_clicked(self): if self.pushButton.isChecked(): try: host = str(ip_address(self.lineEdit_host.text())) port = self.lineEdit_port.text() if not port.isdigit(): raise ValueError except(ValueError): self.pushButton.setChecked(False) self.show_message('Please enter valid numeric IP address and port number.') return self.zeromq_listener_10001 = ZMQListener(host, port, '10001') self.zeromq_listener_10001.moveToThread(self.thread) self.zeromq_listener_10001.message.connect(self.signal_received_10001) self.zeromq_listener_10001.err_msg.connect(self.show_message) self.thread.started.connect(self.zeromq_listener_10001.loop) self.thread.start() self.pushButton.setText('Stop') self.show_message('Connected to server: {}:{}'.format(host, port)) else: self.zeromq_listener_10001.running = False self.thread.terminate() self.pushButton.setText('Start') self.show_message('Disconnected.') def connect_signals(self): """ Connects signals. :return: """ # Action about and Action quit will be shown differently in OSX self.actionAbout.triggered.connect(self.show_about_dialog) self.actionQuit.triggered.connect(self.shutdown) self.pushButton.clicked.connect(self.on_push_button_clicked) def shutdown(self): self.zeromq_listener_10001.running = False self.thread.terminate() self.thread.quit() self.thread.wait() QCoreApplication.instance().quit() def signal_received_10001(self, message): # get the message and split it topic, time, stat_bits, value_str = message.split() current_range = int(stat_bits[-3:], 2) range_str = RANGE_DIC[current_range] self.label_time_stamp.setText(time) self.label_status.setText(stat_bits) self.label_range.setText(range_str) # do the calibration value_float = float(value_str) * CAL_SLOPE + CAL_ITCPT # convert binary to float value value = value_float * RAIL_VOLTAGE / ADC_QUANTIZATION # set to 2 decimal points value = int(value * 100) / 100 # in case more digits are needed # self.lcdNumber.setDigitCount(8) if self.zeromq_listener_10001.running: self.lcdNumber.display(value) else: self.lcdNumber.display(0) def closeEvent(self, event): self.zeromq_listener_10001.running = False self.thread.terminate() self.thread.quit() self.thread.wait() def show_message(self, message): """ Implementation of an abstract method: Show text in status bar :param message: :return: """ self.statusbar.showMessage(message) def show_about_dialog(self): """ Show about dialog :return: """ about_dialog = QDialog() about_dialog.ui = Ui_AbooutDialog() about_dialog.ui.setupUi(about_dialog) about_dialog.ui.labelVersion.setText('Version: {}'.format(__version__)) about_dialog.exec_() about_dialog.show()
class VideoThread(QThread): changePixmap = pyqtSignal(QImage) newFrame = pyqtSignal(int, numpy.ndarray) stateChanged = pyqtSignal( bool) # True for playing started. False for playing stopped positionChanged = pyqtSignal(int) def __init__(self, parent=None, video=None): super().__init__(parent) self.__kill = False self.mutex = QMutex() self.__exporter = None self.video = video self.fps = self.video.get(cv2.CAP_PROP_FPS) self.resolution = self.video.get( cv2.CAP_PROP_FRAME_WIDTH), self.video.get( cv2.CAP_PROP_FRAME_HEIGHT) self.output_resolution = self.resolution # play state self.number_of_frames = int(self.video.get(cv2.CAP_PROP_FRAME_COUNT)) self.current_frame = 0 self.__is_playing = False self.__is_exporting = False self.__exportWorker = None self.__exportThread = None self.__frame = None if video is None: raise Exception("Must provide a video") @property def playing(self): return self.__is_playing @property def frame(self): return self.__frame @property def duration(self): return self.number_of_frames / self.fps def run(self): ret, frame = self.step() self.render_frame() while not self.__kill: while not self.__kill and ret and self.playing and not self.__is_exporting: self.render_frame() # Wait and get next frame time.sleep(1 / self.fps) # TODO: account for processing time if (self.current_frame >= self.number_of_frames - 1): self.pause() else: ret, frame = self.step() # while not self.__kill and ret and self.__is_exporting: # print("Exporting Frame", self.current_frame, "of", self.number_of_frames-1) # if (self.current_frame >= self.number_of_frames-1): # self.__is_exporting = False # self.__finishExport() # print("Export done no ret failure :)") # else: # ret, frame = self.step() # self.__export_progress_bar.setValue(self.current_frame) # if not ret: # print("No return during export at frame {} / {}".format(self.current_frame-1, self.number_of_frames-1)) # ret = True # self.__is_exporting = False # self.__finishExport() # print("Export done") # # break while not self.__kill and not self.playing and not self.__is_exporting: time.sleep(1 / self.fps) # do nothing print("Video Thread Closing") def export(self, path, progressbar): if self.__is_exporting or self.__exportWorker is not None or self.__exportThread is not None: raise Exception("Must wait until previous export is finished") self.__is_exporting = True self.__exportWorker = Exporter(progressbar, self, path) self.__exportThread = QThread() self.__exportWorker.moveToThread(self.__exportThread) self.__exportWorker.start() self.__exportWorker.exportComplete.connect(self.__export_end) # self.__exportWorker.start() def __export_end(self): self.__exportWorker.kill() self.__exportThread.terminate() self.__exportThread.wait() self.__is_exporting = False self.__exportWorker = None self.__exportThread = None # def __finishExport(self): # print("Render loop closed") # # Close writer and remove listeners # self.__exporter.release() # self.__exporter = None # self.__is_exporting = False # self.newFrame.disconnect(self.__exportFrame) # print("Writer Closed") # self.set_frame(self.__export_start_position) # # remove progress bar # self.__export_progress_bar.setValue(self.number_of_frames) # self.__export_progress_bar.parent().layout().removeWidget(self.__export_progress_bar) # self.__export_progress_bar.setParent(None) # self.__export_progress_bar.deleteLater() # def export(self, path, progressbar): # print("Exporting to", path) # # Get export information and video writer # self.__export_start_position = self.current_frame # resolution = tuple(map(int, self.resolution)) # self.__exporter = cv2.VideoWriter( # path, # cv2.VideoWriter_fourcc(*"X264"), # self.fps, # resolution) # # Move video to beginning and listen for frames to export # self.pause() # self.newFrame.connect(self.__exportFrame) # self.set_frame(0) # self.positionChanged.emit(self.current_frame) # # Create progress bar # self.__export_progress_bar = progressbar # self.__export_progress_bar.setMaximum(self.number_of_frames) # self.__export_progress_bar.setValue(0) # # Read first frame # self.step() # self.__is_exporting = True # causes thread to start exporting # def __exportFrame(self, index, frame): # if (self.__exporter != None): # self.mutex.lock() # self.__exporter.write(frame) # self.mutex.unlock() def play(self): if self.playing: pass else: print("Thread playing") if self.current_frame >= self.number_of_frames - 1: self.set_frame(0) self.__is_playing = True self.stateChanged.emit(self.playing) def pause(self): if not self.playing: pass else: print("Thread Pausing") self.__is_playing = False self.stateChanged.emit(self.playing) def step(self): self.mutex.lock() ret, self.__frame = self.video.read() self.mutex.unlock() self.current_frame += 1 if ret: self.mutex.lock() self.newFrame.emit(self.current_frame, self.__frame) self.mutex.unlock() return (ret, self.__frame) def render_frame(self): self.positionChanged.emit(self.current_frame) rgb = cv2.cvtColor(self.__frame, cv2.COLOR_BGR2RGB) # Convert into QT Format h, w, ch = rgb.shape bytesPerLine = ch * w qtImage = QImage(rgb, w, h, bytesPerLine, QImage.Format_RGB888) scaled = qtImage.scaled(self.output_resolution[0], self.output_resolution[1], Qt.KeepAspectRatio) self.changePixmap.emit(scaled) # emit event def set_frame(self, frame_index): if (0 <= frame_index < self.number_of_frames): self.mutex.lock() self.current_frame = frame_index self.video.set(cv2.CAP_PROP_POS_FRAMES, frame_index) ret, self.__frame = self.video.read() self.mutex.unlock() self.newFrame.emit(self.current_frame, self.__frame) self.render_frame() else: raise Exception( "index {} is out of the video bounds 0 -> {}".format( frame_index, self.number_of_frames)) def reblit(self): self.set_frame(self.current_frame) def rerender(self): self.render_frame() def updateSize(self, x, y): aspect_ratio = self.resolution[0] / self.resolution[1] x = x y = x / aspect_ratio self.output_resolution = [x, y] print( "Request to update resolution of {} video ({} aspect ratio) to {} ({} aspect ratio).\n\tActually set to {}" .format(self.resolution, aspect_ratio, (x, y), x / y, self.output_resolution)) def kill(self): self.__kill = True if self.__exportWorker is not None: self.__exportWorker.kill() if self.__exportThread is not None: self.__exportThread.terminate() self.__exportThread.wait() self.terminate() self.wait()
class Debugger(QObject): """ Represents the networked debugger client. """ ETX = b'\x03' # End transmission token. def __init__(self, host, port, proc=None): """ Instantiate given a host, port and process for the debug runner. """ self.host = host self.port = port self.proc = proc self.view = None # Set after instantiation. super().__init__() def start(self): """ Start the debugger session. """ self.listener_thread = QThread(self.view.view) self.command_handler = CommandBufferHandler(self) self.command_handler.moveToThread(self.listener_thread) self.command_handler.on_command.connect(self.on_command) self.command_handler.on_fail.connect(self.on_fail) self.listener_thread.started.connect(self.command_handler.worker) self.listener_thread.start() def on_command(self, command): """ Handle a command emitted by the client thread. """ event, data = json.loads(command) if hasattr(self, 'on_{}'.format(event)): getattr(self, 'on_{}'.format(event))(**data) def on_fail(self, message): """ Handle if there's a connection failure with the debug runner. """ logger.error(message) self.view.debug_on_fail(message) def stop(self): """ Shut down the debugger session. """ self.command_handler.stopped = True self.listener_thread.quit() self.listener_thread.wait() if self.proc is not None: self.output('quit') self.socket.shutdown(socket.SHUT_WR) if self.proc is not None: # Wait for the runner process to die. self.proc.wait() def output(self, event, **data): """ Send a command to the debug runner. """ try: dumped = json.dumps((event, data)).encode('utf-8') self.socket.sendall(dumped + Debugger.ETX) except OSError as e: logger.debug('Debugger client error.') logger.debug(e) except AttributeError as e: logger.debug('Debugger client not connected to runner.') logger.debug(e) def breakpoint(self, breakpoint): """ Given a breakpoint number or (filename, line), return an object representing the referenced breakpoint. """ try: if isinstance(breakpoint, tuple): filename, line = breakpoint return self.bp_index[filename][line] else: return self.bp_list[breakpoint] except KeyError: raise UnknownBreakpoint() def breakpoints(self, filename): """ Return all the breakpoints associated with the referenced file. """ return self.bp_index.get(filename, {}) # Commands that can be passed to the debug runner. def create_breakpoint(self, filename, line, temporary=False): """ Create a new, enabled breakpoint at the specified line of the given file. """ self.output('break', filename=filename, line=line, temporary=temporary) def enable_breakpoint(self, breakpoint): """ Enable an existing breakpoint. """ self.output('enable', bpnum=breakpoint.bpnum) def disable_breakpoint(self, breakpoint): """ Disable an existing breakpoint. """ self.output('disable', bpnum=breakpoint.bpnum) def ignore_breakpoint(self, breakpoint, count): """ Ignore an existing breakpoint for "count" iterations. (N.B. Use a count of 0 to restore the breakpoint. """ self.output('ignore', bpnum=breakpoint.bpnum, count=count) def clear_breakpoint(self, breakpoint): """ Clear an existing breakpoint. """ self.output('clear', bpnum=breakpoint.bpnum) def do_run(self): """ Run the debugger until the next breakpoint. """ self.output('continue') def do_step(self): """ Step through one stack frame. """ self.output('step') def do_next(self): """ Go to the next line in the current stack frame. """ self.output('next') def do_return(self): """ Return to the previous stack frame. """ self.output('return') # Handlers for events raised by the debug runner. These generally follow # the pattern of updating state in the client object to reflect that of # the debug runner, then calling a method in the UI layer to update the # GUI to reflect the changed state. def on_bootstrap(self, breakpoints): """ The runner has finished setting up. """ self.bp_index = {} self.bp_list = list([True, ]) # Breakpoints count from 1 for bp_data in breakpoints: self.on_breakpoint_create(**bp_data) self.view.debug_on_bootstrap() def on_breakpoint_create(self, **bp_data): """ The runner has created a breakpoint. """ bp = Breakpoint(**bp_data) self.bp_index.setdefault(bp.filename, {}).setdefault(bp.line, bp) self.bp_list.append(bp) if bp.enabled: self.view.debug_on_breakpoint_enable(bp) else: self.view.debug_on_breakpoint_disable(bp) def on_breakpoint_enable(self, bpnum): """ The runner has enabled the breakpoint referenced by breakpoint number. """ bp = self.bp_list[bpnum] bp.enabled = True self.view.debug_on_breakpoint_enable(bp) def on_breakpoint_disable(self, bpnum): """ The runner has disabled a breakpoint referenced by breakpoint number. """ bp = self.bp_list[bpnum] bp.enabled = False self.view.debug_on_breakpoint_disable(bp) def on_breakpoint_ignore(self, bpnum, count): """ The runner will ignore the referenced breakpoint "count" iterations. """ bp = self.bp_list[bpnum] bp.ignore = count self.view.debug_on_breakpoint_ignore(bp, count) def on_breakpoint_clear(self, bpnum): """ The runner has cleared the referenced breakpoint. """ bp = self.bp_list[bpnum] self.view.debug_on_breakpoint_clear(bp) def on_stack(self, stack): """ The runner has sent an update to the stack. """ self.stack = stack self.view.debug_on_stack(stack) def on_restart(self): """ The runner has restarted. """ self.view.debug_on_restart() def on_finished(self): """ The debug runner has finished running the script to be debugged. """ self.view.debug_on_finished() def on_call(self, args): """ The runner has called a function with the specified arguments. """ self.view.debug_on_call(args) def on_return(self, retval): """ The runner has returned from a function with the specified return value. """ self.view.debug_on_return(retval) def on_line(self, filename, line): """ The runner has moved to the specified line in the referenced file. """ self.view.debug_on_line(filename, line) def on_exception(self, name, value): """ The runner has encountered a named exception with an associated value. """ self.view.debug_on_exception(name, value) def on_postmortem(self, *args, **kwargs): """ The runner encountered a fatal error and has died. """ self.view.debug_on_postmortem(args, kwargs) def on_info(self, message): """ The runner has sent an informative message. """ logger.info('Debug runner says: {}'.format(message)) self.view.debug_on_info(message) def on_warning(self, message): """ The runner has sent a warning message. """ logger.warning('Debug runner says: {}'.format(message)) self.view.debug_on_warning(message) def on_error(self, message): """ The runner has sent an error message. """ logger.error('Debug runner says: {}'.format(message)) self.view.debug_on_error(message)
class EasyTranslator(QMainWindow, Ui_MainWindow): launch = pyqtSignal(int, str) def __init__(self): super().__init__() self.setupUi(self) self.textEdit_bai.setFontPointSize(12) self.textEdit_bing.setFontPointSize(12) self.textEdit_goo.setFontPointSize(12) self.textEdit_jin.setFontPointSize(12) self.textEdit_you.setFontPointSize(12) self.textEdit_zhi.setFontPointSize(12) self.setWindowTitle('EasyTranslator') self.setWindowIcon(QIcon(resource_path('icon.png'))) self.label.setPixmap(QPixmap(resource_path('google.png'))) self.label_2.setPixmap(QPixmap(resource_path('baidu.png'))) self.label_3.setPixmap(QPixmap(resource_path('bing.png'))) self.label_4.setPixmap(QPixmap(resource_path('powerword.png'))) self.label_5.setPixmap(QPixmap(resource_path('youdao.png'))) self.label_6.setPixmap(QPixmap(resource_path('cnki.png'))) self.checkBox_bai.setCheckState(2) self.checkBox_goo.setCheckState(2) self.checkBox_bing.setCheckState(2) self.checkBox_you.setCheckState(2) self.checkBox_zhi.setCheckState(2) self.checkBox_jin.setCheckState(2) self.aboutAct = QAction('Version', self) self.aboutMenu = self.menuBar().addMenu("About") self.aboutMenu.addAction(self.aboutAct) self.loadStyleSheet(resource_path('dark.qss')) self.resize(860, 650) self.center() self.loader = Loader() self.thread = QThread() self.loader.moveToThread(self.thread) self.textEdit_objs = [ self.textEdit_goo, self.textEdit_bai, self.textEdit_bing, self.textEdit_jin, self.textEdit_you, self.textEdit_zhi ] self.thread.finished.connect(self.loader.deleteLater) self.button_trans.clicked.connect(self.on_button_trans) self.launch.connect(self.loader.load_threads) self.loader.done.connect(self.show_result) self.button_clear.clicked.connect(self.on_button_clear) self.button_goo.clicked.connect(self.on_button_goo) self.button_bai.clicked.connect(self.on_button_bai) self.button_bing.clicked.connect(self.on_button_bing) self.button_jin.clicked.connect(self.on_button_jin) self.button_you.clicked.connect(self.on_button_you) self.button_zhi.clicked.connect(self.on_button_zhi) self.aboutAct.triggered.connect(self.on_aboutAction) self.thread.start() def closeEvent(self, event): self.thread.quit() self.thread.wait() event.accept() # let the window close def on_button_goo(self): QApplication.clipboard().setText(self.textEdit_goo.toPlainText()) def on_button_bai(self): QApplication.clipboard().setText(self.textEdit_bai.toPlainText()) def on_button_bing(self): QApplication.clipboard().setText(self.textEdit_bing.toPlainText()) def on_button_jin(self): QApplication.clipboard().setText(self.textEdit_jin.toPlainText()) def on_button_you(self): QApplication.clipboard().setText(self.textEdit_you.toPlainText()) def on_button_zhi(self): QApplication.clipboard().setText(self.textEdit_zhi.toPlainText()) def on_button_clear(self): self.textEdit_in.clear() def show_result(self): # for i, work in enumerate(self.loader.works): if self.textEdit_in.toPlainText() != '': for obj, work in zip(self.textEdit_objs, self.loader.works): obj.setText(str(work.result)) self.enable_ui() def on_button_trans(self): self.disable_ui() self.launch.emit(self.comboBox.currentIndex(), self.textEdit_in.toPlainText()) def disable_ui(self): self.button_trans.setEnabled(False) self.button_clear.setEnabled(False) self.comboBox.setEnabled(False) self.button_goo.setEnabled(False) self.button_bai.setEnabled(False) self.button_bing.setEnabled(False) self.button_jin.setEnabled(False) self.button_you.setEnabled(False) self.button_zhi.setEnabled(False) def loadStyleSheet(self, file): with open(file, 'r', encoding='utf-8') as f: s = f.readlines() s = ''.join(s).strip('\n') self.setStyleSheet(s) def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.setGeometry(qr) def enable_ui(self): self.button_trans.setEnabled(True) self.button_clear.setEnabled(True) self.comboBox.setEnabled(True) self.button_goo.setEnabled(True) self.button_bai.setEnabled(True) self.button_bing.setEnabled(True) self.button_jin.setEnabled(True) self.button_you.setEnabled(True) self.button_zhi.setEnabled(True) def keyPressEvent(self, event): if QApplication.keyboardModifiers( ) == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier and event.key( ) == QtCore.Qt.Key_Q: self.on_button_trans() event.accept() def on_checkBox_goo_stateChanged(self, state): if state == 0: self.gridLayout_10.removeWidget(self.widget_goo) self.widget_goo.hide() else: self.gridLayout_10.addWidget(self.widget_goo) self.widget_goo.show() def on_checkBox_bai_stateChanged(self, state): if state == 0: self.gridLayout_10.removeWidget(self.widget_bai) self.widget_bai.hide() else: self.gridLayout_10.addWidget(self.widget_bai) self.widget_bai.show() def on_checkBox_bing_stateChanged(self, state): if state == 0: self.gridLayout_10.removeWidget(self.widget_bing) self.widget_bing.hide() else: self.gridLayout_10.addWidget(self.widget_bing) self.widget_bing.show() def on_checkBox_jin_stateChanged(self, state): if state == 0: self.gridLayout_10.removeWidget(self.widget_jin) self.widget_jin.hide() else: self.gridLayout_10.addWidget(self.widget_jin) self.widget_jin.show() def on_checkBox_you_stateChanged(self, state): if state == 0: self.gridLayout_10.removeWidget(self.widget_you) self.widget_you.hide() else: self.gridLayout_10.addWidget(self.widget_you) self.widget_you.show() def on_checkBox_zhi_stateChanged(self, state): if state == 0: self.gridLayout_10.removeWidget(self.widget_zhi) self.widget_zhi.hide() else: self.gridLayout_10.addWidget(self.widget_zhi) self.widget_zhi.show() def on_aboutAction(self): QMessageBox.information( self, 'About', 'EasyTranslator version 2.0\nDeveloped by Daibingh')
class MainWindow(QMainWindow): """ Main window class. Includes the main window itself as well as handling of it's signals """ upload_pictures = pyqtSignal(list) def __init__(self, **kwargs): super().__init__(**kwargs) load_ui('MainWindow.ui', self) self.setWindowTitle('Picup - {}'.format(__version__)) apikey = get_api_key() if not apikey: apikey = self.request_api_key() self.legal_resize = True self.upload_in_progress = False self.upload_thread = QThread(parent=self) self.upload = Upload(apikey=apikey) self.upload_thread.start() self.upload.moveToThread(self.upload_thread) self.list_view_files_model = FileListModel(parent=self) self.list_view_files.setModel(self.list_view_files_model) self.pushButton_close.clicked.connect(self.shutdown) self.pushButton_add_picture.clicked.connect(self.add_file) self.pushButton_add_links.clicked.connect(self.add_url) self.pushButton_upload.clicked.connect(self.start_upload) self.pushButton_clear_list.clicked.connect( self.list_view_files_model.clear_list) self.pushButton_remove_selected.clicked.connect(self.remove_selected) self.upload.upload_finished.connect(self.upload_finished) self.upload.upload_error.connect(self.handle_error) self.upload_pictures.connect(self.upload.upload_multiple) self.dialog = QFileDialog(parent=self) self.dialog.setFileMode(QFileDialog.ExistingFiles) self.dialog.setNameFilters(SUPPORTED_FILE_TYPES) self.resize_container.hide() self.resize_container_percentual.hide() self.check_box_resize.clicked.connect( self.set_resize_box_visibility ) self.radio_button_absolute.toggled.connect( self.set_absolute_resize_box_visibility ) self.radio_button_percentual.toggled.connect( self.set_percentual_resize_box_visibility ) self.spin_box_width.valueChanged.connect(self.update_resize) self.spin_box_higth.valueChanged.connect(self.update_resize) self.spin_box_percentual.valueChanged.connect(self.update_resize) self.comboBox_rotate_options.activated['QString'].connect( self.upload.change_default_rotation ) self.checkBox_delete_exif.toggled.connect( self.upload.change_default_exif ) self.comboBox_rotate_options.addItems(ALLOWED_ROTATION) def request_api_key(self,): """ requests and stores an api key from the user, if non is stores yet. If none is given a default one is used. """ window = KeyRequest(parent=self) if window.exec_(): apikey = window.lineEdit_apikey.text() if apikey: set_api_key(apikey) return apikey return DEFAULT_API_KEY sys.exit(0) @pyqtSlot() def add_file(self): """ add file(s) to the upload list. using Qts file dialog. """ if self.dialog.exec_(): files = self.dialog.selectedFiles() files = [(file_, 'file') for file_ in files] self.list_view_files_model.add_files(files) @pyqtSlot() def add_url(self,): """ add url(s) to the upload list. using a text box. """ url_input = UrlInput() code = url_input.exec_() urls = url_input.text() new_entrys = [] not_added = [] if code and urls != '': for url in urls.split('\n'): # skip empty lines if url == '': continue parsed_url = urlparse(url, scheme='http') scheme = parsed_url.scheme.lower() if scheme in ['http', 'https', 'ftp']: new_entrys.append((urlunparse(parsed_url), 'url')) else: not_added.append(url) if not_added: message = QMessageBox(QMessageBox.Warning, 'Fehler', ('Ein oder mehrere link(s) konnten ' 'nicht hinzugefügt werden.'), buttons=QMessageBox.Ok, parent=self) message.setDetailedText('\n'.join(not_added)) self.list_view_files_model.add_files(new_entrys) @pyqtSlot() def start_upload(self,): """ starts the upload and does some setup for the status/result dialog. As well as some cleanup afterwards. It locks the application for any further uploads until this one is \ finished. """ if (len(self.list_view_files_model.files) and not self.upload_in_progress and self.legal_resize): self.upload_in_progress = True files = self.list_view_files_model.files.copy() link_dialog = ShowLinks(self.upload, len(files), parent=self) link_dialog.readd_pictures.connect( self.list_view_files_model.add_files ) link_dialog.show() LOGGER.debug('emitting upload signal with arguments: %s', files) self.upload_pictures.emit(files) LOGGER.debug('cleanup main window') self.list_view_files_model.clear_list() elif self.upload_in_progress: LOGGER.debug('Upload already in progress.') QMessageBox.warning(self, 'Upload Läuft', 'Es läuft bereits ein Upload Prozess.') elif not self.legal_resize: LOGGER.debug('illegal resize string will not upload.') # pylint: disable=line-too-long # would harm readability QMessageBox.warning(self, 'Auflösung ungültig', ('Die für die Skalierung angegebene Auflösung ist ungültig. ' 'Bitte gib diese im folgendem format an: breite x höhe') ) else: LOGGER.info('There is nothing to upload.') QMessageBox.information(self, 'Nüx da', ('Es wurden keine bilder zum hochladen ' 'hinzugefügt')) @pyqtSlot() def upload_finished(self,): """ called through a signal after upload is finished to release the lock. """ self.upload_in_progress = False @pyqtSlot(type, tuple) def handle_error(self, exception_type, args): """ displays informations about an exception. """ message = QMessageBox(QMessageBox.Warning, 'Fehler', 'Fehler beim upload.', buttons=QMessageBox.Ok, parent=self) message.setDetailedText(repr(exception_type) + '\n' + repr(args)) message.exec_() @pyqtSlot() def update_resize(self,): if (self.check_box_resize.isChecked() and self.radio_button_absolute.isChecked()): width = self.spin_box_width.value() higth = self.spin_box_higth.value() self.upload.change_default_resize("{}x{}".format(width, higth)) elif (self.check_box_resize.isChecked() and self.radio_button_percentual.isChecked()): percentage = self.spin_box_percentual.value() self.upload.change_default_resize("{}%".format(percentage)) else: self.upload.change_default_resize(None) @pyqtSlot(bool) def set_resize_box_visibility(self, visible): if visible: LOGGER.debug('show resize box') self.update_resize() else: LOGGER.debug('hide resize box') self.update_resize() self.resize_container.setVisible(visible) @pyqtSlot(bool) def set_absolute_resize_box_visibility(self, visible): if visible: LOGGER.debug('show absolute resize box') self.update_resize() else: LOGGER.debug('hide absolute resize box') self.resize_container_absolute.setVisible(visible) @pyqtSlot(bool) def set_percentual_resize_box_visibility(self, visible): if visible: LOGGER.debug('show percentual resize box') self.update_resize() else: LOGGER.debug('hide percentual resize box') self.resize_container_percentual.setVisible(visible) @pyqtSlot() def remove_selected(self,): """ remove selected files from the upload list. """ for item in self.list_view_files.selectedIndexes(): self.list_view_files_model.remove_element(item.row(), item.row()) @pyqtSlot() def display_about_qt(self,): """ displays the about qt dialog """ QMessageBox.aboutQt(self,) @pyqtSlot() def shutdown(self,): """shut down Qapp""" self.thread_cleanup() QCoreApplication.instance().quit() def thread_cleanup(self): """ shuts down the upload thread at exit. """ LOGGER.debug('begin cleanup threads') try: self.upload_thread.quit() self.upload_thread.wait() # pylint: disable=bare-except # I do want to catch them all here, to be able to log them. except: LOGGER.exception('Exception while cleanup') LOGGER.debug('thread cleanup finished')
class MainGUI(QMainWindow): """The main GUI for azimuthal integration.""" _root_dir = osp.dirname(osp.abspath(__file__)) start_sgn = pyqtSignal() stop_sgn = pyqtSignal() quit_sgn = pyqtSignal() _db = RedisConnection() _WIDTH, _HEIGHT = config['GUI_MAIN_GUI_SIZE'] def __init__(self, pause_ev, close_ev): """Initialization.""" super().__init__() self._pause_ev = pause_ev self._close_ev = close_ev self._input_update_ev = Event() self._input = MpInQueue(self._input_update_ev, pause_ev, close_ev) self._pulse_resolved = config["PULSE_RESOLVED"] self._require_geometry = config["REQUIRE_GEOMETRY"] self._queue = deque(maxlen=1) self.setAttribute(Qt.WA_DeleteOnClose) self.title = f"EXtra-foam {__version__} ({config['DETECTOR']})" self.setWindowTitle(self.title + " - main GUI") # ************************************************************* # Central widget # ************************************************************* self._ctrl_widgets = [] # book-keeping control widgets self._cw = QSplitter() self._cw.setChildrenCollapsible(False) self.setCentralWidget(self._cw) self._left_cw_container = QScrollArea() self._left_cw_container.setFrameShape(QFrame.NoFrame) self._left_cw = QTabWidget() self._right_cw_container = QScrollArea() self._right_cw_container.setFrameShape(QFrame.NoFrame) self._right_cw = QSplitter(Qt.Vertical) self._right_cw.setChildrenCollapsible(False) self._source_cw = self.createCtrlWidget(DataSourceWidget) self._extension_cw = self.createCtrlWidget(ExtensionCtrlWidget) self._ctrl_panel_cw = QTabWidget() self._analysis_cw = QWidget() self._statistics_cw = QWidget() self._util_panel_container = QWidget() self._util_panel_cw = QTabWidget() # ************************************************************* # Tool bar # Note: the order of 'addAction` affect the unittest!!! # ************************************************************* self._tool_bar = self.addToolBar("Control") # make icon a bit larger self._tool_bar.setIconSize(1.25 * self._tool_bar.iconSize()) self._start_at = self.addAction("Start bridge", "start.png") self._start_at.triggered.connect(self.onStart) self._stop_at = self.addAction("Stop bridge", "stop.png") self._stop_at.triggered.connect(self.onStop) self._stop_at.setEnabled(False) self._tool_bar.addSeparator() image_tool_at = self.addAction("Image tool", "image_tool.png") image_tool_at.triggered.connect(lambda: (self._image_tool.show( ), self._image_tool.activateWindow())) open_poi_window_at = self.addAction("Pulse-of-interest", "poi.png") open_poi_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, PulseOfInterestWindow)) if not self._pulse_resolved: open_poi_window_at.setEnabled(False) pump_probe_window_at = self.addAction("Pump-probe", "pump-probe.png") pump_probe_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, PumpProbeWindow)) open_statistics_window_at = self.addAction("Correlation", "correlation.png") open_statistics_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, CorrelationWindow)) open_statistics_window_at = self.addAction("Histogram", "histogram.png") open_statistics_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, HistogramWindow)) open_bin2d_window_at = self.addAction("Binning", "binning.png") open_bin2d_window_at.triggered.connect( functools.partial(self.onOpenPlotWindow, BinningWindow)) self._tool_bar.addSeparator() open_file_stream_window_at = self.addAction("File stream", "file_stream.png") open_file_stream_window_at.triggered.connect( lambda: self.onOpenSatelliteWindow(FileStreamWindow)) open_about_at = self.addAction("About EXtra-foam", "about.png") open_about_at.triggered.connect( lambda: self.onOpenSatelliteWindow(AboutWindow)) # ************************************************************* # Miscellaneous # ************************************************************* # book-keeping opened windows self._plot_windows = WeakKeyDictionary() self._satellite_windows = WeakKeyDictionary() self._gui_logger = GuiLogger(parent=self) logger.addHandler(self._gui_logger) self._configurator = Configurator() self._thread_logger = ThreadLoggerBridge() self.quit_sgn.connect(self._thread_logger.stop) self._thread_logger_t = QThread() self._thread_logger.moveToThread(self._thread_logger_t) self._thread_logger_t.started.connect(self._thread_logger.recv) self._thread_logger.connectToMainThread(self) # For real time plot self._running = False self._plot_timer = QTimer() self._plot_timer.timeout.connect(self.updateAll) # For checking the connection to the Redis server self._redis_timer = QTimer() self._redis_timer.timeout.connect(self.pingRedisServer) self.__redis_connection_fails = 0 self._mon_proxy = MonProxy() # ************************************************************* # control widgets # ************************************************************* # analysis control widgets self.analysis_ctrl_widget = self.createCtrlWidget(AnalysisCtrlWidget) self.pump_probe_ctrl_widget = self.createCtrlWidget( PumpProbeCtrlWidget) self.fom_filter_ctrl_widget = self.createCtrlWidget( FomFilterCtrlWidget) # statistics control widgets self.bin_ctrl_widget = self.createCtrlWidget(BinCtrlWidget) self.histogram_ctrl_widget = self.createCtrlWidget(HistogramCtrlWidget) self.correlation_ctrl_widget = self.createCtrlWidget( CorrelationCtrlWidget) # ************************************************************* # status bar # ************************************************************* # StatusBar to display topic name self.statusBar().showMessage(f"TOPIC: {config['TOPIC']}") self.statusBar().setStyleSheet("QStatusBar{font-weight:bold;}") # ImageToolWindow is treated differently since it is the second # control window. self._image_tool = ImageToolWindow( queue=self._queue, pulse_resolved=self._pulse_resolved, require_geometry=self._require_geometry, parent=self) self.initUI() self.initConnections() self.updateMetaData() self._configurator.onInit() self.setMinimumSize(640, 480) self.resize(self._WIDTH, self._HEIGHT) self.show() def createCtrlWidget(self, widget_class): widget = widget_class(pulse_resolved=self._pulse_resolved, require_geometry=self._require_geometry) self._ctrl_widgets.append(widget) return widget def initUI(self): self.initLeftUI() self.initRightUI() self._cw.addWidget(self._left_cw_container) self._cw.addWidget(self._right_cw_container) self._cw.setSizes([self._WIDTH * 0.5, self._WIDTH * 0.5]) def initLeftUI(self): self._left_cw.setTabPosition(QTabWidget.TabPosition.West) self._left_cw.addTab(self._source_cw, "Data source") self._left_cw_container.setWidget(self._left_cw) self._left_cw_container.setWidgetResizable(True) self._left_cw.addTab(self._extension_cw, "Extension") def initRightUI(self): self.initCtrlUI() self.initUtilUI() self._right_cw.addWidget(self._ctrl_panel_cw) self._right_cw.addWidget(self._util_panel_container) self._ctrl_panel_cw.setFixedHeight( self._ctrl_panel_cw.minimumSizeHint().height()) self._right_cw_container.setWidget(self._right_cw) self._right_cw_container.setWidgetResizable(True) def initCtrlUI(self): self.initGeneralAnalysisUI() self.initStatisticsAnalysisUI() self._ctrl_panel_cw.addTab(self._analysis_cw, "General analysis") self._ctrl_panel_cw.addTab(self._statistics_cw, "Statistics analysis") def initGeneralAnalysisUI(self): layout = QVBoxLayout() layout.addWidget(self.analysis_ctrl_widget) layout.addWidget(self.pump_probe_ctrl_widget) layout.addWidget(self.fom_filter_ctrl_widget) self._analysis_cw.setLayout(layout) def initStatisticsAnalysisUI(self): layout = QVBoxLayout() layout.addWidget(self.correlation_ctrl_widget) layout.addWidget(self.bin_ctrl_widget) layout.addWidget(self.histogram_ctrl_widget) self._statistics_cw.setLayout(layout) def initUtilUI(self): self._util_panel_cw.addTab(self._gui_logger.widget, "Logger") self._util_panel_cw.addTab(self._configurator, "Configurator") self._util_panel_cw.setTabPosition(QTabWidget.TabPosition.South) layout = QVBoxLayout() layout.addWidget(self._util_panel_cw) self._util_panel_container.setLayout(layout) def initConnections(self): self._configurator.load_metadata_sgn.connect(self.loadMetaData) def connect_input_to_output(self, output): self._input.connect(output) @profiler("Update Plots", process_time=True) def updateAll(self): """Update all the plots in the main and child windows.""" if not self._running: return try: processed = self._input.get() self._queue.append(processed) except Empty: return # clear the previous plots no matter what comes next # for w in self._plot_windows.keys(): # w.reset() data = self._queue[0] self._image_tool.updateWidgetsF() for w in itertools.chain(self._plot_windows): try: w.updateWidgetsF() except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() logger.debug( repr(traceback.format_tb(exc_traceback)) + repr(e)) logger.error(f"[Update plots] {repr(e)}") logger.debug(f"Plot train with ID: {data.tid}") def pingRedisServer(self): try: self._db.ping() if self.__redis_connection_fails > 0: # Note: Indeed, we do not have mechanism to recover from # a Redis server crash. It is recommended to restart # Extra-foam if you encounter this situation. logger.info("Reconnect to the Redis server!") self.__redis_connection_fails = 0 except ConnectionError: self.__redis_connection_fails += 1 rest_attempts = config["REDIS_MAX_PING_ATTEMPTS"] - \ self.__redis_connection_fails if rest_attempts > 0: logger.warning(f"No response from the Redis server! Shut " f"down after {rest_attempts} attempts ...") else: logger.warning(f"No response from the Redis server! " f"Shutting down!") self.close() def addAction(self, description, filename): icon = QIcon(osp.join(self._root_dir, "icons/" + filename)) action = QAction(icon, description, self) self._tool_bar.addAction(action) return action def onOpenPlotWindow(self, instance_type): """Open a plot window if it does not exist. Otherwise bring the opened window to the table top. """ if self.checkWindowExistence(instance_type, self._plot_windows): return return instance_type(self._queue, pulse_resolved=self._pulse_resolved, parent=self) def onOpenSatelliteWindow(self, instance_type): """Open a satellite window if it does not exist. Otherwise bring the opened window to the table top. """ if self.checkWindowExistence(instance_type, self._satellite_windows): return return instance_type(parent=self) def checkWindowExistence(self, instance_type, windows): for key in windows: if isinstance(key, instance_type): key.activateWindow() return True return False def registerWindow(self, instance): self._plot_windows[instance] = 1 def unregisterWindow(self, instance): del self._plot_windows[instance] def registerSatelliteWindow(self, instance): self._satellite_windows[instance] = 1 def unregisterSatelliteWindow(self, instance): del self._satellite_windows[instance] @property def input(self): return self._input def start(self): """Start running. ProcessWorker interface. """ self._thread_logger_t.start() self._plot_timer.start(config["GUI_PLOT_UPDATE_TIMER"]) self._redis_timer.start(config["REDIS_PING_ATTEMPT_INTERVAL"]) self._input.start() def onStart(self): if not self.updateMetaData(): return self.start_sgn.emit() self._start_at.setEnabled(False) self._stop_at.setEnabled(True) for widget in self._ctrl_widgets: widget.onStart() self._image_tool.onStart() self._configurator.onStart() self._running = True # starting to update plots self._input_update_ev.set() # notify update def onStop(self): """Actions taken before the end of a 'run'.""" self._running = False self.stop_sgn.emit() # TODO: wait for some signal self._start_at.setEnabled(True) self._stop_at.setEnabled(False) for widget in self._ctrl_widgets: widget.onStop() self._image_tool.onStop() self._configurator.onStop() def updateMetaData(self): """Update metadata from all the ctrl widgets. :returns bool: True if all metadata successfully parsed and emitted, otherwise False. """ for widget in self._ctrl_widgets: succeeded = widget.updateMetaData() if not succeeded: return False return self._image_tool.updateMetaData() def loadMetaData(self): """Load metadata from Redis and set child control widgets.""" for widget in self._ctrl_widgets: widget.loadMetaData() return self._image_tool.loadMetaData() @pyqtSlot(str, str) def onLogMsgReceived(self, ch, msg): if ch == 'log:debug': logger.debug(msg) elif ch == 'log:info': logger.info(msg) elif ch == 'log:warning': logger.warning(msg) elif ch == 'log:error': logger.error(msg) def closeEvent(self, QCloseEvent): # prevent from logging in the GUI when it has been closed logger.removeHandler(self._gui_logger) # tell all processes to close self._close_ev.set() # clean up the logger thread self.quit_sgn.emit() self._thread_logger_t.quit() self._thread_logger_t.wait() # shutdown pipeline workers and Redis server shutdown_all() self._image_tool.close() for window in list( itertools.chain(self._plot_windows, self._satellite_windows)): # Close all open child windows to make sure their resources # (any running process etc.) are released gracefully. This # is especially necessary for the case when file stream was # still ongoing when the main GUI was closed. window.close() super().closeEvent(QCloseEvent)
def join(self): QThread.wait(self) return self.code
class Q3DWindow(QMainWindow): def __init__(self, qgisIface, settings, preview=True): QMainWindow.__init__(self, parent=qgisIface.mainWindow()) self.setAttribute(Qt.WA_DeleteOnClose) # set map settings settings.setMapSettings(qgisIface.mapCanvas().mapSettings()) self.qgisIface = qgisIface self.settings = settings self.lastDir = None self.thread = QThread(self) if RUN_CNTLR_IN_BKGND else None self.controller = Q3DController(settings, self.thread) self.controller.enabled = preview if self.thread: self.thread.finished.connect(self.controller.deleteLater) self.thread.finished.connect(self.thread.deleteLater) # start worker thread event loop self.thread.start() self.setWindowIcon(QIcon(pluginDir("Qgis2threejs.png"))) self.ui = Ui_Q3DWindow() self.ui.setupUi(self) self.iface = Q3DViewerInterface(settings, self.ui.webView._page, self, self.ui.treeView, parent=self) self.controller.connectToIface(self.iface) self.setupMenu() self.setupContextMenu() self.setupStatusBar(self.iface, preview) self.ui.treeView.setup(self.iface) self.ui.treeView.addLayers(settings.getLayerList()) self.ui.webView.setup(self.iface, settings, self, preview) self.ui.dockWidgetConsole.hide() if DEBUG_MODE: self.ui.actionInspector = QAction(self) self.ui.actionInspector.setObjectName("actionInspector") self.ui.actionInspector.setText("Web Inspector...") self.ui.menuWindow.addSeparator() self.ui.menuWindow.addAction(self.ui.actionInspector) self.ui.actionInspector.triggered.connect( self.ui.webView.showInspector) # signal-slot connections # map canvas self.controller.connectToMapCanvas(qgisIface.mapCanvas()) # console self.ui.lineEditInputBox.returnPressed.connect(self.runInputBoxString) self.alwaysOnTopToggled(False) # restore window geometry and dockwidget layout settings = QSettings() self.restoreGeometry(settings.value("/Qgis2threejs/wnd/geometry", b"")) self.restoreState(settings.value("/Qgis2threejs/wnd/state", b"")) def closeEvent(self, event): self.iface.abort() # save export settings to a settings file self.settings.saveSettings() settings = QSettings() settings.setValue("/Qgis2threejs/wnd/geometry", self.saveGeometry()) settings.setValue("/Qgis2threejs/wnd/state", self.saveState()) # stop worker thread event loop if self.thread: self.thread.quit() self.thread.wait() # close dialogs for dlg in self.findChildren(QDialog): dlg.close() QMainWindow.closeEvent(self, event) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.iface.abort() QMainWindow.keyPressEvent(self, event) def setupMenu(self): self.ui.menuPanels.addAction( self.ui.dockWidgetLayers.toggleViewAction()) self.ui.menuPanels.addAction( self.ui.dockWidgetConsole.toggleViewAction()) self.ui.actionGroupCamera = QActionGroup(self) self.ui.actionPerspective.setActionGroup(self.ui.actionGroupCamera) self.ui.actionOrthographic.setActionGroup(self.ui.actionGroupCamera) self.ui.actionOrthographic.setChecked(self.settings.isOrthoCamera()) # signal-slot connections self.ui.actionExportToWeb.triggered.connect(self.exportToWeb) self.ui.actionSaveAsImage.triggered.connect(self.saveAsImage) self.ui.actionSaveAsGLTF.triggered.connect(self.saveAsGLTF) self.ui.actionLoadSettings.triggered.connect(self.loadSettings) self.ui.actionSaveSettings.triggered.connect(self.saveSettings) self.ui.actionClearSettings.triggered.connect(self.clearSettings) self.ui.actionPluginSettings.triggered.connect(self.pluginSettings) self.ui.actionSceneSettings.triggered.connect( self.showScenePropertiesDialog) self.ui.actionGroupCamera.triggered.connect(self.switchCamera) self.ui.actionAddPointCloudLayer.triggered.connect( self.showAddPointCloudLayerDialog) self.ui.actionNorthArrow.triggered.connect(self.showNorthArrowDialog) self.ui.actionHeaderFooterLabel.triggered.connect( self.showHFLabelDialog) self.ui.actionResetCameraPosition.triggered.connect( self.ui.webView.resetCameraState) self.ui.actionReload.triggered.connect(self.ui.webView.reloadPage) self.ui.actionAlwaysOnTop.toggled.connect(self.alwaysOnTopToggled) self.ui.actionHelp.triggered.connect(self.help) self.ui.actionHomePage.triggered.connect(self.homePage) self.ui.actionSendFeedback.triggered.connect(self.sendFeedback) self.ui.actionAbout.triggered.connect(self.about) def setupContextMenu(self): # console self.ui.actionConsoleCopy.triggered.connect(self.copyConsole) self.ui.actionConsoleClear.triggered.connect(self.clearConsole) self.ui.listWidgetDebugView.addAction(self.ui.actionConsoleCopy) self.ui.listWidgetDebugView.addAction(self.ui.actionConsoleClear) def setupStatusBar(self, iface, previewEnabled=True): w = QProgressBar(self.ui.statusbar) w.setObjectName("progressBar") w.setMaximumWidth(250) w.setAlignment(Qt.AlignCenter) w.setVisible(False) self.ui.statusbar.addPermanentWidget(w) self.ui.progressBar = w w = QCheckBox(self.ui.statusbar) w.setObjectName("checkBoxPreview") w.setText("Preview") # _translate("Q3DWindow", "Preview")) w.setChecked(previewEnabled) self.ui.statusbar.addPermanentWidget(w) self.ui.checkBoxPreview = w self.ui.checkBoxPreview.toggled.connect(iface.previewStateChanged) def switchCamera(self, action): self.iface.requestCameraSwitch(action == self.ui.actionOrthographic) def loadSettings(self): # file open dialog directory = self.lastDir or QgsProject.instance().homePath( ) or QDir.homePath() filterString = "Settings files (*.qto3settings);;All files (*.*)" filename, _ = QFileDialog.getOpenFileName(self, "Load Export Settings", directory, filterString) if not filename: return self.ui.treeView.uncheckAll() # hide all 3D objects from the scene settings = self.settings.clone() settings.loadSettingsFromFile(filename) self.ui.treeView.updateLayersCheckState(settings) self.iface.requestExportSettingsUpdate(settings) self.lastDir = os.path.dirname(filename) def saveSettings(self): # file save dialog directory = self.lastDir or QgsProject.instance().homePath( ) or QDir.homePath() filename, _ = QFileDialog.getSaveFileName( self, "Save Export Settings", directory, "Settings files (*.qto3settings)") if not filename: return # append .qto3settings extension if filename doesn't have if os.path.splitext(filename)[1].lower() != ".qto3settings": filename += ".qto3settings" self.settings.saveSettings(filename) self.lastDir = os.path.dirname(filename) def clearSettings(self): if QMessageBox.question( self, "Qgis2threejs", "Are you sure you want to clear export settings?" ) != QMessageBox.Yes: return self.ui.treeView.uncheckAll() # hide all 3D objects from the scene self.ui.treeView.clearPointCloudLayers() self.ui.actionPerspective.setChecked(True) settings = self.settings.clone() settings.clear() settings.updateLayerList() self.iface.requestExportSettingsUpdate(settings) def alwaysOnTopToggled(self, checked): if checked: self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint) self.show() def changeEvent(self, event): if event.type() == QEvent.WindowStateChange: if self.windowState() & Qt.WindowMinimized: self.runScript("app.pause();") else: self.runScript("app.resume();") def copyConsole(self): # copy selected item(s) text to clipboard indices = self.ui.listWidgetDebugView.selectionModel().selectedIndexes( ) text = "\n".join( [str(index.data(Qt.DisplayRole)) for index in indices]) if text: QApplication.clipboard().setText(text) def clearConsole(self): self.ui.listWidgetDebugView.clear() def printConsoleMessage(self, message, lineNumber="", sourceID=""): if sourceID: source = sourceID if lineNumber == "" else "{} ({})".format( sourceID.split("/")[-1], lineNumber) text = "{}: {}".format(source, message) else: text = message self.ui.listWidgetDebugView.addItem(text) def runInputBoxString(self): text = self.ui.lineEditInputBox.text() self.ui.listWidgetDebugView.addItem("> " + text) result = self.ui.webView._page.mainFrame().evaluateJavaScript(text) if result is not None: self.ui.listWidgetDebugView.addItem("<- {}".format(result)) self.ui.listWidgetDebugView.scrollToBottom() self.ui.lineEditInputBox.clear() def runScript(self, string, message="", sourceID="Q3DWindow.py"): return self.ui.webView.runScript(string, message, sourceID=sourceID) def exportToWeb(self): from .exporttowebdialog import ExportToWebDialog dialog = ExportToWebDialog(self.settings, self.ui.webView._page, self) dialog.show() dialog.exec_() def saveAsImage(self): if not self.ui.checkBoxPreview.isChecked(): QMessageBox.warning( self, "Save Scene as Image", "You need to enable the preview to use this function.") return from .imagesavedialog import ImageSaveDialog dialog = ImageSaveDialog(self) dialog.exec_() def saveAsGLTF(self): if not self.ui.checkBoxPreview.isChecked(): QMessageBox.warning( self, "Save Current Scene as glTF", "You need to enable the preview to use this function.") return filename, _ = QFileDialog.getSaveFileName( self, self.tr("Save Current Scene as glTF"), self.lastDir or QDir.homePath(), "glTF files (*.gltf);;Binary glTF files (*.glb)") if filename: self.ui.statusbar.showMessage( "Exporting current scene to a glTF file...") self.ui.webView._page.loadScriptFile(q3dconst.SCRIPT_GLTFEXPORTER) self.runScript("saveModelAsGLTF('{0}');".format( filename.replace("\\", "\\\\"))) self.ui.statusbar.clearMessage() self.lastDir = os.path.dirname(filename) def pluginSettings(self): from .pluginsettings import SettingsDialog dialog = SettingsDialog(self) if dialog.exec_(): pluginManager().reloadPlugins() def showScenePropertiesDialog(self): dialog = PropertiesDialog(self.settings, self.qgisIface, self) dialog.propertiesAccepted.connect(self.updateSceneProperties) dialog.showSceneProperties() # @pyqtSlot(dict) def updateSceneProperties(self, properties): if self.settings.sceneProperties() != properties: self.iface.requestSceneUpdate(properties) def showLayerPropertiesDialog(self, layer): dialog = PropertiesDialog(self.settings, self.qgisIface, self) dialog.propertiesAccepted.connect(self.updateLayerProperties) dialog.showLayerProperties(layer) # @pyqtSlot(Layer) def updateLayerProperties(self, layer): orig_layer = self.settings.getItemByLayerId(layer.layerId) if layer.name != orig_layer.name: item = self.ui.treeView.getItemByLayerId(layer.layerId) if item: item.setText(layer.name) if layer.properties != orig_layer.properties: layer.updated = True self.iface.requestLayerUpdate(layer) def getDefaultProperties(self, layer): dialog = PropertiesDialog(self.settings, self.qgisIface, self) dialog.setLayer(layer) return dialog.page.properties() def showAddPointCloudLayerDialog(self): dialog = AddPointCloudLayerDialog(self) if dialog.exec_(): url = dialog.ui.lineEdit_Source.text() self.addPointCloudLayer(url) def addPointCloudLayer(self, url): try: name = url.split("/")[-2] except IndexError: name = "No name" layerId = "pc:" + name + datetime.now().strftime("%y%m%d%H%M%S") properties = {"url": url} layer = Layer(layerId, name, q3dconst.TYPE_POINTCLOUD, properties, visible=True) self.iface.addLayerRequest.emit(layer) self.ui.treeView.addLayer(layer) def showNorthArrowDialog(self): dialog = NorthArrowDialog( self.settings.decorationProperties("NorthArrow"), self) dialog.propertiesAccepted.connect( lambda p: self.iface.requestDecorationUpdate("NorthArrow", p)) dialog.show() dialog.exec_() def showHFLabelDialog(self): dialog = HFLabelDialog(self.settings.decorationProperties("Label"), self) dialog.propertiesAccepted.connect( lambda p: self.iface.requestDecorationUpdate("Label", p)) dialog.show() dialog.exec_() def help(self): QDesktopServices.openUrl(QUrl("https://qgis2threejs.readthedocs.io/")) def homePage(self): QDesktopServices.openUrl( QUrl("https://github.com/minorua/Qgis2threejs")) def sendFeedback(self): QDesktopServices.openUrl( QUrl("https://github.com/minorua/Qgis2threejs/issues")) def about(self): QMessageBox.information(self, "Qgis2threejs Plugin", "Plugin version: {0}".format(PLUGIN_VERSION), QMessageBox.Ok)