Esempio n. 1
0
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()
Esempio n. 2
0
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
Esempio n. 3
0
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)
Esempio n. 4
0
File: future.py Progetto: gpa14/enki
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
Esempio n. 5
0
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)
Esempio n. 6
0
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)
Esempio n. 7
0
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()
Esempio n. 8
0
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
Esempio n. 9
0
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()
Esempio n. 11
0
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)
Esempio n. 12
0
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
Esempio n. 13
0
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()
Esempio n. 14
0
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()
Esempio n. 15
0
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()
Esempio n. 16
0
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}个笔记')
Esempio n. 17
0
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
Esempio n. 18
0
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"])
Esempio n. 19
0
class DAQ_Move(Ui_Form, QObject):
    """
        | DAQ_Move object is a module used to control one motor from a specified list.
        |
        | Preset is an optional list of dicts used to managers programatically settings such as the name of the controller from the list of possible controllers, COM address...
        |
        | Init is a boolean to tell the programm to initialize the controller at the start of the programm given the managers options

        ========================= =================================================
        **Attributes**             **Type**
        *command_stage*            instance of 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)
Esempio n. 20
0
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)
Esempio n. 21
0
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)
Esempio n. 22
0
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()
Esempio n. 23
0
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
Esempio n. 24
0
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()
Esempio n. 25
0
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
Esempio n. 26
0
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()
Esempio n. 27
0
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()
Esempio n. 28
0
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()
Esempio n. 29
0
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()
Esempio n. 30
0
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()
Esempio n. 31
0
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)
Esempio n. 32
0
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')
Esempio n. 33
0
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')
Esempio n. 34
0
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)
Esempio n. 35
0
 def join(self):
     QThread.wait(self)
     return self.code
Esempio n. 36
0
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)