Esempio n. 1
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. 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 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. 4
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. 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 MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.showMaximized()
        self.setWindowTitle("EVGP Race Control System")

        self.is_server_started = False

        teams_list_file_path = "racers_list.yaml"
        if not os.path.exists(teams_list_file_path):
            logging.warning("No racers_list.yaml found!")
            msgBox = QMessageBox()
            msgBox.setIcon(QMessageBox.Warning)
            msgBox.setText(
                "No racers_list.yaml found.\nPress Open and use the file explorer to select the racer list YAML file"
            )
            msgBox.setWindowTitle("No racers_list.yaml found!")
            msgBox.setStandardButtons(QMessageBox.Open)
            returnValue = msgBox.exec()
            if returnValue == QMessageBox.Open:
                teams_list_file_path = QFileDialog.getOpenFileName(
                    self, 'Open Racer List File', os.getcwd(),
                    "Yaml files (*.yaml)")[0]
        self.model = RCSModel(teams_list_file_path)

        layout = QGridLayout()
        layout.setColumnStretch(0, 10)
        layout.setColumnStretch(1, 10)

        #FILTERED ACTIVE RACE TABLE
        self.activeRaceProxyModel = RCSSortFilterProxyModel(True)
        self.activeRaceProxyModel.setDynamicSortFilter(True)
        self.activeRaceProxyModel.setSourceModel(self.model)

        self.activeRaceTable = QtWidgets.QTableView()
        self.activeRaceTable.setModel(self.activeRaceProxyModel)
        self.activeRaceTable.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.activeRaceTable.horizontalHeader().setStretchLastSection(True)
        self.activeRaceTable.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.ResizeToContents)
        self.activeRaceTable.setSelectionBehavior(
            QtWidgets.QTableView.SelectRows)
        self.activeRaceTable.setSelectionMode(
            QtWidgets.QTableView.SingleSelection)
        layout.addWidget(self.activeRaceTable, 1, 0)

        self.standbyRaceProxyModel = RCSSortFilterProxyModel(False)
        self.standbyRaceProxyModel.setDynamicSortFilter(True)
        self.standbyRaceProxyModel.setSourceModel(self.model)

        self.standbyRaceTable = QtWidgets.QTableView()
        self.standbyRaceTable.setModel(self.standbyRaceProxyModel)
        self.standbyRaceTable.setSizeAdjustPolicy(
            QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.standbyRaceTable.horizontalHeader().setSectionResizeMode(
            QtWidgets.QHeaderView.ResizeToContents)
        self.standbyRaceTable.horizontalHeader().setStretchLastSection(True)
        self.standbyRaceTable.setSelectionBehavior(
            QtWidgets.QTableView.SelectRows)
        self.standbyRaceTable.setSelectionMode(
            QtWidgets.QTableView.SingleSelection)
        layout.addWidget(self.standbyRaceTable, 1, 1)

        self.activeRaceTableLabel = QLabel("Active Race Table")
        self.activeRaceTableLabel.setAlignment(Qt.AlignCenter)
        self.standbyRaceTableLabel = QLabel("Other Teams Table")
        self.standbyRaceTableLabel.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.activeRaceTableLabel, 0, 0)
        layout.addWidget(self.standbyRaceTableLabel, 0, 1)

        self.standbyRaceTable.setSortingEnabled(True)
        self.standbyRaceTable.sortByColumn(1, Qt.AscendingOrder)
        self.activeRaceTable.setSortingEnabled(True)
        self.activeRaceTable.sortByColumn(1, Qt.AscendingOrder)

        self.selectedIndex = None
        self.standbyRaceTable.selectionModel().selectionChanged.connect(
            self.standby_race_table_selection_handler)
        self.activeRaceTable.selectionModel().selectionChanged.connect(
            self.active_race_table_selection_handler)

        self.horizontalGroupBox = QGroupBox("")
        self.horizontalGroupBox.setLayout(layout)

        # All relevant buttons in sidebar
        self.button_sidebar_vBox = QVBoxLayout()
        layout.addLayout(self.button_sidebar_vBox, 1, 3)
        self.button_sidebar_vBox.setAlignment(Qt.AlignTop)

        # Move Racers Buttons
        self.button_container_stylesheet = "QWidget#ButtonContainer{background-color: rgb(200, 200, 200);\n border-radius: 5;\n}"
        move_racers_button_container = QWidget()
        move_racers_button_container.setObjectName("ButtonContainer")
        move_racers_button_container.setStyleSheet(
            self.button_container_stylesheet)
        move_racers_button_container.setSizePolicy(QSizePolicy.Preferred,
                                                   QSizePolicy.Maximum)
        move_racers_layout = QVBoxLayout(move_racers_button_container)
        move_racers_layout.addWidget(QLabel("ADMIN CONTROLS"))
        move_to_active_race_button = QPushButton("Move to Active Race")
        move_to_active_race_button.clicked.connect(self.move_to_active_race)
        move_racers_layout.addWidget(move_to_active_race_button)
        remove_from_active_race_button = QPushButton("Remove from Active Race")
        remove_from_active_race_button.clicked.connect(
            self.remove_from_active_race)
        move_racers_layout.addWidget(remove_from_active_race_button)

        self.button_sidebar_vBox.addWidget(move_racers_button_container)

        # Team State Control Buttons
        team_state_button_container = QWidget()
        team_state_button_container.setObjectName("ButtonContainer")
        team_state_button_container.setStyleSheet(
            self.button_container_stylesheet)
        team_state_button_container.setSizePolicy(QSizePolicy.Preferred,
                                                  QSizePolicy.Maximum)
        team_state_btns = self.create_team_state_buttons()
        team_state_layout = QVBoxLayout(team_state_button_container)
        team_state_layout.addWidget(QLabel("TEAM CONTROLS"))
        for btn in team_state_btns:
            team_state_layout.addWidget(btn)
        self.button_sidebar_vBox.addWidget(team_state_button_container)

        # Race State Control Buttons
        race_state_button_container = QWidget()
        race_state_button_container.setObjectName("ButtonContainer")
        race_state_button_container.setStyleSheet(
            self.button_container_stylesheet)
        race_state_button_container.setSizePolicy(QSizePolicy.Preferred,
                                                  QSizePolicy.Maximum)
        race_state_btns = self.create_race_state_buttons()
        race_state_layout = QVBoxLayout(race_state_button_container)
        race_state_layout.addWidget(QLabel("RACE CONTROLS"))
        for btn in race_state_btns:
            race_state_layout.addWidget(btn)
        self.button_sidebar_vBox.addWidget(race_state_button_container)

        self.info_group_box = QGroupBox("Race Status Information")
        self.race_state_label = QLabel("Race State: IN_GARAGE")
        self.info_label = QLabel("Race Status: No Race running.")
        info_layout = QVBoxLayout()
        info_layout.addWidget(self.race_state_label)
        info_layout.addWidget(self.info_label)
        self.info_group_box.setLayout(info_layout)
        layout.addWidget(self.info_group_box, 3, 0)

        self.buttonController = ButtonStateController(
            race_state_btns[0], race_state_btns[1], race_state_btns[2],
            race_state_btns[3], race_state_btns[4], team_state_btns[0],
            move_to_active_race_button, remove_from_active_race_button,
            self.race_state_label, self.info_label)
        self.model.race_state_change_signal.connect(
            self.buttonController.race_state_updated)

        verticalSpacer = QtWidgets.QSpacerItem(20, 40, QSizePolicy.Minimum,
                                               QSizePolicy.Expanding)
        layout.addItem(verticalSpacer, 4, 0, rowSpan=1, columnSpan=3)

        self.create_menu_bar()

        # wait for start of server
        self.server_wait_label = QLabel(
            "Waiting for TCP Server to start. Please hold on.")
        self.server_wait_label.setAlignment(Qt.AlignCenter)
        self.setCentralWidget(self.server_wait_label)
        self.start_server()

    # Make sure we stop the server on window close
    def closeEvent(self, event):
        self.stop_server()
        event.accept()

    def create_menu_bar(self):
        menuBar = QtWidgets.QMenuBar(self)
        self.setMenuBar(menuBar)
        helpMenu = menuBar.addMenu("&Help")
        self.reset_gui_buttons_actions = QtWidgets.QAction("Reset GUI buttons")
        self.reset_gui_buttons_actions.triggered.connect(
            self.buttonController.enable_all_buttons)
        helpMenu.addAction(self.reset_gui_buttons_actions)
        self.about_action = QtWidgets.QAction("About")
        self.about_action.triggered.connect(self.show_about_message)
        helpMenu.addAction(self.about_action)

    def show_about_message(self):
        text = "The Electric Vehicle GrandPrix Autonomous Race Control System " \
        "is brought to you by the RoboJackets at Georgia Tech.<br>" \
        "Contribute to the RCS at <a href='https://github.com/RoboJackets/evgp-rcs'>https://github.com/RoboJackets/evgp-rcs</a>"

        QMessageBox.question(self, 'About the EVGP Race Control System', text,
                             QMessageBox.Ok)

    def create_race_state_buttons(self):
        grid_active_race_button = QPushButton("GRID ACTIVE RACE")
        grid_active_race_button.clicked.connect(
            lambda: self.race_state_change_callback(RaceState.GRID_ACTIVE))
        start_race_button = QPushButton("START RACE")
        start_race_button.setEnabled(False)
        start_race_button.clicked.connect(
            lambda: self.race_state_change_callback(RaceState.GREEN_GREEN))
        red_flag_race_button = QPushButton("RED FLAG RACE")
        red_flag_race_button.clicked.connect(
            lambda: self.race_state_change_callback(RaceState.RED_FLAG))
        e_stop_race_button = QPushButton("E-STOP RACE")
        e_stop_race_button.clicked.connect(
            lambda: self.race_state_change_callback(RaceState.RED_RED))
        finish_race_button = QPushButton("FINISH RACE")
        finish_race_button.clicked.connect(
            lambda: self.race_state_change_callback(RaceState.IN_GARAGE))

        return [
            grid_active_race_button, start_race_button, red_flag_race_button,
            e_stop_race_button, finish_race_button
        ]

    def create_team_state_buttons(self):
        in_garage_team_button = QPushButton("IN GARAGE TEAM")
        in_garage_team_button.clicked.connect(
            lambda: self.team_state_change_callback(RaceState.IN_GARAGE))
        red_flag_team_button = QPushButton("RED FLAG TEAM")
        red_flag_team_button.clicked.connect(
            lambda: self.team_state_change_callback(RaceState.RED_FLAG))
        e_stop_team_button = QPushButton("E-STOP TEAM")
        e_stop_team_button.clicked.connect(
            lambda: self.team_state_change_callback(RaceState.RED_RED))

        return [
            in_garage_team_button, red_flag_team_button, e_stop_team_button
        ]

    def team_state_change_callback(self, state):
        if self.selectedIndex is not None:
            self.model.team_state_change(self.selectedIndex, state)

    def race_state_change_callback(self, state):
        self.model.race_state_change(state)

    def move_to_active_race(self):
        if self.selectedIndex is not None:
            changed = self.model.move_to_active_race(self.selectedIndex)
            if changed:
                self.clearAllSelections()

    def remove_from_active_race(self):
        if self.selectedIndex is not None:
            changed = self.model.move_to_standby_race(self.selectedIndex)
            if changed:
                self.clearAllSelections()

    def start_server(self):
        if not self.is_server_started:
            self.is_server_started = True
            port = 12017
            server_backlog = 10
            send_hz = 10
            ip_list = self.model.teams_list.keys()
            self.server = TCPServer(port,
                                    server_backlog,
                                    whitelist=ip_list,
                                    hz=send_hz)
            self.server.new_connection.connect(
                self.model.new_connection_handler)
            self.server.lost_connection.connect(
                self.model.lost_connection_handler)
            self.server.new_response.connect(self.model.new_response_handler)

            self.server.server_ready.connect(self.server_ready_handler)

            self.model.team_state_change_signal.connect(
                self.server.on_race_state_change)

            self.server_thread = QThread()
            self.server.moveToThread(self.server_thread)
            self.server_thread.started.connect(self.server.run_server)
            self.server_thread.start()

    def stop_server(self):
        if self.is_server_started:
            self.server.stop()
            self.is_server_started = False
            self.server_thread.quit()

    @QtCore.pyqtSlot(bool)
    def server_ready_handler(self, isReady):
        if isReady:
            self.setCentralWidget(self.horizontalGroupBox)
        if not isReady:
            self.server_wait_label.setText(
                "Server Error: Please restart program.")
            QMessageBox.question(
                self, 'Server Error',
                "Server failed to start.\nPress \"Close\" to quit program, then fix your network issues and restart this program.",
                QMessageBox.Close)
            self.close()

    @QtCore.pyqtSlot(QItemSelection, QItemSelection)
    def standby_race_table_selection_handler(self, filterTableSelection,
                                             filterTableDeselected):
        if filterTableSelection.indexes():
            self.activeRaceTable.selectionModel().clearSelection()
            self.selectedIndex = self.standbyRaceProxyModel.mapToSource(
                filterTableSelection.indexes()[0]).row()

    @QtCore.pyqtSlot(QItemSelection, QItemSelection)
    def active_race_table_selection_handler(self, tableSelection,
                                            tableDeselected):
        if tableSelection.indexes():
            self.standbyRaceTable.selectionModel().clearSelection()
            self.selectedIndex = self.activeRaceProxyModel.mapToSource(
                tableSelection.indexes()[0]).row()

    def clearAllSelections(self):
        self.activeRaceTable.selectionModel().clearSelection()
        self.standbyRaceTable.selectionModel().clearSelection()
        self.selectedIndex = None
Esempio n. 10
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.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.view_results_tab = ViewResultsTab(shared_state, self._computations_thread)
        self.view_results_tab.setupUi(self.viewResultsTab)

        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.view_results_tab]

        self.MainTabs.currentChanged.connect(lambda index: self.tabs[index].tab_opened())

    def closeEvent(self, event):
        sys.stdout = self._stdout_old
        sys.stderr = self._stderr_old
        self._message_receiver.is_running = False
        self._logging_update_thread.quit()
        self._logging_update_thread.wait(10)
        super().closeEvent(event)

    def send_sigint(self, *args):
        self.close()

    @pyqtSlot()
    def computations_started(self):
        self.executionStatusLabel.setText('Computing')
        self.executionStatusIcon.setPixmap(QtGui.QPixmap(":/main_gui/icon_status_green.png"))

    @pyqtSlot()
    def computations_finished(self):
        self.executionStatusLabel.setText('Idle')
        self.executionStatusIcon.setPixmap(QtGui.QPixmap(":/main_gui/icon_status_red.png"))

    @pyqtSlot(str)
    def update_log(self, string):
        sb = self.loggingTextBox.verticalScrollBar()
        scrollbar_position = sb.value()
        autoscroll = scrollbar_position == sb.maximum()
        self.loggingTextBox.moveCursor(QtGui.QTextCursor.End)
        self.loggingTextBox.insertPlainText(string)

        if autoscroll:
            sb.setValue(sb.maximum())
        else:
            sb.setValue(scrollbar_position)
class ControlSystem(object):

    def __init__(self, parent_app, title="PyControlSystem", server_ip='127.0.0.1', server_port=5000, debug=False):

        # Get the root folder of this script
        self._root = os.path.abspath(os.path.dirname(__file__))
        self._title = title

        current_time = time.strftime('%a-%d-%b-%Y_%H-%M-%S-EST', time.localtime())
        self._data_logger = DataLogger(filename=r"D:\mist-1_cs_logs\smist-1_log_{}.h5".format(current_time))
        self._data_logger.initialize()

        # Initialize communicator thread as None
        self._communicator = None

        # Store reference to 'parent' QApplication and set some parameters
        self._app = parent_app

        # TODO: Create Default XML file and Settings dialog to choose using this or not.
        if qdarkstyle is not None:
            self._app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())

        self._app.aboutToQuit.connect(self.on_quit_button)  # connect the closing event to the quit button procedure

        # --- Set up Qt UI and connect some UI signals --- #
        self._window = MainWindow.MainWindow()
        self._window.setWindowTitle(self._title)

        self._window.ui.btnSave.triggered.connect(self.on_save_button)
        self._window.ui.action_save_session.triggered.connect(self.on_save_button)

        self._window.ui.btnSaveAs.triggered.connect(self.on_save_as_button)
        self._window.ui.action_save_session_as.triggered.connect(self.on_save_as_button)

        self._window.ui.btnLoad.triggered.connect(self.on_load_button)
        self._window.ui.action_load_session.triggered.connect(self.on_load_button)

        self._window.ui.btnQuit.triggered.connect(self.on_quit_button)
        self._window.ui.action_quit.triggered.connect(self.on_quit_button)

        # Connect printing functions
        self._window.ui.action_print.triggered.connect(self.handle_print)
        self._window.ui.action_print_preview.triggered.connect(self.handle_preview)
        self._print_editor = QTextEdit()  # Create a QTextEdit object that holds the information to be printed
        self._print_font = QFont()
        self._print_font.setFamily("Calibri")
        self._print_font.setPointSize(12)
        self._print_editor.setFont(self._print_font)
        self._print_editor.hide()

        self._window.ui.btnStartPause.clicked.connect(self.on_start_pause_click)
        self._window.ui.btnStop_2.clicked.connect(self.on_stop_click)
        self._window.ui.btnStop_2.setEnabled(False)
        self._window.ui.btnResetPinnedPlot.clicked.connect(self.reset_pinned_plot_callback)

        # Connect the menu buttons
        self._window.ui.action_slack.triggered.connect(self.show_slack_dialog)

        self._window.ui.btnSetupDevicePlots.clicked.connect(self.show_PlotChooseDialog)
        self._window.ui.btnAddProcedure.clicked.connect(self.show_ProcedureDialog)
        self._window._sig_entry_form_changed.connect(self.connect_device_channel_entry_form)

        # --- Plotting timer --- #
        self._plot_timer = QTimer()
        self._plot_timer.timeout.connect(self.update_value_displays)
        # self._plot_timer.start(25)

        # Handling of tabs to windows to tabs
        self._plots_in_window = False  # Flag that tells the GUI whether the plot tab is in a window or a tab
        self._window.ui.tabMain.sig_window_to_tab.connect(self.window_to_tab)
        self._window.ui.tabMain.sig_tab_to_window.connect(self.tab_to_window)

        # --- Initialize server --- #
        self.debug = debug
        self._server_url = 'http://{}:{}/'.format(server_ip, server_port)

        try:
            r = requests.get(self._server_url + 'initialize/')
            if r.status_code == 200:
                self._window.status_message(r.text)
            else:
                print('[Error initializing server] {}: {}'.format(r.status_code, r.text))
        except Exception as e:
            print('Exception was: {}'.format(e))
            print("Did you start the server first? Do you have the correct IP address?")
            exit()

        # --- Get devices connected to server --- #
        # r = requests.get(self._server_url + 'device/active')
        # if r.status_code == 200:
        #     devices = json.loads(r.text)
        #     for device_id, device_info in devices.items():
        #         self._window.status_message('Found {} with ID {} on port {}.'.format(
        #             device_info['identifier'],
        #             device_id,
        #             device_info['port']))
        # else:
        #     print('[Error getting devices] {}: {}'.format(r.status_code, r.text))
        #     sys.exit(0)

        # --- Set up communication pipes --- #
        self._keep_communicating = False
        self._retry_devices = False
        self._polling_rate = 50.0
        self._com_period = 1.0 / self._polling_rate

        # --- Set up data dictionaries and lists --- #
        self._devices = {}
        self._procedures = {}
        self._critical_procedures = {}
        self._emergency_stop_signals = {}
        self._plotted_channels = []
        self._locked_devices = []

        # --- Keep persistent communicator thread --- #
        self._com_thread = QThread()

        self._pinned_curve = self._window._pinnedplot.curve
        self._pinned_channel = None

        self._slack_token = None
        self._slack_channel = None

        self._device_file_name = ''
        self._window.status_message('Initialization complete.')

    def window_to_tab(self, content):
        if content.objectName() == "plotting":
            self._plots_in_window = False

    def tab_to_window(self, content):
        if content.objectName() == "plotting":
            self._plots_in_window = True

    # ---- Server Communication ---- #
    def setup_communication_threads(self):
        """ Create gui/server pipe pair, start communicator """
        self._pipe_gui, pipe_server = Pipe()

        self._com_process = Process(target=query_server,
                                    args=(pipe_server, self._server_url, self.debug,))

        self._keep_communicating = True

        self._communicator = Communicator(self._pipe_gui, self._app)
        self._communicator.moveToThread(self._com_thread)
        self._com_thread.started.connect(self._communicator.communicate)
        self._communicator.sig_status.connect(self.on_communicator_status)
        self._communicator.sig_poll_rate.connect(self.on_communicator_poll_rate)
        self._communicator.sig_device_info.connect(self.on_communicator_device_info)
        self._com_thread.start()

        # Tell the query process the current polling rate:
        pipe_message = ["com_period", self._com_period]
        self._communicator.send_message(pipe_message)

        # Get initial device/channel list and send to query process:
        self.device_or_channel_changed()

        # Start the query process:
        self._com_process.start()

        # this thread auto retries device connection every 30 seconds
        self._retry_devices = True
        self._retry_thread = threading.Thread(target=self.update_device_retry_labels, args=())
        self._retry_thread.start()

    def shutdown_communication_threads(self):
        self._keep_communicating = False
        try:
            self._com_process.terminate()
            self._com_process.join()
        except AttributeError:
            # if process doesn't exist
            pass

        try:
            self._communicator.terminate()
            self._com_thread.quit()
        except AttributeError:
            pass

        self._retry_devices = False
        try:
            del self._retry_thread
        except AttributeError:
            pass

    # @pyqtSlot(str)
    def on_communicator_status(self, data: str):
        """ update status bar with thread message """
        self._window.status_message(data)

    # @pyqtSlot(float)
    def on_communicator_poll_rate(self, data: float):
        """ update polling rate in GUI """
        self._window.set_polling_rate('{0:.2f}'.format(data))

    # @pyqtSlot(dict)
    def on_communicator_device_info(self, data: dict):
        """ Read in message from the server, and update devices accordingly """
        parsed_response = data
        # print(parsed_response)

        for device_name, device in self._devices.items():
            device_id = device.device_id

            if device.locked or device_id not in parsed_response.keys():
                continue
            # if device_name == "naims":
            #     print("Working on naims is {}".format(device_id in parsed_response.keys()))

            # if "ERROR" in parsed_response[device_id]:
            if any(resp in parsed_response[device_id] for resp in ("ERROR", "TIMEOUT")):
                device.lock(message=parsed_response[device_id])
                if device not in self._locked_devices:
                    self._locked_devices.append(device)
                continue

            if device in self._locked_devices:
                self._locked_devices.remove(device)
                device.overview_widget.hide_error_message()

            try:
                timestamp = parsed_response[device_id]['timestamp']
                device.polling_rate = parsed_response[device_id]['polling_rate']
            except KeyError:
                # did not get a valid response or dict might be empty
                continue

            for channel_name, value in parsed_response[device_id].items():
                # metadata the server sent back that doesn't contain channel values
                if channel_name in ['timestamp', 'polling_rate']:
                    continue
                #
                # if device_name == "naims":
                #     print("Updating channel {} with value {}".format(channel_name, value))

                channel = device.get_channel_by_name(channel_name)
                if channel is None:
                    device.lock(message='Could not find channel with name {}.'.format(channel_name))
                    if device not in self._locked_devices:
                        self._locked_devices.append(device)
                    continue

                # Scale value back to channel
                channel.value = value / channel.scaling
                self.update_stored_values(device_name, channel_name, timestamp)

                # try:
                #     self.log_data(channel, timestamp)
                # except Exception as e:
                #     if self.debug:
                #         print("Exception '{}' caught while trying to log data.".format(e))

    # @pyqtSlot()
    def device_or_channel_changed(self):
        """ Sends a device changed request to the pipe """
        device_dict_list = [{
            'device_driver': device.driver,
            'device_id': device.device_id,
            'locked_by_server': False,
            'channel_ids': [name for name, mych in device.channels.items() if
                            mych.mode in ['read', 'both']],
            'precisions': [mych.precision for name, mych in device.channels.items() if
                           mych.mode in ['read', 'both']],
            'values': [None for name, mych in device.channels.items() if
                       mych.mode in ['read', 'both']],
            'data_types': [str(mych.data_type) for name, mych in device.channels.items() if
                           mych.mode in ['read', 'both']]
        }

            for device_name, device in self._devices.items()
            if not (device.locked or
                    len([name for name, mych in device.channels.items() if
                         mych.mode in ['read', 'both']]) == 0)]

        pipe_message = ["device_or_channel_changed", device_dict_list]

        try:
            self._communicator.send_message(pipe_message)
        except AttributeError:
            pass

    # @pyqtSlot(Channel, object)
    def set_value_callback(self, channel, val):
        """ Creates a SET message to send to server """
        # values = None
        if channel.data_type == float:
            values = val * channel.scaling
        else:
            values = float(val)
        if self.debug:
            print('Set value callback was called with widget {}, '
                  'type {}, and scaled value {}.'.format(channel,
                                                         channel.data_type,
                                                         channel.value))

        _data = {'device_driver': channel.parent_device.driver,
                 'device_id': channel.parent_device.device_id,
                 'locked_by_server': False,
                 'channel_ids': [channel.name],
                 'precisions': [None],
                 'values': [values],
                 'data_types': [str(channel.data_type)]}

        # Create a new thread that sends the set command to the server and
        # waits for an answer.
        new_set_thread = threading.Thread(target=self.update_device_on_server, args=(_data,))
        new_set_thread.start()

    def update_device_retry_labels(self):
        while self._retry_devices:
            tr = 30
            for i in range(tr):
                tr -= 1
                for device in self._locked_devices:
                    device.overview_widget.set_retry_label(tr)
                time.sleep(1)
                if not self._retry_devices:
                    return

            for device in self._locked_devices:
                device.unlock()
            self.device_or_channel_changed()

    def update_device_on_server(self, _data):
        """ Sends POST request to server with new device/channel info """
        _url = self._server_url + "device/set"
        try:
            _data = {'data': json.dumps(_data)}
            _r = requests.post(_url, data=_data)

            if _r.status_code == 200:
                print("Sending set command to server successful, response was: {}".format(_r.text))
            else:
                print("Sending set command to server unsuccessful, response-code was: {}".format(_r.status_code))
        except Exception as e:
            if self.debug:
                print("Exception '{}' caught while communicating with RasPi server.".format(e))

    # ---- Internal variable modifiers ----

    def update_stored_values(self, device_name, channel_name, timestamp):
        """ Update the value deques for each channel """
        ch = self._devices[device_name].channels[channel_name]

        # zero values will crash log scale plots
        if ch.value == 0:
            ch.value = 1e-20

        if len(ch.x_values) > 0:
            # only append new data (i.e. if we are polling faster than this
            # device can respond, might receive same point twice)
            if ch.x_values[-1] != timestamp:
                ch.append_data(timestamp, ch.value)
                if LOG_DATA:
                    self._data_logger.log_value(device_name, channel_name, ch.value, timestamp)
        else:
            ch.append_data(timestamp, ch.value)
            if LOG_DATA:
                self._data_logger.log_value(device_name, channel_name, ch.value, timestamp)

        # check basic procedures to see if we should activate them
        for name, procedure in self._procedures.items():
            if not isinstance(procedure, BasicProcedure):
                continue

            if not self._devices[device_name] in procedure.rule_devices():
                continue

            if procedure.should_perform_procedure():
                procedure.do_actions()

    # # @pyqtSlot(object)
    def connect_device_channel_entry_form(self, obj):
        """ Connects the new object's save and delete signals to the control system """
        try:
            obj.sig_entry_form_ok.disconnect()
        except TypeError:
            pass
        obj.sig_entry_form_ok.connect(self.on_device_channel_changed)

        try:
            obj.sig_delete.disconnect()
        except TypeError:
            pass
        obj.sig_delete.connect(self.on_device_channel_delete)

    # # @pyqtSlot(object)
    def on_device_channel_delete(self, obj):
        for procedure_name, procedure in self._procedures.items():
            used_devices, used_channels = procedure.devices_channels_used()
            if obj in used_devices | used_channels:
                self.show_ErrorDialog(
                    'Object is part of a procedure. Delete the procedure before deleting this object.')
                return

        ignored = self.show_WarningDialog('Delete objects at your own risk!')
        if ignored:
            if isinstance(obj, Device):
                del self._devices[obj.name]
            else:
                dev = self._devices[obj.parent_device.name]
                del dev.channels[obj.name]
                dev.update()

            self.update_gui_devices()
            self.device_or_channel_changed()
        print(obj)

    # @pyqtSlot(object, dict)
    def on_device_channel_changed(self, obj, vals):
        """ Called when user presses Save Changes button on the settings page.
            Gets passed an old device/channel and new values, or a new
            device/channel, and no values. """

        # make sure the object is not part of a procedure
        for procedure_name, procedure in self._procedures.items():
            used_devices, used_channels = procedure.devices_channels_used()
            if obj in used_devices | used_channels:
                ignored = self.show_WarningDialog(
                    'Object is part of a procedure. Delete the procedure before editing this object.')
                if not ignored:
                    obj.reset_entry_form()
                    return
                else:
                    # if part of multiple procedures, we only want to show this once
                    break

        if isinstance(obj, Device):

            name_in_use = vals['name'] in self._devices.keys()
            id_in_use = vals['device_id'] in [x.device_id for _, x in self._devices.items()]
            editing_device = obj in [x for _, x in self._devices.items()]

            if editing_device:
                if name_in_use and vals['name'] != obj.name:
                    self.show_ErrorDialog('Device name already in use.')
                    obj.reset_entry_form()
                    # attempting to change device name to a name in-use
                    return

                if id_in_use and vals['device_id'] != obj.device_id:
                    self.show_ErrorDialog('Device ID already in use.')
                    obj.reset_entry_form()
                    # attempting to change device_id to an id in-use
                    return

            else:
                # adding a new device
                if name_in_use or id_in_use:
                    self.show_ErrorDialog('Device name already in use.')
                    obj.reset_entry_form()
                    # attempting to add a device with name or id in-use
                    return

            for attr, val in vals.items():
                if attr == 'name' and editing_device:
                    self._devices[val] = self._devices.pop(obj.name)
                setattr(obj, attr, val)

            if not editing_device:
                self.add_device(obj)

        elif isinstance(obj, Channel):

            editing_channel = obj in [x for _, x in obj.parent_device.channels.items()]
            name_in_use = vals['name'] in obj.parent_device.channels.keys()

            if editing_channel:
                if name_in_use and vals['name'] != obj.name:
                    self.show_ErrorDialog('Channel name already in use.')
                    obj.reset_entry_form()
                    return
            else:
                if name_in_use:
                    self.show_ErrorDialog('Channel name already in use.')
                    obj.reset_entry_form()
                    return

            for attr, val in vals.items():
                if attr == 'name' and editing_channel:
                    self._devices[obj.parent_device.name].channels[val] = \
                        self._devices[obj.parent_device.name].channels.pop(obj.name)
                setattr(obj, attr, val)

            if not editing_channel:
                obj.parent_device.add_channel(obj)
                self.add_channel(obj)

        self.device_or_channel_changed()
        self.update_gui_devices()

    def add_device(self, device):
        """ Adds a device to the control system """
        if device.name in self._devices.keys():
            self.show_ErrorDialog('Device with the same name already loaded.')
            return False

        device.parent = self

        # Add device to the list of devices in the control system
        self._devices[device.name] = device

        if device.overview_order == -1:
            device.overview_order = min([x.overview_order for _, x in self._devices.items()]) - 1
        device.initialize()

        for chname, ch in device.channels.items():
            self.add_channel(ch)

        # if the device gets disconnected and reconnected, need to send a message to the server
        device.sig_update_server.connect(self.device_or_channel_changed)

        device.reset_entry_form()

        """
        # Add corresponding channels to the hdf5 log.
        for channel_name, channel in device.channels().items():
            if channel.mode() == "read" or channel.mode() == "both":
                self._data_logger.add_channel(channel)


        # Add the device to the settings page tree.
        device_iter = self._settings_page_tree_store.insert(None, (len(self._settings_page_tree_store) - 1),
                                                            [device.label(), "Device", "edit_device", device.name(),
                                                             device.name()])
        """

        # return true if successful
        return True

    def add_channel(self, channel):
        if channel.parent_device is None:
            print('Attempt to add channel with no parent device to gui')
            return

        # channel.initialize()

        channel._set_signal.connect(self.set_value_callback)
        channel._pin_signal.connect(self.set_pinned_plot_callback)
        channel._settings_signal.connect(self.set_plot_settings_callback)

        channel.reset_entry_form()

        channel.parent_device.update()

    # ---- GUI ---- #

    def update_gui_devices(self):
        """ Main update function to be called when a device is changed """
        for name, device in self._devices.items():
            device.update()
        self._window.update_overview(self._devices)
        self._window.update_device_settings(self._devices)
        self._window.update_plots(self._devices, self._plotted_channels)
        self._window.update_procedures(self._procedures)

    # # @pyqtSlot()
    def on_start_pause_click(self):
        btn = self._window.ui.btnStartPause
        if btn.text() == 'Start Polling':
            self.setup_communication_threads()
            self._plot_timer.start(50)
            btn.setText('Pause Polling')
            self._window.ui.btnStop_2.setEnabled(True)
        elif btn.text() == 'Pause Polling':
            self._communicator.send_message('pause_query', )
            self._keep_communicating = False
            self._communicator.isRunning = False
            self._plot_timer.stop()
            btn.setText('Resume Polling')
        else:
            self._communicator.send_message('pause_query', )
            self._keep_communicating = True
            self._communicator.isRunning = True
            self._plot_timer.start(50)
            btn.setText('Pause Polling')

    # # @pyqtSlot()
    def on_stop_click(self):
        # First we pause the polling like in the on_start_paus_click() function
        self._communicator.send_message('pause_query', )
        self._keep_communicating = False
        self._communicator.isRunning = False
        self._plot_timer.stop()

        # Then we shut it down
        self.shutdown_communication_threads()
        self._window.ui.btnStartPause.setText('Start Polling')
        self._window.ui.btnStop_2.setEnabled(False)
        for device in self._locked_devices:
            device.unlock()
            device.overview_widget.hide_error_message()
        self._locked_devices = []

        for device_name, device in self._devices.items():
            device.error_message = ''
            for channel_name, channel in device.channels.items():
                channel.clear_data()

        self.update_value_displays()
        # self._plotted_channels = {}
        # self.update_gui_devices()

    # @pyqtSlot()
    def update_value_displays(self):
        """ This function is called by a QTimer to ensure the GUI has a chance
            to get input. Handles updating of 'read' values on the overview
            page, and redraws plots if applicable """

        # update the pinned plot
        if self._pinned_channel is not None:
            self._pinned_curve.setData(self._pinned_channel.x_values,
                                       self._pinned_channel.y_values,
                                       clear=True, _callsync='off')

        if self._window.current_tab == 'main':
            # update read values on overview page
            for name, device in self._devices.items():
                for chname, channel in device.channels.items():
                    if channel.read_widget is None:
                        continue

                    if channel.data_type in [int, float]:
                        fmt = '{:' + channel.displayformat + '}'
                        val = str(fmt.format(channel.value))
                        channel.read_widget.setText(val)

        # If plot tab is in window mode, we would like it to update always...
        if self._window.current_tab == 'plots' or self._plots_in_window:

            # update the plotted channels
            for _, _ in self._devices.items():

                for channel in self._plotted_channels:
                    # pyqtgraph cant plot log of 0
                    channel._plot_curve.setData(channel.x_values, channel.y_values,
                                                clear=True, _callsync='off')

        self._app.processEvents()

    def reset_pinned_plot_callback(self):

        if self._pinned_channel is not None:

            x = self._window._gbpinnedplot.layout().itemAt(0).widget()
            x.settings = self._pinned_channel.plot_settings

    def set_pinned_plot_callback(self, device, channel):
        """ Set the pinned plot when a user pressed the plot's pin button """
        # click button emits (device, channel)
        self._pinned_channel = channel

        # update plot settings
        x = self._window._gbpinnedplot.layout().itemAt(0).widget()
        x.setLabel('left', '{} [{}]'.format(channel.label, channel.unit))
        x.settings = channel.plot_settings
        self._window._gbpinnedplot.setTitle('{}.{}'.format(device.label, channel.label))

    # @pyqtSlot(Channel)
    def set_plot_settings_callback(self, ch):
        """ Show the plot settings dialog when the user presses the plot's setting button """
        rng = ch._plot_widget.view_range
        ch._plot_settings['x']['min'] = rng[0][0]
        ch._plot_settings['x']['max'] = rng[0][1]
        ch._plot_settings['y']['min'] = rng[1][0]
        ch._plot_settings['y']['max'] = rng[1][1]

        _plotsettingsdialog = PlotSettingsDialog(ch)
        _plotsettingsdialog.exec_()

    def send_notification(self, notification_text):
        # Callback function to handle sending of notifications
        # This is in the main GUI loop so we can centrally update slack token
        # and channel without communicating with the procedure threads. They
        # just emit a signal to trigger this function here.
        # :param: notification text
        if self._slack_channel is not None and self._slack_token is not None:

            _sc = SlackClient(self._slack_token)
            ret_data = _sc.api_call("chat.postMessage", channel="mist-1-alarms", text=notification_text)
            print(ret_data)

            if not ret_data["ok"]:

                self._window.status_message("A procedure was triggered, but failed to send a Slack message.")

        else:

            self._window.status_message("A procedure was triggered, but no Slack token and channel were specified.")

    def show_slack_dialog(self):
        # Open the slack dialog to get the slack token
        _slackdialog = SlackDialog(self._slack_token, self._slack_channel)
        success, ret_data = _slackdialog.exec_()

        # Note that SlackDialog does the checking for us, it will return
        # None for ret_data if it couldn't communicate with Slack
        # On Cancel (success==0) we do nothing.
        if success:

            if ret_data is not None:

                self._window.status_message("Slack token and channel were updated.")
                self._slack_token = ret_data["token"]
                self._slack_channel = ret_data["channel_id"]

            else:

                self._window.status_message("Slack token and channel were reset to None.")
                self._slack_token = None
                self._slack_channel = None

        print(self._slack_token)

    # # @pyqtSlot()
    def on_quit_button(self):
        # First we pause the polling like in the on_start_paus_click() function
        if self._communicator is not None:
            self._communicator.send_message('pause_query', )
            self._keep_communicating = False
            self._communicator.isRunning = False
            self._plot_timer.stop()

        # Then we shut down communication threads
        self.shutdown_communication_threads()
        self._window.close()

    # ---- dialogs ---- #
    def on_save_button(self):
        if self._device_file_name == '':
            fileName, _ = QFileDialog.getSaveFileName(self._window,
                                                      "Save Session as JSON", "", "Text Files (*.txt)")

            if fileName == '':
                return

            if fileName[-4:] != '.txt':
                fileName += '.txt'

            self._device_file_name = fileName

        with open(self._device_file_name, 'w') as f:
            devdict = {}
            for device_name, device in self._devices.items():
                devdict[device_name] = device.get_json()

            procdict = {}
            for proc_name, procedure in self._procedures.items():
                procdict[proc_name] = procedure.json

            winsettingsdict = self._window.current_settings()

            chpinname = None
            devpinname = None
            if self._pinned_channel is not None:
                chpinname = self._pinned_channel.name
                devpinname = self._pinned_channel.parent_device.name

            cssettingsdict = {
                'pinned-channel': chpinname,
                'pinned-device': devpinname,
                'plotted-channels': [(x.name, x.parent_device.name) for
                                     x in self._plotted_channels]}

            # TODO: I don't like saving the Slack token in plain text json! -DW
            slacksettingsdict = {'token': self._slack_token,
                                 'channel': self._slack_channel}

            output = {
                'devices': devdict,
                'procedures': procdict,
                'window-settings': winsettingsdict,
                'control-system-settings': cssettingsdict,
                'slack-settings': slacksettingsdict
            }

            json.dump(output, f, sort_keys=True, indent=4, separators=(', ', ': '))

        self._window.status_message('Saved session to {}.'.format(self._device_file_name))

    def on_save_as_button(self):
        _fn, _ = QFileDialog.getSaveFileName(self._window,
                                             "Save Session as JSON", "", "Text Files (*.txt)")

        if _fn == '':
            return

        if _fn[-4:] != '.txt':
            _fn += '.txt'

        self._device_file_name = _fn

        self.on_save_button()

    def handle_print(self):

        printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
        dialog = QtPrintSupport.QPrintDialog(printer, self._window)

        if dialog.exec_() == QtPrintSupport.QPrintDialog.Accepted:
            self.assemble_print_text()
            self._print_editor.document().print_(dialog.printer())
            self._window.status_message("Sent summary document to printer {}".format(dialog.printer().printerName()))

    def handle_preview(self):

        self.assemble_print_text()

        dialog = QtPrintSupport.QPrintPreviewDialog()
        dialog.paintRequested.connect(self._print_editor.print_)
        if dialog.exec_() == QtPrintSupport.QPrintPreviewDialog.Accepted:
            self._window.status_message("Sent summary document to printer {}".format(dialog.printer().printerName()))

    def assemble_print_text(self):
        # Query Devices for information
        self._print_editor.setText("")
        self._print_editor.setFontUnderline(True)
        self._print_editor.setFontPointSize(14)
        self._print_editor.setText("{} summary from {}:"
                                   "\n".format(self._title, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
        self._print_editor.moveCursor(QTextCursor.End)
        self._print_editor.setFontUnderline(False)
        self._print_editor.setFontPointSize(12)

        text = ""
        for _, _dev in self._devices.items():

            text += "\nDevice {}:\n".format(_dev.label)

            for _, _cha in _dev.channels.items():

                text += _cha.get_print_str()

        self._print_editor.insertPlainText(text)

    # # @pyqtSlot()
    def on_load_button(self):
        successes = 0

        filename, _ = QFileDialog.getOpenFileName(self._window,
                                                  "Load session from JSON", "", "Text Files (*.txt)")

        if filename == '':
            return

        # load_from_csv is a homebrewed function in FileOps.py
        res = load_from_csv(filename)

        if res is None:
            self._window.status_message('Unable to read JSON.')
            return
        else:

            devices, procedures, winsettings, cssettings, slack_settings = res

            self._slack_token = slack_settings["token"]
            self._slack_channel = slack_settings["channel"]

        # Add devices and procedures
        for _, dev in devices.items():
            if self.add_device(dev):
                successes += 1

        for _, proc in procedures.items():
            self.add_procedure(proc)

        # Load control system settings
        self._window.apply_settings(winsettings)
        self.apply_settings(cssettings)

        # Test slack client
        if self._slack_token is not None:
            _sc = SlackClient(self._slack_token)
            test_data = _sc.api_call("api.test")
            if not test_data['ok']:
                self._window.status_message("Testing Slack token: Fail!, Error: {}. "
                                            "Token and channel deleted.".format(test_data['error']))
                self._slack_token = None
                self._slack_channel = None
            else:
                test_data = _sc.api_call("channels.info", channel=self._slack_channel)
                if not test_data['ok']:
                    self._window.status_message("Testing Slack channel '{}': Fail!, Error: {}. "
                                                "Channel deleted (token worked.)".format(self._slack_channel,
                                                                                         test_data['error']))
                    self._slack_channel = None

        if successes > 0:
            self._device_file_name = filename
            self.update_gui_devices()
            self._window.status_message('Loaded {} devices from JSON.'.format(successes))

    # # @pyqtSlot()
    def show_PlotChooseDialog(self):
        _plotchoosedialog = PlotChooseDialog(self._devices, self._plotted_channels)
        accept, chs = _plotchoosedialog.exec_()
        self._plotted_channels = chs
        self.update_gui_devices()

    def add_procedure(self, procedure):

        self._procedures[procedure.name] = procedure
        procedure.signal_edit.connect(self.edit_procedure)
        procedure.signal_delete.connect(self.delete_procedure)

        if isinstance(procedure, PidProcedure):
            procedure.set_signal.connect(self.set_value_callback)

        if isinstance(procedure, BasicProcedure):
            procedure.set_signal.connect(self.set_value_callback)
            procedure.send_notification_signal.connect(self.send_notification)
            # connect emergency stop button signal
            if procedure.triggertype == 'emstop':
                f = lambda: procedure.do_actions()
                self._window.ui.btnStop.clicked.connect(f)
                self._emergency_stop_signals[procedure.name] = f

        procedure.initialize()

    def edit_procedure(self, proc):
        self.show_ProcedureDialog(False, proc=proc)

    def delete_procedure(self, proc):
        # unbind PyQtSlot from emergency stop button signal
        if isinstance(proc, BasicProcedure):
            if proc.triggertype == 'emstop':
                self._window.ui.btnStop.clicked.disconnect(self._emergency_stop_signals[proc.name])
                del self._emergency_stop_signals[proc.name]
      
        del self._procedures[proc.name]
        self._window.update_procedures(self._procedures)

    # # @pyqtSlot()
    # # @pyqtSlot(Procedure)
    def show_ProcedureDialog(self, dummy_bool, proc=None):
        _proceduredialog = ProcedureDialog(self._devices, self._procedures.keys(), proc)
        accept, rproc = _proceduredialog.exec_()

        if rproc is not None:
            if proc is not None:
                # if we edited a procedure delete the old version before adding the new one
                self.delete_procedure(proc)

            self.add_procedure(rproc)
            self._window.update_procedures(self._procedures)

    # @pyqtSlot()
    def show_ErrorDialog(self, error_message='Error'):
        _errordialog = ErrorDialog(error_message)
        _errordialog.exec_()
        self.update_gui_devices()

    def show_WarningDialog(self, warning_message='Warning'):
        _warningdialog = WarningDialog(warning_message)
        userignored = _warningdialog.exec_()

        return userignored

    # ---- Other functions ---- #
    def apply_settings(self, settings):
        if settings == {}:
            return

        if settings['pinned-device'] is not None:
            dev = self._devices[settings['pinned-device']]
            ch = dev.channels[settings['pinned-channel']]
            self._pinned_channel = ch
            self.set_pinned_plot_callback(dev, ch)

        for item in settings['plotted-channels']:
            self._plotted_channels.append(self._devices[item[1]].channels[item[0]])

    def run(self):
        # self.setup_communication_threads()
        self.update_gui_devices()
        self._window.show()
Esempio n. 12
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. 13
0
class WeatherStationBrowser(QWidget):
    """
    Widget that allows the user to browse and select ECCC climate stations.
    """

    ConsoleSignal = QSignal(str)
    staListSignal = QSignal(list)

    PROV_NAME = [x[0].title() for x in PROV_NAME_ABB]
    PROV_ABB = [x[1] for x in PROV_NAME_ABB]

    def __init__(self, parent=None):
        super(WeatherStationBrowser, self).__init__(parent)
        self.stn_finder_worker = WeatherStationFinder()
        self.stn_finder_worker.sig_load_database_finished.connect(
                self.receive_load_database)
        self.stn_finder_thread = QThread()
        self.stn_finder_worker.moveToThread(self.stn_finder_thread)

        self.station_table = WeatherSationView()
        self.waitspinnerbar = WaitSpinnerBar()
        self.stn_finder_worker.sig_progress_msg.connect(
                self.waitspinnerbar.set_label)
        self.__initUI__()

        self.start_load_database()

    def __initUI__(self):
        self.setWindowTitle('Weather Stations Browser')
        self.setWindowIcon(icons.get_icon('master'))
        self.setWindowFlags(Qt.Window)

        now = datetime.now()

        # ---- Tab Widget Search

        # ---- Proximity filter groupbox

        label_Lat = QLabel('Latitude :')
        label_Lat2 = QLabel('North')

        self.lat_spinBox = QDoubleSpinBox()
        self.lat_spinBox.setAlignment(Qt.AlignCenter)
        self.lat_spinBox.setSingleStep(0.1)
        self.lat_spinBox.setValue(0)
        self.lat_spinBox.setMinimum(0)
        self.lat_spinBox.setMaximum(180)
        self.lat_spinBox.setSuffix(u' °')
        self.lat_spinBox.valueChanged.connect(self.proximity_grpbox_toggled)

        label_Lon = QLabel('Longitude :')
        label_Lon2 = QLabel('West')

        self.lon_spinBox = QDoubleSpinBox()
        self.lon_spinBox.setAlignment(Qt.AlignCenter)
        self.lon_spinBox.setSingleStep(0.1)
        self.lon_spinBox.setValue(0)
        self.lon_spinBox.setMinimum(0)
        self.lon_spinBox.setMaximum(180)
        self.lon_spinBox.setSuffix(u' °')
        self.lon_spinBox.valueChanged.connect(self.proximity_grpbox_toggled)

        self.radius_SpinBox = QComboBox()
        self.radius_SpinBox.addItems(['25 km', '50 km', '100 km', '200 km'])
        self.radius_SpinBox.currentIndexChanged.connect(
                self.search_filters_changed)

        prox_search_grid = QGridLayout()
        row = 0
        prox_search_grid.addWidget(label_Lat, row, 1)
        prox_search_grid.addWidget(self.lat_spinBox, row, 2)
        prox_search_grid.addWidget(label_Lat2, row, 3)
        row += 1
        prox_search_grid.addWidget(label_Lon, row, 1)
        prox_search_grid.addWidget(self.lon_spinBox, row, 2)
        prox_search_grid.addWidget(label_Lon2, row, 3)
        row += 1
        prox_search_grid.addWidget(QLabel('Search Radius :'), row, 1)
        prox_search_grid.addWidget(self.radius_SpinBox, row, 2)

        prox_search_grid.setColumnStretch(0, 100)
        prox_search_grid.setColumnStretch(4, 100)
        prox_search_grid.setRowStretch(row+1, 100)
        prox_search_grid.setHorizontalSpacing(20)
        prox_search_grid.setContentsMargins(10, 10, 10, 10)  # (L, T, R, B)

        self.prox_grpbox = QGroupBox("Proximity filter :")
        self.prox_grpbox.setCheckable(True)
        self.prox_grpbox.setChecked(False)
        self.prox_grpbox.toggled.connect(self.proximity_grpbox_toggled)
        self.prox_grpbox.setLayout(prox_search_grid)

        # ---- Province filter

        prov_names = ['All']
        prov_names.extend(self.PROV_NAME)
        self.prov_widg = QComboBox()
        self.prov_widg.addItems(prov_names)
        self.prov_widg.setCurrentIndex(0)
        self.prov_widg.currentIndexChanged.connect(self.search_filters_changed)

        layout = QGridLayout()
        layout.addWidget(self.prov_widg, 2, 1)
        layout.setColumnStretch(2, 100)
        layout.setVerticalSpacing(10)

        prov_grpbox = QGroupBox("Province filter :")
        prov_grpbox.setLayout(layout)

        # ---- Data availability filter

        # Number of years with data

        self.nbrYear = QSpinBox()
        self.nbrYear.setAlignment(Qt.AlignCenter)
        self.nbrYear.setSingleStep(1)
        self.nbrYear.setMinimum(0)
        self.nbrYear.setValue(3)
        self.nbrYear.valueChanged.connect(self.search_filters_changed)

        subgrid1 = QGridLayout()
        subgrid1.addWidget(self.nbrYear, 0, 0)
        subgrid1.addWidget(QLabel('years of data between'), 0, 1)

        subgrid1.setHorizontalSpacing(10)
        subgrid1.setContentsMargins(0, 0, 0, 0)  # (L, T, R, B)
        subgrid1.setColumnStretch(2, 100)

        # Year range

        self.minYear = QSpinBox()
        self.minYear.setAlignment(Qt.AlignCenter)
        self.minYear.setSingleStep(1)
        self.minYear.setMinimum(1840)
        self.minYear.setMaximum(now.year)
        self.minYear.setValue(1840)
        self.minYear.valueChanged.connect(self.minYear_changed)

        label_and = QLabel('and')
        label_and.setAlignment(Qt.AlignCenter)

        self.maxYear = QSpinBox()
        self.maxYear.setAlignment(Qt.AlignCenter)
        self.maxYear.setSingleStep(1)
        self.maxYear.setMinimum(1840)
        self.maxYear.setMaximum(now.year)
        self.maxYear.setValue(now.year)
        self.maxYear.valueChanged.connect(self.maxYear_changed)

        subgrid2 = QGridLayout()
        subgrid2.addWidget(self.minYear, 0, 0)
        subgrid2.addWidget(label_and, 0, 1)
        subgrid2.addWidget(self.maxYear, 0, 2)

        subgrid2.setHorizontalSpacing(10)
        subgrid2.setContentsMargins(0, 0, 0, 0)  # (L, T, R, B)
        subgrid2.setColumnStretch(4, 100)

        # Subgridgrid assembly

        grid = QGridLayout()

        grid.addWidget(QLabel('Search for stations with at least'), 0, 0)
        grid.addLayout(subgrid1, 1, 0)
        grid.addLayout(subgrid2, 2, 0)

        grid.setVerticalSpacing(5)
        grid.setRowStretch(0, 100)
        # grid.setContentsMargins(0, 0, 0, 0)  # (L, T, R, B)

        self.year_widg = QGroupBox("Data Availability filter :")
        self.year_widg.setLayout(grid)

        # ---- Toolbar

        self.btn_addSta = btn_addSta = QPushButton('Add')
        btn_addSta.setIcon(icons.get_icon('add2list'))
        btn_addSta.setIconSize(icons.get_iconsize('small'))
        btn_addSta.setToolTip('Add selected found weather stations to the '
                              'current list of weather stations.')
        btn_addSta.clicked.connect(self.btn_addSta_isClicked)

        btn_save = QPushButton('Save')
        btn_save.setIcon(icons.get_icon('save'))
        btn_save.setIconSize(icons.get_iconsize('small'))
        btn_save.setToolTip('Save current found stations info in a csv file.')
        btn_save.clicked.connect(self.btn_save_isClicked)

        self.btn_fetch = btn_fetch = QPushButton('Fetch')
        btn_fetch.setIcon(icons.get_icon('refresh'))
        btn_fetch.setIconSize(icons.get_iconsize('small'))
        btn_fetch.setToolTip("Updates the climate station database by"
                             " fetching it again from the ECCC ftp server.")
        btn_fetch.clicked.connect(self.btn_fetch_isClicked)

        toolbar_grid = QGridLayout()
        toolbar_widg = QWidget()

        for col, btn in enumerate([btn_addSta, btn_save, btn_fetch]):
            toolbar_grid.addWidget(btn, 0, col+1)

        toolbar_grid.setColumnStretch(toolbar_grid.columnCount(), 100)
        toolbar_grid.setSpacing(5)
        toolbar_grid.setContentsMargins(0, 30, 0, 0)  # (L, T, R, B)

        toolbar_widg.setLayout(toolbar_grid)

        # ---- Left Panel

        panel_title = QLabel('<b>Weather Station Search Criteria :</b>')

        left_panel = QFrame()
        left_panel_grid = QGridLayout()

        left_panel_grid.addWidget(panel_title, 0, 0)
        left_panel_grid.addWidget(self.prox_grpbox, 1, 0)
        left_panel_grid.addWidget(prov_grpbox, 2, 0)
        left_panel_grid.addWidget(self.year_widg, 3, 0)
        left_panel_grid.setRowStretch(4, 100)
        left_panel_grid.addWidget(toolbar_widg, 5, 0)

        left_panel_grid.setVerticalSpacing(20)
        left_panel_grid.setContentsMargins(0, 0, 0, 0)   # (L, T, R, B)
        left_panel.setLayout(left_panel_grid)

        # ----- Main grid

        # Widgets

        vLine1 = QFrame()
        vLine1.setFrameStyle(StyleDB().VLine)

        # Grid

        main_layout = QGridLayout(self)

        main_layout.addWidget(left_panel, 0, 0)
        main_layout.addWidget(vLine1, 0, 1)
        main_layout.addWidget(self.station_table, 0, 2)
        main_layout.addWidget(self.waitspinnerbar, 0, 2)

        main_layout.setContentsMargins(10, 10, 10, 10)  # (L,T,R,B)
        main_layout.setRowStretch(0, 100)
        main_layout.setHorizontalSpacing(15)
        main_layout.setVerticalSpacing(5)
        main_layout.setColumnStretch(col, 100)

    @property
    def stationlist(self):
        return self.station_table.get_stationlist()

    @property
    def search_by(self):
        return ['proximity', 'province'][self.tab_widg.currentIndex()]

    @property
    def prov(self):
        if self.prov_widg.currentIndex() == 0:
            return self.PROV_ABB
        else:
            return self.PROV_ABB[self.prov_widg.currentIndex()-1]

    @property
    def lat(self):
        return self.lat_spinBox.value()

    def set_lat(self, x, silent=True):
        if silent:
            self.lat_spinBox.blockSignals(True)
        self.lat_spinBox.setValue(x)
        self.lat_spinBox.blockSignals(False)
        self.proximity_grpbox_toggled()

    @property
    def lon(self):
        return self.lon_spinBox.value()

    def set_lon(self, x, silent=True):
        if silent:
            self.lon_spinBox.blockSignals(True)
        self.lon_spinBox.setValue(x)
        self.lon_spinBox.blockSignals(False)
        self.proximity_grpbox_toggled()

    @property
    def rad(self):
        return int(self.radius_SpinBox.currentText()[:-3])

    @property
    def prox(self):
        if self.prox_grpbox.isChecked():
            return (self.lat, -self.lon, self.rad)
        else:
            return None

    @property
    def year_min(self):
        return int(self.minYear.value())

    def set_yearmin(self, x, silent=True):
        if silent:
            self.minYear.blockSignals(True)
        self.minYear.setValue(x)
        self.minYear.blockSignals(False)

    @property
    def year_max(self):
        return int(self.maxYear.value())

    def set_yearmax(self, x, silent=True):
        if silent:
            self.maxYear.blockSignals(True)
        self.maxYear.setValue(x)
        self.maxYear.blockSignals(False)

    @property
    def nbr_of_years(self):
        return int(self.nbrYear.value())

    def set_yearnbr(self, x, silent=True):
        if silent:
            self.nbrYear.blockSignals(True)
        self.nbrYear.setValue(x)
        self.nbrYear.blockSignals(False)

    # ---- Weather Station Finder Handlers

    def start_load_database(self, force_fetch=False):
        """Start the process of loading the climate station database."""
        if self.stn_finder_thread.isRunning():
            return

        self.station_table.clear()
        self.waitspinnerbar.show()

        # Start the downloading process.
        if force_fetch:
            self.stn_finder_thread.started.connect(
                    self.stn_finder_worker.fetch_database)
        else:
            self.stn_finder_thread.started.connect(
                    self.stn_finder_worker.load_database)
        self.stn_finder_thread.start()

    @QSlot()
    def receive_load_database(self):
        """Handles when loading the database is finished."""
        # Disconnect the thread.
        self.stn_finder_thread.started.disconnect()

        # Quit the thread.
        self.stn_finder_thread.quit()
        waittime = 0
        while self.stn_finder_thread.isRunning():
            sleep(0.1)
            waittime += 0.1
            if waittime > 15:                                # pragma: no cover
                print("Unable to quit the thread.")
                break
        # Force an update of the GUI.
        self.proximity_grpbox_toggled()
        if self.stn_finder_worker.data is None:
            self.waitspinnerbar.show_warning_icon()
        else:
            self.waitspinnerbar.hide()

    # ---- GUI handlers

    def show(self):
        super(WeatherStationBrowser, self).show()
        qr = self.frameGeometry()
        if self.parent():
            parent = self.parent()
            wp = parent.frameGeometry().width()
            hp = parent.frameGeometry().height()
            cp = parent.mapToGlobal(QPoint(wp/2, hp/2))
        else:
            cp = QDesktopWidget().availableGeometry().center()

        qr.moveCenter(cp)
        self.move(qr.topLeft())

    # -------------------------------------------------------------------------

    def minYear_changed(self):
        min_yr = min_yr = max(self.minYear.value(), 1840)

        now = datetime.now()
        max_yr = now.year

        self.maxYear.setRange(min_yr, max_yr)
        self.search_filters_changed()

    def maxYear_changed(self):
        min_yr = 1840

        now = datetime.now()
        max_yr = min(self.maxYear.value(), now.year)

        self.minYear.setRange(min_yr, max_yr)
        self.search_filters_changed()

    # ---- Toolbar Buttons Handlers

    def btn_save_isClicked(self):
        ddir = os.path.join(os.getcwd(), 'weather_station_list.csv')
        filename, ftype = QFileDialog().getSaveFileName(
                self, 'Save normals', ddir, '*.csv;;*.xlsx;;*.xls')
        self.station_table.save_stationlist(filename)

    def btn_addSta_isClicked(self):
        rows = self.station_table.get_checked_rows()
        if len(rows) > 0:
            staList = self.station_table.get_content4rows(rows)
            self.staListSignal.emit(staList)
            print('Selected stations sent to list')
        else:
            print('No station currently selected')

    def btn_fetch_isClicked(self):
        """Handles when the button fetch is clicked."""
        self.start_load_database(force_fetch=True)

    # ---- Search Filters Handlers

    def proximity_grpbox_toggled(self):
        """
        Set the values for the reference geo coordinates that are used in the
        WeatherSationView to calculate the proximity values and forces a
        refresh of the content of the table.
        """
        if self.prox_grpbox.isChecked():
            self.station_table.set_geocoord((self.lat, -self.lon))
        else:
            self.station_table.set_geocoord(None)
        self.search_filters_changed()

    def search_filters_changed(self):
        """
        Search for weather stations with the current filter values and forces
        an update of the station table content.
        """
        if self.stn_finder_worker.data is not None:
            stnlist = self.stn_finder_worker.get_stationlist(
                    prov=self.prov, prox=self.prox,
                    yrange=(self.year_min, self.year_max, self.nbr_of_years))
            self.station_table.populate_table(stnlist)
Esempio n. 14
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. 15
0
class FileLoader:
    def __init__(self, main_window, tab_data_table):
        super(FileLoader, self).__init__()
        self.tab_data_table = tab_data_table
        self.main_window = main_window

    # def __del__(self):
    #     logging.info('Destructor call check to ensure it fires after complete execution')

    @staticmethod
    def is_csv_file(file_extension):
        return file_extension.lower() == ".csv"

    @staticmethod
    def is_excel_file(file_extension):
        return file_extension.lower() == ".xlsx"

    def load_csv(self):
        """
                Loads the file from file selector to a table
        """

        loaded_file_path = QFileDialog.getOpenFileName(
            self.main_window, "Load File", "",
            'csv or xlxs(*.csv *.xlsx);;CSV(*.csv);; XLSX(*.xlsx)')

        filename, self.file_extension = os.path.splitext(loaded_file_path[0])

        # Proceed if and only if a valid file is selected and the file dialog is not cancelled
        if loaded_file_path[0]:
            # Get only the file name from path. eg. 'data_file.csv'
            filepath = os.path.normpath(loaded_file_path[0])
            filename = filepath.split(os.sep)
            self.csv_file_name = filename[-1]

            self.initUI()

            self.worker = Worker(file_path=loaded_file_path,
                                 file_extension=self.file_extension)

            self.thread = QThread()
            self.worker.current_progress.connect(self.set_progress_value)
            self.worker.read_values.connect(self.update_table_values)

            self.worker.moveToThread(self.thread)

            self.worker.max_progress_value.connect(self.set_max_progress_value)
            self.worker.finished.connect(self.task_finished)

            self.thread.started.connect(self.worker.load_file)

            self.thread.start()

            QApplication.restoreOverrideCursor()

    def update_table_values(self, row, col, value):
        item = QTableWidgetItem(value)
        self.tab_data_table.setItem(row, col, item)

    def initUI(self):
        # # Show waiting cursor till the time file is being processed
        QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)

        self.loading_progress = QProgressDialog("Reading Rows. Please wait...",
                                                None, 0, 0, self.main_window)

        if self.is_csv_file(self.file_extension):
            self.loading_progress.setWindowTitle("Loading CSV File...")
        elif self.is_excel_file(self.file_extension):
            self.loading_progress.setWindowTitle("Loading XLSX File...")

        self.loading_progress.setCancelButton(None)

        # enable custom window hint
        self.loading_progress.setWindowFlags(
            self.loading_progress.windowFlags()
            | QtCore.Qt.CustomizeWindowHint)
        # disable (but not hide) close button
        self.loading_progress.setWindowFlags(
            self.loading_progress.windowFlags()
            & ~QtCore.Qt.WindowCloseButtonHint)

        self.loading_progress.show()

    def set_progress_value(self, val):
        self.loading_progress.setValue(val)

    def set_max_progress_value(self, val):
        # Set the maximum progress for progressbar (fetch values from signal)
        self.loading_progress.setMaximum(val)
        self.loading_progress.setValue(0)

    def task_finished(self):
        logging.info("on_task_finish_called")
        # Stretch to fill the column width according to content
        # self.tab_data_table.resizeColumnsToContents()

        # Change the cursor back to normal
        QApplication.restoreOverrideCursor()

        # Properly manage quiting the thread
        self.thread.quit()
        # Wait for it to cleanup
        self.thread.wait()
        # Remove pointer to the current FileLoader so it can be GCed
        self.tab_data_table.setProperty("file_pointer", None)
Esempio n. 16
0
class Pireal(QMainWindow):

    """
    Main Window class

    This class is responsible for installing all application services.
    """

    __SERVICES = {}
    __ACTIONS = {}

    # The name of items is the connection text
    TOOLBAR_ITEMS = [
        'create_database',
        'open_database',
        'save_database',
        '',  # Is a separator!
        'new_query',
        'open_query',
        'save_query',
        '',
        'undo_action',
        'redo_action',
        'cut_action',
        'copy_action',
        'paste_action',
        '',
        'create_new_relation',
        'remove_relation',
        'edit_relation',
        '',
        'execute_queries'
    ]

    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle(self.tr("Pireal"))
        self.setMinimumSize(700, 500)

        # Load window geometry
        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        window_maximized = qsettings.value('window_max', True, type=bool)
        if window_maximized:
            self.showMaximized()
        else:
            size = qsettings.value('window_size')
            self.resize(size)
            position = qsettings.value('window_pos')
            self.move(position)
        # Toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setIconSize(QSize(22, 22))
        self.toolbar.setMovable(False)
        self.addToolBar(self.toolbar)

        # Menu bar
        menubar = self.menuBar()
        self.__load_menubar(menubar)
        # Load notification widget after toolbar actions
        notification_widget = Pireal.get_service("notification")
        self.toolbar.addWidget(notification_widget)
        # Message error
        self._msg_error_widget = message_error.MessageError(self)
        # Central widget
        central_widget = Pireal.get_service("central")
        central_widget.databaseSaved.connect(notification_widget.show_text)
        central_widget.querySaved.connect(notification_widget.show_text)
        self.setCentralWidget(central_widget)
        central_widget.add_start_page()

        # Check for updates
        self._thread = QThread()
        self._updater = updater.Updater()
        self._updater.moveToThread(self._thread)
        self._thread.started.connect(self._updater.check_updates)
        self._updater.finished.connect(self.__on_thread_update_finished)
        self._thread.start()
        notification_widget.show_text(
            self.tr("Checking for updates..."), time_out=0)

        # Install service
        Pireal.load_service("pireal", self)

    @classmethod
    def get_service(cls, service):
        """ Return the instance of a loaded service """

        return cls.__SERVICES.get(service, None)

    @classmethod
    def load_service(cls, name, instance):
        """ Load a service providing the service name and the instance """

        cls.__SERVICES[name] = instance

    @classmethod
    def get_action(cls, name):
        """ Return the instance of a loaded QAction """

        return cls.__ACTIONS.get(name, None)

    @classmethod
    def load_action(cls, name, action):
        """ Load a QAction """

        cls.__ACTIONS[name] = action

    def __load_menubar(self, menubar):
        """
        This method installs the menubar and toolbar, menus and QAction's,
        also connects to a slot each QAction.
        """

        from src.gui import menu_actions
        from src import keymap

        # Keymap
        kmap = keymap.KEYMAP
        # Toolbar items
        toolbar_items = {}

        central = Pireal.get_service("central")

        # Load menu bar
        for item in menu_actions.MENU:
            menubar_item = menu_actions.MENU[item]
            menu_name = menubar_item['name']
            items = menubar_item['items']
            menu = menubar.addMenu(menu_name)
            for menu_item in items:
                if isinstance(menu_item, str):
                    # Is a separator
                    menu.addSeparator()
                else:
                    action = menu_item['name']
                    obj, connection = menu_item['slot'].split(':')
                    if obj.startswith('central'):
                        obj = central
                    else:
                        obj = self
                    qaction = menu.addAction(action)
                    # Icon name is connection
                    icon = QIcon(":img/%s" % connection)
                    qaction.setIcon(icon)

                    # Install shortcuts
                    shortcut = kmap.get(connection, None)
                    if shortcut is not None:
                        qaction.setShortcut(shortcut)

                    # Items for toolbar
                    if connection in Pireal.TOOLBAR_ITEMS:
                        toolbar_items[connection] = qaction

                    # The name of QAction is the connection
                    Pireal.load_action(connection, qaction)
                    slot = getattr(obj, connection, None)
                    if isinstance(slot, Callable):
                        qaction.triggered.connect(slot)

        # Install toolbar
        self.__install_toolbar(toolbar_items)
        # Disable some actions
        self.set_enabled_db_actions(False)
        self.set_enabled_relation_actions(False)
        self.set_enabled_query_actions(False)
        self.set_enabled_editor_actions(False)

    def __install_toolbar(self, toolbar_items):
        for action in Pireal.TOOLBAR_ITEMS:
            qaction = toolbar_items.get(action, None)
            if qaction is not None:
                self.toolbar.addAction(qaction)
            else:
                self.toolbar.addSeparator()

    def __show_status_message(self, msg):
        status = Pireal.get_service("status")
        status.show_message(msg)

    def __on_thread_update_finished(self):
        self._thread.quit()
        # Clear notificator
        notification_widget = Pireal.get_service("notification")
        notification_widget.clear()

        msg = QMessageBox(self)
        if not self._updater.error:
            if self._updater.version:
                version = self._updater.version
                msg.setWindowTitle(self.tr("New version available!"))
                msg.setText(self.tr("Check the web site to "
                                    "download <b>Pireal {}</b>".format(
                                        version)))
                download_btn = msg.addButton(self.tr("Download!"),
                                             QMessageBox.YesRole)
                msg.addButton(self.tr("Cancel"),
                              QMessageBox.RejectRole)
                msg.exec_()
                r = msg.clickedButton()
                if r == download_btn:
                    webbrowser.open_new(
                        "http://centaurialpha.github.io/pireal")
        self._thread.deleteLater()
        self._updater.deleteLater()

    def change_title(self, title):
        self.setWindowTitle("Pireal " + '[' + title + ']')

    def set_enabled_db_actions(self, value):
        """ Public method. Enables or disables db QAction """

        actions = [
            'new_query',
            'open_query',
            'close_database',
            'save_database',
            'save_database_as',
            'load_relation'
        ]

        for action in actions:
            qaction = Pireal.get_action(action)
            qaction.setEnabled(value)

    def set_enabled_relation_actions(self, value):
        """ Public method. Enables or disables relation's QAction """

        actions = [
            'create_new_relation',
            'remove_relation',
            'edit_relation'
        ]

        for action in actions:
            qaction = Pireal.get_action(action)
            qaction.setEnabled(value)

    def set_enabled_query_actions(self, value):
        """ Public method. Enables or disables queries QAction """

        actions = [
            'execute_queries',
            'save_query'
        ]

        for action in actions:
            qaction = Pireal.get_action(action)
            qaction.setEnabled(value)

    def set_enabled_editor_actions(self, value):
        """ Public slot. Enables or disables editor actions """

        actions = [
            'undo_action',
            'redo_action',
            'copy_action',
            'cut_action',
            'paste_action',
            'zoom_in',
            'zoom_out',
            'comment',
            'uncomment'
        ]

        for action in actions:
            qaction = Pireal.get_action(action)
            qaction.setEnabled(value)

    def about_qt(self):
        """ Show about qt dialog """

        QMessageBox.aboutQt(self)

    def about_pireal(self):
        """ Show the bout Pireal dialog """

        from src.gui.dialogs import about_dialog
        dialog = about_dialog.AboutDialog(self)
        dialog.exec_()

    def report_issue(self):
        """ Open in the browser the page to create new  issue """

        webbrowser.open("http://github.com/centaurialpha/pireal/issues/new")

    def show_hide_menubar(self):
        """ Change visibility of menu bar """

        if self.menuBar().isVisible():
            self.menuBar().hide()
        else:
            self.menuBar().show()

    def show_hide_toolbar(self):
        """ Change visibility of tool bar """

        if self.toolbar.isVisible():
            self.toolbar.hide()
        else:
            self.toolbar.show()

    def show_error_message(self, text, syntax_error=True):
        self._msg_error_widget.show_msg(text, syntax_error)
        self._msg_error_widget.show()

    def closeEvent(self, event):
        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        # Save window geometry
        if self.isMaximized():
            qsettings.setValue('window_max', True)
        else:
            qsettings.setValue('window_max', False)
            qsettings.setValue('window_pos', self.pos())
            qsettings.setValue('window_size', self.size())

        central_widget = Pireal.get_service("central")
        # Save recent databases
        qsettings.setValue('recent_databases',
                           central_widget.recent_databases)
        db = central_widget.get_active_db()
        if db is not None:
            # Save splitters size
            db.save_sizes()
            # Databases unsaved
            if db.modified:
                msg = QMessageBox(self)
                msg.setIcon(QMessageBox.Question)
                msg.setWindowTitle(self.tr("Some changes where not saved"))
                msg.setText(
                    self.tr("Do you want to save changes to the database?"))
                cancel_btn = msg.addButton(self.tr("Cancel"),
                                           QMessageBox.RejectRole)
                msg.addButton(self.tr("No"),
                              QMessageBox.NoRole)
                yes_btn = msg.addButton(self.tr("Yes"),
                                        QMessageBox.YesRole)
                msg.exec_()
                r = msg.clickedButton()
                if r == yes_btn:
                    central_widget.save_database()
                if r == cancel_btn:
                    event.ignore()
            # Query files
            unsaved_editors = central_widget.get_unsaved_queries()
            if unsaved_editors:
                msg = QMessageBox(self)
                msg.setIcon(QMessageBox.Question)
                msg.setWindowTitle(self.tr("Unsaved Queries"))
                text = '\n'.join([editor.name for editor in unsaved_editors])
                msg.setText(self.tr("{files}<br><br>Do you want to "
                                    "save them?".format(files=text)))
                cancel_btn = msg.addButton(self.tr("Cancel"),
                                           QMessageBox.RejectRole)
                msg.addButton(self.tr("No"),
                              QMessageBox.NoRole)
                yes_btn = msg.addButton(self.tr("Yes"),
                                        QMessageBox.YesRole)
                msg.exec_()
                if msg.clickedButton() == yes_btn:
                    for editor in unsaved_editors:
                        central_widget.save_query(editor)
                if msg.clickedButton() == cancel_btn:
                    event.ignore()
Esempio n. 17
0
class Preferences(QDialog):
    # Signal to warn that the window is closed
    settingsClosed = pyqtSignal()

    def __init__(self, parent=None):
        super(Preferences, self).__init__(parent)

        # Main container
        # This contains a grid
        main_box = QVBoxLayout(self)
        main_box.setContentsMargins(200, 50, 200, 100)

        # The grid contains two containers
        # left container and right container
        grid = QGridLayout()

        # Left Container
        left_container = QVBoxLayout()
        left_container.setContentsMargins(0, 0, 0, 0)

        # General
        group_gral = QGroupBox(self.tr("General"))
        box_gral = QVBoxLayout(group_gral)
        # Updates
        btn_updates = QPushButton(self.tr("Check for updates"))
        box_gral.addWidget(btn_updates)
        # Language
        group_language = QGroupBox(self.tr("Language"))
        box = QVBoxLayout(group_language)
        # Find .qm files in language path
        available_langs = file_manager.get_files_from_folder(
            settings.LANGUAGE_PATH)

        languages = ["English"] + available_langs
        self._combo_lang = QComboBox()
        box.addWidget(self._combo_lang)
        self._combo_lang.addItems(languages)
        self._combo_lang.currentIndexChanged[int].connect(
            self._change_lang)
        if PSetting.LANGUAGE:
            self._combo_lang.setCurrentText(PSetting.LANGUAGE)
        box.addWidget(QLabel(self.tr("(Requires restart)")))

        # Add widgets
        left_container.addWidget(group_gral)
        left_container.addWidget(group_language)
        left_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding,
                                           QSizePolicy.Expanding))

        # Right Container
        right_container = QVBoxLayout()
        right_container.setContentsMargins(0, 0, 0, 0)

        # Editor
        editor_group = QGroupBox(self.tr("Editor Configurations"))
        box_editor = QHBoxLayout(editor_group)
        # Current line
        self._highlight_current_line = QCheckBox(
            self.tr("Highlight Current Line"))
        self._highlight_current_line.setChecked(
            PSetting.HIGHLIGHT_CURRENT_LINE)
        self._highlight_current_line.stateChanged[int].connect(
            self.__current_line_value_changed)
        box_editor.addWidget(self._highlight_current_line)
        # Matching paren
        self._matching_paren = QCheckBox(self.tr("Matching Parenthesis"))
        self._matching_paren.setChecked(
            PSetting.MATCHING_PARENTHESIS)
        self._matching_paren.stateChanged[int].connect(
            self.__set_enabled_matching_parenthesis)
        box_editor.addWidget(self._matching_paren)
        # Font group
        font_group = QGroupBox(self.tr("Font"))
        font_grid = QGridLayout(font_group)
        font_grid.addWidget(QLabel(self.tr("Family")), 0, 0)
        self._combo_font = QFontComboBox()
        self._combo_font.setCurrentFont(PSetting.FONT)
        font_grid.addWidget(self._combo_font, 0, 1)
        font_grid.addWidget(QLabel(self.tr("Point Size")), 1, 0)
        self._combo_font_size = QComboBox()
        fdb = QFontDatabase()
        combo_sizes = fdb.pointSizes(PSetting.FONT.family())
        current_size_index = combo_sizes.index(
            PSetting.FONT.pointSize())

        self._combo_font_size.addItems([str(f) for f in combo_sizes])
        self._combo_font_size.setCurrentIndex(current_size_index)
        font_grid.addWidget(self._combo_font_size, 1, 1)

        right_container.addWidget(editor_group)
        right_container.addWidget(font_group)
        right_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding,
                                            QSizePolicy.Expanding))

        # Add widgets
        grid.addLayout(left_container, 0, 0)
        grid.addLayout(right_container, 0, 1)
        main_box.addLayout(grid)

        # Button close and reset
        hbox = QHBoxLayout()
        hbox.setSpacing(20)
        hbox.addItem(QSpacerItem(1, 0, QSizePolicy.Expanding))
        btn_cancel = QPushButton(self.tr("Back"))
        hbox.addWidget(btn_cancel)
        btn_reset = QPushButton(self.tr("Reset Configurations"))
        hbox.addWidget(btn_reset)
        main_box.addLayout(hbox)

        # Overlay
        self.overlay = overlay_widget.OverlayWidget(self)
        self.overlay.hide()

        # Effect and animations
        self.effect = QGraphicsOpacityEffect()
        self.setGraphicsEffect(self.effect)
        duration, x = 180, 150  # Animation duration
        # Animation start
        # Opacity animation
        self.opacity_animation_s = QPropertyAnimation(self.effect, b"opacity")
        self.opacity_animation_s.setDuration(duration)
        self.opacity_animation_s.setStartValue(0.0)
        self.opacity_animation_s.setEndValue(1.0)
        # X animation
        self.x_animation_s = QPropertyAnimation(self, b"geometry")
        self.x_animation_s.setDuration(duration)
        self.x_animation_s.setStartValue(QRect(x, 0, parent.width(),
                                               parent.height()))
        self.x_animation_s.setEndValue(QRect(0, 0, parent.width(),
                                             parent.height()))
        # Animation end
        # Opacity animation
        self.opacity_animation_e = QPropertyAnimation(self.effect, b"opacity")
        self.opacity_animation_e.setDuration(duration)
        self.opacity_animation_e.setStartValue(1.0)
        self.opacity_animation_e.setEndValue(0.0)
        # X animation
        self.x_animation_e = QPropertyAnimation(self, b"geometry")
        self.x_animation_e.setDuration(duration)
        self.x_animation_e.setStartValue(QRect(0, 0, parent.width(),
                                               parent.height()))
        self.x_animation_e.setEndValue(QRect(-x, 0, parent.width(),
                                             parent.height()))

        # Group animation start
        self.group_animation_s = QParallelAnimationGroup()
        self.group_animation_s.addAnimation(self.opacity_animation_s)
        self.group_animation_s.addAnimation(self.x_animation_s)

        # Group animation end
        self.group_animation_e = QParallelAnimationGroup()
        self.group_animation_e.addAnimation(self.opacity_animation_e)
        self.group_animation_e.addAnimation(self.x_animation_e)

        # Connections
        self.group_animation_e.finished.connect(
            self._on_group_animation_finished)
        btn_cancel.clicked.connect(self.close)
        btn_reset.clicked.connect(self._reset_settings)
        btn_updates.clicked.connect(self._check_for_updates)
        # self.thread.finished.connect(self._on_thread_finished)
        self._combo_font.currentFontChanged.connect(
            self._change_font)
        self._combo_font_size.currentTextChanged.connect(
            self._change_font_size)

    def __current_line_value_changed(self, value):
        qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        qs.setValue('highlight_current_line', value)
        PSetting.HIGHLIGHT_CURRENT_LINE = value

    def __set_enabled_matching_parenthesis(self, value):
        qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        qs.setValue("matching_parenthesis", value)
        PSetting.MATCHING_PARENTHESIS = value

    def _change_font(self, font):
        # FIXME: un quilombo esto
        central = Pireal.get_service("central")
        mcontainer = central.get_active_db()
        if mcontainer is not None:
            query_widget = mcontainer.query_container.currentWidget()
            if query_widget is not None:
                weditor = query_widget.get_editor()
                if weditor is not None:
                    qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
                    weditor.set_font(font)
                    qs.setValue("font", font)

    def _change_font_size(self, size):
        # FIXME: un quilombo esto
        font = self._combo_font.currentFont()
        font.setPointSize(int(size))
        central = Pireal.get_service("central")
        mcontainer = central.get_active_db()
        if mcontainer is not None:
            query_widget = mcontainer.query_container.currentWidget()
            if query_widget is not None:
                weditor = query_widget.get_editor()
                if weditor is not None:
                    qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
                    weditor.set_font(font)
                    qs.setValue("font", font)

    def showEvent(self, event):
        super(Preferences, self).showEvent(event)
        self.group_animation_s.start()

    def resizeEvent(self, event):
        self.overlay.resize(self.size())
        event.accept()

    def done(self, result):
        self.res = result
        self.group_animation_e.start()

    def _on_group_animation_finished(self):
        super(Preferences, self).done(self.res)
        self.settingsClosed.emit()

    def _check_for_updates(self):
        # Thread
        self._thread = QThread()
        self._updater = updater.Updater()
        self._updater.moveToThread(self._thread)
        self._thread.started.connect(self._updater.check_updates)
        self._updater.finished.connect(self.__on_thread_update_finished)
        # Show overlay widget
        self.overlay.show()
        # Start thread
        self._thread.start()

    def __on_thread_update_finished(self):
        # Hide overlay widget
        self.overlay.hide()
        self._thread.quit()
        msg = QMessageBox(self)
        if not self._updater.error:
            if self._updater.version:
                version = self._updater.version
                msg.setWindowTitle(self.tr("New version available!"))
                msg.setText(self.tr("Check the web site to "
                                    "download <b>Pireal {}</b>".format(
                                        version)))
                download_btn = msg.addButton(self.tr("Download!"),
                                             QMessageBox.YesRole)
                msg.addButton(self.tr("Cancel"),
                              QMessageBox.RejectRole)
                msg.exec_()
                r = msg.clickedButton()
                if r == download_btn:
                    webbrowser.open_new(
                        "http://centaurialpha.github.io/pireal")
            else:
                msg.setWindowTitle(self.tr("Information"))
                msg.setText(self.tr("Last version installed"))
                msg.addButton(self.tr("Ok"),
                              QMessageBox.AcceptRole)
                msg.exec_()
        else:
            msg.critical(self, self.tr("Error"),
                         self.tr("Connection error"))

        self._thread.deleteLater()
        self._updater.deleteLater()

    def _reset_settings(self):
        """ Remove all settings """

        msg = QMessageBox(self)
        msg.setWindowTitle(self.tr("Reset Settings"))
        msg.setText(self.tr("Are you sure you want to clear all settings?"))
        msg.setIcon(QMessageBox.Question)
        msg.addButton(self.tr("No"), QMessageBox.NoRole)
        yes_btn = msg.addButton(self.tr("Yes"),
                                QMessageBox.YesRole)
        msg.exec_()
        r = msg.clickedButton()
        if r == yes_btn:
            QSettings(settings.SETTINGS_PATH, QSettings.IniFormat).clear()
            self.close()

    def _change_lang(self, index):
        lang = self._combo_lang.itemText(index)
        qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        qs.setValue('language', lang)
Esempio n. 18
0
class RechgEvalWidget(QFrameLayout):

    sig_new_gluedf = QSignal(GLUEDataFrameBase)

    def __init__(self, parent):
        super(RechgEvalWidget, self).__init__(parent)
        self.setWindowTitle('Recharge Calibration Setup')
        self.setWindowFlags(Qt.Window)

        self.wxdset = None
        self.wldset = None
        self.figstack = FigureStackManager(parent=self)

        self.progressbar = QProgressBar()
        self.progressbar.setValue(0)
        self.progressbar.hide()
        self.__initUI__()

        # Set the worker and thread mechanics

        self.rechg_worker = RechgEvalWorker()
        self.rechg_worker.sig_glue_finished.connect(self.receive_glue_calcul)
        self.rechg_worker.sig_glue_progress.connect(self.progressbar.setValue)

        self.rechg_thread = QThread()
        self.rechg_worker.moveToThread(self.rechg_thread)
        self.rechg_thread.started.connect(self.rechg_worker.eval_recharge)

    def __initUI__(self):

        class QRowLayout(QWidget):
            def __init__(self, items, parent=None):
                super(QRowLayout, self).__init__(parent)

                layout = QGridLayout()
                for col, item in enumerate(items):
                    layout.addWidget(item, 0, col)
                layout.setContentsMargins(0, 0, 0, 0)
                layout.setColumnStretch(0, 100)
                self.setLayout(layout)

        # ---- Parameters

        # Specific yield (Sy) :

        self.QSy_min = QDoubleSpinBox(0.05, 3)
        self.QSy_min.setRange(0.001, 1)

        self.QSy_max = QDoubleSpinBox(0.2, 3)
        self.QSy_max.setRange(0.001, 1)

        # Maximum readily available water (RASmax) :

        # units=' mm'

        self.QRAS_min = QDoubleSpinBox(5)
        self.QRAS_min.setRange(0, 999)

        self.QRAS_max = QDoubleSpinBox(40)
        self.QRAS_max.setRange(0, 999)

        # Runoff coefficient (Cro) :

        self.CRO_min = QDoubleSpinBox(0.1, 3)
        self.CRO_min.setRange(0, 1)

        self.CRO_max = QDoubleSpinBox(0.3, 3)
        self.CRO_max.setRange(0, 1)

        # Snowmelt parameters :

        # units=' °C'

        self._Tmelt = QDoubleSpinBox(0, 1)
        self._Tmelt.setRange(-25, 25)

        # units=' mm/°C'

        self._CM = QDoubleSpinBox(4, 1, 0.1, )
        self._CM.setRange(0.1, 100)

        # units=' days'

        self._deltaT = QDoubleSpinBox(0, 0, )
        self._deltaT.setRange(0, 999)

        class QLabelCentered(QLabel):
            def __init__(self, text):
                super(QLabelCentered, self).__init__(text)
                self.setAlignment(Qt.AlignCenter)

        # ---- Parameters

        params_group = QFrameLayout()
        params_group.setContentsMargins(10, 5, 10, 0)  # (L, T, R, B)
        params_group.setObjectName("viewport")
        params_group.setStyleSheet("#viewport {background-color:transparent;}")

        row = 0
        params_group.addWidget(QLabel('Sy :'), row, 0)
        params_group.addWidget(self.QSy_min, row, 1)
        params_group.addWidget(QLabelCentered('to'), row, 2)
        params_group.addWidget(self.QSy_max, row, 3)
        row += 1
        params_group.addWidget(QLabel('RAS<sub>max</sub> :'), row, 0)
        params_group.addWidget(self.QRAS_min, row, 1)
        params_group.addWidget(QLabelCentered('to'), row, 2)
        params_group.addWidget(self.QRAS_max, row, 3)
        params_group.addWidget(QLabel('mm'), row, 4)
        row += 1
        params_group.addWidget(QLabel('Cro :'), row, 0)
        params_group.addWidget(self.CRO_min, row, 1)
        params_group.addWidget(QLabelCentered('to'), row, 2)
        params_group.addWidget(self.CRO_max, row, 3)
        row += 1
        params_group.setRowMinimumHeight(row, 10)
        row += 1
        params_group.addWidget(QLabel('Tmelt :'), row, 0)
        params_group.addWidget(self._Tmelt, row, 1)
        params_group.addWidget(QLabel('°C'), row, 2, 1, 3)
        row += 1
        params_group.addWidget(QLabel('CM :'), row, 0)
        params_group.addWidget(self._CM, row, 1)
        params_group.addWidget(QLabel('mm/°C'), row, 2, 1, 3)
        row += 1
        params_group.addWidget(QLabel('deltaT :'), row, 0)
        params_group.addWidget(self._deltaT, row, 1)
        params_group.addWidget(QLabel('days'), row, 2, 1, 3)
        row += 1
        params_group.setRowStretch(row, 100)
        params_group.setColumnStretch(5, 100)

        # ---- Layout ----

        qtitle = QLabel('Parameter Range')
        qtitle.setAlignment(Qt.AlignCenter)

        sa = QScrollArea()
        sa.setWidget(params_group)
        sa.setWidgetResizable(True)
        sa.setFrameStyle(0)
        sa.setStyleSheet("QScrollArea {background-color:transparent;}")
        sa.setSizePolicy(QSizePolicy(QSizePolicy.Ignored,
                                     QSizePolicy.Preferred))

        # ---- Main Layout

        self.addWidget(qtitle, 0, 0)
        self.addWidget(HSep(), 1, 0)
        self.addWidget(sa, 2, 0)
        self.addWidget(HSep(), 3, 0)
        self.setRowMinimumHeight(4, 5)
        self.addWidget(self.setup_toolbar(), 5, 0)

        self.setRowStretch(2, 100)
        self.setVerticalSpacing(5)
        self.setContentsMargins(0, 0, 0, 10)   # (L, T, R, B)

    def setup_toolbar(self):
        """Setup the toolbar of the widget. """
        toolbar = QWidget()

        btn_calib = QPushButton('Compute Recharge')
        btn_calib.clicked.connect(self.btn_calibrate_isClicked)

        self.btn_show_result = QToolButtonSmall(icons.get_icon('search'))
        self.btn_show_result.clicked.connect(self.figstack.show)
        self.btn_show_result.setToolTip("Show GLUE results.")

        self.btn_save_glue = ExportGLUEButton(self.wxdset)

        layout = QGridLayout(toolbar)
        layout.addWidget(btn_calib, 0, 0)
        layout.addWidget(self.btn_show_result, 0, 1)
        layout.addWidget(self.btn_save_glue, 0, 2)
        layout.setContentsMargins(10, 0, 10, 0)  # (L, T, R, B)

        return toolbar

    def set_wldset(self, wldset):
        """Set the namespace for the water level dataset."""
        self.wldset = wldset
        self.setEnabled(self.wldset is not None and self.wxdset is not None)
        gluedf = None if wldset is None else wldset.get_glue_at(-1)
        self._setup_ranges_from_wldset(gluedf)
        self.figstack.set_gluedf(gluedf)
        self.btn_save_glue.set_model(gluedf)

    def set_wxdset(self, wxdset):
        """Set the namespace for the weather dataset."""
        self.wxdset = wxdset
        self.setEnabled(self.wldset is not None and self.wxdset is not None)

    def _setup_ranges_from_wldset(self, gluedf):
        """
        Set the parameter range values from the last values that were used
        to produce the last GLUE results saved into the project.
        """
        if gluedf is not None:
            self.QSy_min.setValue(min(gluedf['ranges']['Sy']))
            self.QSy_max.setValue(max(gluedf['ranges']['Sy']))

            self.CRO_min.setValue(min(gluedf['ranges']['Cro']))
            self.CRO_max.setValue(max(gluedf['ranges']['Cro']))

            self.QRAS_min.setValue(min(gluedf['ranges']['RASmax']))
            self.QRAS_max.setValue(max(gluedf['ranges']['RASmax']))

            self._Tmelt.setValue(gluedf['params']['tmelt'])
            self._CM.setValue(gluedf['params']['CM'])
            self._deltaT.setValue(gluedf['params']['deltat'])

    def get_Range(self, name):
        if name == 'Sy':
            return [self.QSy_min.value(), self.QSy_max.value()]
        elif name == 'RASmax':
            return [self.QRAS_min.value(), self.QRAS_max.value()]
        elif name == 'Cro':
            return [self.CRO_min.value(), self.CRO_max.value()]
        else:
            raise ValueError('Name must be either Sy, Rasmax or Cro.')

    @property
    def Tmelt(self):
        return self._Tmelt.value()

    @property
    def CM(self):
        return self._CM.value()

    @property
    def deltaT(self):
        return self._deltaT.value()

    def btn_calibrate_isClicked(self):
        """
        Handles when the button to compute recharge and its uncertainty is
        clicked.
        """
        self.start_glue_calcul()

    def start_glue_calcul(self):
        """
        Start the method to evaluate ground-water recharge and its
        uncertainty.
        """
        # Set the parameter ranges.

        self.rechg_worker.Sy = self.get_Range('Sy')
        self.rechg_worker.Cro = self.get_Range('Cro')
        self.rechg_worker.RASmax = self.get_Range('RASmax')

        self.rechg_worker.TMELT = self.Tmelt
        self.rechg_worker.CM = self.CM
        self.rechg_worker.deltat = self.deltaT

        # Set the data and check for errors.

        error = self.rechg_worker.load_data(self.wxdset, self.wldset)
        if error is not None:
            QMessageBox.warning(self, 'Warning', error, QMessageBox.Ok)
            return

        # Start the computation of groundwater recharge.

        self.progressbar.show()
        waittime = 0
        while self.rechg_thread.isRunning():
            time.sleep(0.1)
            waittime += 0.1
            if waittime > 15:
                print('Impossible to quit the thread.')
                return
        self.rechg_thread.start()

    def receive_glue_calcul(self, glue_dataframe):
        """
        Handle the plotting of the results once ground-water recharge has
        been evaluated.
        """
        self.rechg_thread.quit()
        self.progressbar.hide()
        if glue_dataframe is None:
            msg = ("Recharge evaluation was not possible because all"
                   " the models produced were deemed non-behavioural."
                   "\n\n"
                   "This usually happens when the range of values for"
                   " Sy, RASmax, and Cro are too restrictive or when the"
                   " Master Recession Curve (MRC) does not represent well the"
                   " behaviour of the observed hydrograph.")
            QMessageBox.warning(self, 'Warning', msg, QMessageBox.Ok)
        else:
            self.wldset.clear_glue()
            self.wldset.save_glue(glue_dataframe)
            self.sig_new_gluedf.emit(glue_dataframe)

            self.btn_save_glue.set_model(glue_dataframe)
            self.figstack.set_gluedf(glue_dataframe)
Esempio n. 19
0
class Preferences(QDialog):
    settingsClosed = pyqtSignal()

    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        self.__need_restart = False
        box = QVBoxLayout(self)
        box.setContentsMargins(0, 0, 0, 0)
        view = QQuickWidget()
        view.setResizeMode(QQuickWidget.SizeRootObjectToView)
        qml = os.path.join(settings.QML_PATH, "Preferences.qml")
        view.setSource(QUrl.fromLocalFile(qml))
        box.addWidget(view)

        self.__root = view.rootObject()
        # Lista de idiomas para el Combo qml
        available_langs = file_manager.get_files_from_folder(
            settings.LANGUAGE_PATH)
        langs = ["English"] + available_langs
        self.__root.addLangsToCombo(langs)

        self.__root.setCurrentLanguage(CONFIG.get("language"))

        font = CONFIG.get("fontFamily")
        size = CONFIG.get("fontSize")
        if font is None:
            font, size = CONFIG._get_font()

        self.__root.setFontFamily(font, size)

        self.__root.setInitialStates(
            CONFIG.get("highlightCurrentLine"),
            CONFIG.get("matchParenthesis"))

        # Conexiones
        self.__root.close.connect(lambda: self.settingsClosed.emit())
        self.__root.resetSettings.connect(self.__reset_settings)
        self.__root.checkForUpdates.connect(self.__check_for_updates)
        self.__root.changeLanguage.connect(self.__change_language)
        self.__root.stateCurrentLineChanged[bool].connect(
            self.__on_state_current_line_changed)
        self.__root.stateMatchingParenChanged[bool].connect(
            self.__on_state_matching_parenthesis_changed)
        self.__root.needChangeFont.connect(self.__change_font)

    @pyqtSlot()
    def __change_font(self):
        font = CONFIG.get("fontFamily")
        size = CONFIG.get("fontSize")
        if font is None:
            font, size = CONFIG._get_font()
        font, ok = QFontDialog.getFont(QFont(font, size), self)
        if ok:
            CONFIG.set_value("fontFamily", font.family())
            CONFIG.set_value("fontSize", font.pointSize())
            central = Pireal.get_service("central")
            mcontainer = central.get_active_db()
            if mcontainer is not None:
                query_widget = mcontainer.query_container.currentWidget()
                if query_widget is not None:
                    weditor = query_widget.get_editor()
                    if weditor is not None:
                        weditor.set_font(font.family(), font.pointSize())
            # Cambio el texto en la interfáz QML
            self.__root.setFontFamily(font.family(), font.pointSize())

    @pyqtSlot(bool)
    def __on_state_current_line_changed(self, state):
        CONFIG.set_value("highlightCurrentLine", state)

    @pyqtSlot(bool)
    def __on_state_matching_parenthesis_changed(self, state):
        CONFIG.set_value("matchParenthesis", state)

    @pyqtSlot('QString')
    def __change_language(self, lang):
        qsettings = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat)
        current_lang = qsettings.value('language', 'English')
        if current_lang != lang:
            qsettings.setValue('language', lang)
            self.__need_restart = True

    @pyqtSlot()
    def __check_for_updates(self):
        # Thread
        self._thread = QThread()
        self._updater = updater.Updater()
        self._updater.moveToThread(self._thread)
        self._thread.started.connect(self._updater.check_updates)
        self._updater.finished.connect(self.__on_thread_update_finished)
        # Start thread
        self._thread.start()

    @pyqtSlot()
    def __on_thread_update_finished(self):
        self._thread.quit()
        msg = QMessageBox(self)
        if not self._updater.error:
            if self._updater.version:
                version = self._updater.version
                msg.setWindowTitle(self.tr("New version available!"))
                msg.setText(self.tr("Check the web site to "
                                    "download <b>Pireal {}</b>".format(
                                        version)))
                download_btn = msg.addButton(self.tr("Download!"),
                                             QMessageBox.YesRole)
                msg.addButton(self.tr("Cancel"),
                              QMessageBox.RejectRole)
                msg.exec_()
                r = msg.clickedButton()
                if r == download_btn:
                    webbrowser.open_new(
                        "http://centaurialpha.github.io/pireal")
            else:
                # Cierro BusyIndicator de qml
                self.__root.threadFinished()
                msg.setWindowTitle(self.tr("Information"))
                msg.setText(self.tr("Last version installed"))
                msg.addButton(self.tr("Ok"),
                              QMessageBox.AcceptRole)
                msg.exec_()
        else:
            msg.critical(self, self.tr("Error"),
                         self.tr("Connection error"))

        self._thread.deleteLater()
        self._updater.deleteLater()
        self.__root.threadFinished()

    @pyqtSlot()
    def __reset_settings(self):
        """ Remove all settings """

        msg = QMessageBox(self)
        msg.setWindowTitle(self.tr("Reset Settings"))
        msg.setText(self.tr("Are you sure you want to clear all settings?"))
        msg.setIcon(QMessageBox.Question)
        msg.addButton(self.tr("No"), QMessageBox.NoRole)
        yes_btn = msg.addButton(self.tr("Yes"),
                                QMessageBox.YesRole)
        msg.exec_()
        r = msg.clickedButton()
        if r == yes_btn:
            QSettings(settings.SETTINGS_PATH, QSettings.IniFormat).clear()
Esempio n. 20
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. 21
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(2 * 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_correlation_window_at = self._addAction("Correlation",
                                                     "correlation.png")
        open_correlation_window_at.triggered.connect(
            functools.partial(self.onOpenPlotWindow, CorrelationWindow))

        open_histogram_window_at = self._addAction("Histogram",
                                                   "histogram.png")
        open_histogram_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._analysis_setup_manager = AnalysisSetupManager()

        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.fom_filter_ctrl_widget = self.createCtrlWidget(
            FomFilterCtrlWidget)

        # *************************************************************
        # 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._analysis_setup_manager.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,
                              parent=self)
        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.6, self._WIDTH * 0.4])

    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._right_cw_container.setWidget(self._right_cw)
        self._right_cw_container.setWidgetResizable(True)

    def initCtrlUI(self):
        self.initGeneralAnalysisUI()

        self._ctrl_panel_cw.addTab(self._analysis_cw, "General analysis setup")

    def initGeneralAnalysisUI(self):
        layout = QVBoxLayout()
        layout.addWidget(self.analysis_ctrl_widget)
        layout.addWidget(self.fom_filter_ctrl_widget)
        self._analysis_cw.setLayout(layout)

    def initUtilUI(self):
        self._util_panel_cw.addTab(self._gui_logger.widget, "Logger")
        self._util_panel_cw.addTab(self._analysis_setup_manager,
                                   "Analysis Setup Manager")
        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._analysis_setup_manager.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,
                             require_geometry=self._require_geometry,
                             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()
        for win in self._plot_windows:
            win.onStart()
        self._image_tool.onStart()
        self._analysis_setup_manager.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()
        for win in self._plot_windows:
            win.onStop()
        self._image_tool.onStop()
        self._analysis_setup_manager.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:
            if not widget.updateMetaData():
                return False

        for win in self._plot_windows:
            if not win.updateMetaData():
                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()

        for win in self._plot_windows:
            win.loadMetaData()

        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. 22
0
 def quit(self):
     """graceful exit when quiting the task"""
     self.abort = True
     QThread.quit(self)
     QThread.wait(self)
Esempio n. 23
0
class Window(QMainWindow):
    """
    A class that connect functionality to the GUI window. Starts a new thread with a worker when clicking on a button.
    """

    ## Initialize different signals which can be sent to the Worker class
    loadingModel = pyqtSignal(str, torch.nn.Module)
    uploadingImage = pyqtSignal(str)
    predictingImage = pyqtSignal()

    def __init__(self, app):
        """
        Initializes the window, initializes a (parallel) worker and connects all the functionalities to the dialog.

        Args:
            app (QApplication): A QApplication object of the GUI.

        Returns:
            None.

        """

        ## Create a window
        super(Window, self).__init__()

        ## Load lay-out of GUI from the generated dialog file
        self.dialog = Ui_AFMNet()
        self.dialog.setupUi(self)

        ## Get the directory of the AFMNet folder
        self.dirPath = os.path.dirname(
            os.path.realpath(__file__)) + os.path.sep + ".."

        ## Set the device for Pytorch
        self.device = torch.device(
            "cuda:0" if torch.cuda.is_available() else "cpu")

        ## Add the model architectures (ConvNet, FineNet and FeatureNet) to the dropdown menu
        archs = next(os.walk(self.dirPath + os.path.sep + "models"))[1]
        for arch in archs:
            self.dialog.comboBox.addItem(arch)

        ## Initialize a (parallel) worker and pass the directory and device
        self.worker = Worker(self.dirPath, self.device)

        ## Connect functions to the different buttons
        self.dialog.modelButton.clicked.connect(self.loadModel)
        self.dialog.uploadButton.clicked.connect(self.uploadImage)
        self.dialog.predictButton.clicked.connect(self.predictImage)

        ## Close all threads when the window closes
        app.aboutToQuit.connect(self.closeWindow)

    def enableButtons(self, boolean=True):
        """
        Enables or disabled all buttons.

        Args:
            boolean (bool, optional): All buttons will be enabled if 'True', they will be disabled if 'False'. Defaults to True.

        Returns:
            None.

        """
        self.dialog.modelButton.setEnabled(boolean)
        self.dialog.uploadButton.setEnabled(boolean)
        self.dialog.predictButton.setEnabled(boolean)

    def startThread(self, signal, slot):
        """
        Creates and starts a thread for the (parallel) worker. Also connects a signal to a slot of the worker.

        Args:
            signal (pyqtSignal): A signal attribute of the Window class.
            slot (pyqtSignal): A slot attribute of the Worker class.

        Returns:
            None.

        """
        ## Start a QThread if it does not exist yet
        if not (hasattr(self, "thread")
                and isinstance(getattr(self, "thread"), QThread)):
            self.thread = QThread()

        ## Connect the signals of the worker to methods in the window class
        self.worker.progress.connect(self.setProgress)
        self.worker.text.connect(self.setTextLabel)
        self.worker.image.connect(self.setImageLabel)

        ## Move the worker to the thread
        self.worker.moveToThread(self.thread)

        ## Connect a sginal of the worker to a method in the window class
        signal.connect(slot)
        self.worker.finished.connect(self.finished)

        ## Start the thread
        self.thread.start()

    def setProgress(self, value):
        """
        Sets the progress of the progress bar to a certain value.

        Args:
            value (int): The progress bar will be set to this (percentage) value.

        Returns:
            None.

        """
        self.dialog.progressBar.setValue(value)

    def setTextLabel(self, string):
        """
        Sets the text label of the text beneath the image.

        Args:
            string (str): String of desired text for the label.

        Returns:
            None.

        """
        self.dialog.textlabel.setText(string)

    def setImageLabel(self, pixmap):
        """
        Updates the image label with the provided pixmap.

        Args:
            pixmap (QPixmap): A pixmap of an AFM image.

        Returns:
            None.

        """
        self.dialog.image.setUnScaledPixmap(pixmap)

    def finished(self):
        """
        Called when a thread is finished. Sets the progress to 100, enables all buttons and quits the thread.

        Returns:
            None.

        """
        self.setProgress(100)
        self.enableButtons()
        self.thread.quit()

    def loadModel(self):
        """
        Method connected to the 'Load Model' button. Loads a model architecture.

        Returns:
            None.

        """

        ## Set the progress to 0, disable all buttons and set the text label to "Loading..."
        self.setProgress(0)
        self.enableButtons(False)
        self.setTextLabel("Loading...")

        ## Start a thread which connects the loadingModel signal to the LoadModel slot of the worker
        self.startThread(self.loadingModel, self.worker.loadModel)

        ## Retrieve the selected network architecture
        comboText = self.dialog.comboBox.currentText()
        if comboText == "Select Net...":
            ## If no network architecture is selected, prompt the user to select a network architecture.
            self.setTextLabel("Select a network architecture first!")
            self.finished()
            return
        else:
            self.net_arch = comboText.split(' ', 1)[0]

        ## Open an explorer window in which you can select a model of architecture net_arch
        self.modelPath, _ = QFileDialog.getOpenFileName(
            self, "Load Model", self.dirPath + os.path.sep + "models" +
            os.path.sep + self.net_arch, "Path Files (*pt *pth)")

        ## Initialize the network (with a class from nets.py)
        if not self.net_arch.__eq__("ConvNet"):
            arg = eval("models." + self.modelPath.split('/')[-1].split('_')[0])
            self.net = eval("nets." + self.net_arch)(arg)
        else:
            self.net = eval("nets." + self.net_arch)()

        ## Send a signal with the path and network to the loadModel slot of the worker
        self.loadingModel.emit(self.modelPath, self.net)

    def uploadImage(self):
        """
        Method connected to the 'Upload Image' button. Opens an AFM image to predict.

        Returns:
            None.

        """

        ## Set the progress to 0, disable all buttons and set the text label to "Loading..."
        self.setProgress(0)
        self.enableButtons(False)
        self.setTextLabel("Loading...")

        ## Open an explorer window in which you can open an AFM image
        self.fileName, _ = QFileDialog.getOpenFileName(
            self, "Upload Image",
            self.dirPath + os.path.sep + "data" + os.path.sep + "val",
            'Images (*bmp *gif *jpg *jpeg *png *pbm *tiff *svg *xbm)')

        ## Start a thread which connects the uploadingImage signal to the uploadImage slot of the worker
        self.startThread(self.uploadingImage, self.worker.uploadImage)

        ## Send a signal with the path to the uploadImage slot of the worker
        self.uploadingImage.emit(self.fileName)

    def predictImage(self):
        """
        Method connected to the 'Predict Image' button. Classifies an image as 'Good' or 'Bad'.

        Returns:
            None.

        """
        ## Set the progress to 0, disable all buttons and set the text label to "Busy..."
        self.setProgress(0)
        self.enableButtons(False)
        self.setTextLabel("Busy...")

        ## Start a thread which connects the predictingImage signal to the predictImage slot of the worker
        self.startThread(self.predictingImage, self.worker.predictImage)

        ## Send a signal to the predictImage slot of the worker
        self.predictingImage.emit()

    def closeWindow(self):
        """
        Closes the remaining threads.

        Returns:
            None.

        """
        if hasattr(self, "thread") and isinstance(getattr(self, "thread"),
                                                  QThread):
            self.thread.quit()
Esempio n. 24
0
class TrainingWidget(QWidget):
    data_dir_selected = pyqtSignal(str)
    save_dir_selected = pyqtSignal(str)
    test_count = pyqtSignal(str)
    train_count = pyqtSignal(str)
    training = pyqtSignal(bool)

    def __init__(self):
        super().__init__()
        self.data_dir = None
        self.save_dir = None
        self.train_button = None
        self.text_edit = None
        self.is_training = False
        self.runner_thread = None
        self.worker = None
        self.training_info_box = {}
        self.init_ui()

    def init_ui(self):
        vbox = QVBoxLayout()
        size = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        ## Data Box
        data_hbox = QHBoxLayout()
        data_label = QLabel("Data directory: ")
        data_label.setSizePolicy(size)
        data_label_dir = QLabel("")
        data_label_dir.setSizePolicy(size)
        self.data_dir_selected.connect(data_label_dir.setText)

        data_button = QPushButton("Choose Folder")
        data_button.setSizePolicy(size)
        data_button.pressed.connect(self.data_button_pressed)

        data_hbox.addWidget(data_label)
        data_hbox.addWidget(data_label_dir)
        data_hbox.addStretch(1)
        data_hbox.addWidget(data_button)
        data_hbox.addStretch(2)

        # train box
        train_hbox = QHBoxLayout()

        train_count_label = QLabel("Training images: ")
        train_count_label.setSizePolicy(size)

        train_count_value_label = QLabel("")
        train_count_value_label.setSizePolicy(size)
        self.train_count.connect(train_count_value_label.setText)

        train_hbox.addWidget(train_count_label)
        train_hbox.addWidget(train_count_value_label)
        train_hbox.addStretch(1)

        # test box
        test_hbox = QHBoxLayout()
        test_count_label = QLabel("Testing images: ")
        test_count_value_label = QLabel("")
        test_count_label.setSizePolicy(size)
        test_count_value_label.setSizePolicy(size)
        self.test_count.connect(test_count_value_label.setText)
        test_hbox.addWidget(test_count_label)
        test_hbox.addWidget(test_count_value_label)
        test_hbox.addStretch(1)

        ## Save box
        save_hbox = QHBoxLayout()
        save_label = QLabel("Save directory: ")
        save_label.setSizePolicy(size)
        save_label_dir = QLabel("")
        save_label_dir.setSizePolicy(size)
        self.save_dir_selected.connect(save_label_dir.setText)

        save_button = QPushButton("Choose Folder")
        save_button.setSizePolicy(size)
        save_button.pressed.connect(self.save_button_pressed)

        save_hbox.addWidget(save_label)
        save_hbox.addWidget(save_label_dir)
        save_hbox.addStretch(1)
        save_hbox.addWidget(save_button)
        save_hbox.addStretch(2)

        ## Train button
        self.train_button = QPushButton("Strat Training")
        self.train_button.setMaximumWidth(300)
        self.train_button.setEnabled(False)
        self.train_button.pressed.connect(self.start_training)
        self.training.connect(self.train_button.setDisabled)

        train_info_layout = self.create_training_info()

        vbox.setSpacing(20)
        vbox.addLayout(data_hbox)
        vbox.addLayout(train_hbox)
        vbox.addLayout(test_hbox)
        vbox.addLayout(save_hbox)
        vbox.addSpacing(40)
        vbox.addWidget(self.train_button)
        vbox.addLayout(train_info_layout)
        vbox.addStretch(1)
        self.setLayout(vbox)

        self.setSizePolicy(size)
        self.setObjectName("training")

    def set_training_visible(self, visible):
        self.training_info_box["text_edit_label"].setVisible(visible)
        self.training_info_box["text_edit"].setVisible(visible)
        self.training_info_box["bar"].setVisible(visible)
        self.training_info_box["figure"].set_visible(visible)
        plt.draw()

    def create_training_info(self):
        self.training_info_box["layout"] = QVBoxLayout()

        size = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        ##QTextEdit
        self.training_info_box["text_edit_label"] = QLabel("Info: ")
        self.training_info_box["text_edit_label"].setSizePolicy(size)
        self.training_info_box["text_edit"] = QTextEdit()
        self.training_info_box["text_edit"].setFixedHeight(100)
        self.training_info_box["text_edit"].setReadOnly(True)

        ## Progress bar
        self.training_info_box["bar"] = QProgressBar()

        ## matplotlib
        self.training_info_box["figure"] = plt.figure()
        self.training_info_box["canvas"] = FigureCanvas(
            self.training_info_box["figure"])

        self.training_info_box["layout"].addWidget(
            self.training_info_box["text_edit_label"])
        self.training_info_box["layout"].addWidget(
            self.training_info_box["text_edit"])
        self.training_info_box["layout"].addWidget(
            self.training_info_box["bar"])
        self.training_info_box["layout"].addWidget(
            self.training_info_box["canvas"])
        self.set_training_visible(False)
        return self.training_info_box["layout"]

    def change_train_button_state(self):
        try:
            if self.data_dir is not None and self.save_dir is not None:
                self.train_button.setEnabled(True)
            else:
                self.train_button.setEnabled(False)
        except Exception as a:
            print(a)

    @pyqtSlot()
    def start_training(self):
        self.training.emit(True)
        self.runner_thread = QThread()
        self.worker = TrainRunner(self.data_dir, self.save_dir)
        self.worker.moveToThread(self.runner_thread)
        self.worker.msg_from_job.connect(self.handleRunner)
        self.runner_thread.started.connect(self.worker.run)
        self.runner_thread.start()

    @pyqtSlot(object)
    def handleRunner(self, obj):
        if obj.type == "command" and obj.data == "end":
            self.text_edit.append("Done.\n")
            self.runner_thread.quit()
            self.training.emit(False)

        elif obj.type == "total_epochs":
            self.set_training_visible(True)
            axis = self.training_info_box["figure"].gca()
            axis.y_limit([0, 1])
            axis.x_limit([0, obj.data])
            self.training_info_box["bar"].setMinimum(0)
            self.training_info_box["bar"].setMaximum(obj.data)

        elif obj.type == "epoch_logs":
            self.text_edit.append("accuracy: " + str(obj.data["acc"]) +
                                  ", validation_accuracy: " +
                                  str(obj.data["val_acc"]))
        elif obj.type == "text":
            self.text_edit.append(obj.data + "\n")

    @pyqtSlot()
    def data_button_pressed(self):
        data_dir = QFileDialog.getExistingDirectory(self, "Choose data folder")
        if len(data_dir):
            try:
                if not os.path.isdir(os.path.join(data_dir, "training")) or \
                            not os.path.isdir(os.path.join(data_dir, "validation")):
                    raise Exception

                len_train = 0
                for root, dir, files in os.walk(
                        os.path.join(data_dir, "training")):
                    len_train += len(files)

                len_test = 0
                for root, dir, files in os.walk(
                        os.path.join(data_dir, "validation")):
                    len_test += len(files)

                self.data_dir = data_dir
                self.data_dir_selected.emit(data_dir)
                self.test_count.emit(str(len_test))
                self.train_count.emit(str(len_train))
            except Exception as e:
                print(e)
                showError("Directory doesn't have the correct structure.")
                self.data_dir_selected.emit("")
                self.test_count.emit("")
                self.train_count.emit("")
                self.data_dir = None
            finally:
                self.change_train_button_state()

    @pyqtSlot()
    def save_button_pressed(self):
        save_dir = QFileDialog.getExistingDirectory(self, "Choose save folder")
        if len(save_dir):
            self.save_dir = save_dir
            self.save_dir_selected.emit(save_dir)
            self.change_train_button_state()
Esempio n. 25
0
class TVLinker(QWidget):
    def __init__(self, settings: QSettings, parent=None):
        super(TVLinker, self).__init__(parent)
        self.firstrun = True
        self.rows, self.cols = 0, 0
        self.parent = parent
        self.settings = settings
        self.taskbar = TaskbarProgress(self)
        self.init_styles()
        self.init_settings()
        self.init_icons()
        if sys.platform.startswith('linux'):
            notify.init(qApp.applicationName())
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(15, 15, 15, 0)
        form_groupbox = QGroupBox(self, objectName='mainForm')
        form_groupbox.setLayout(self.init_form())
        self.table = TVLinkerTable(0, 4, self)
        self.table.doubleClicked.connect(self.show_hosters)
        layout.addWidget(form_groupbox)
        layout.addWidget(self.table)
        layout.addLayout(self.init_metabar())
        self.setLayout(layout)
        qApp.setWindowIcon(self.icon_app)
        self.resize(FixedSettings.windowSize)
        self.show()
        self.start_scraping()
        self.firstrun = False

    class ProcError(Enum):
        FAILED_TO_START = 0
        CRASHED = 1
        TIMED_OUT = 2
        READ_ERROR = 3
        WRITE_ERROR = 4
        UNKNOWN_ERROR = 5

    class NotifyIcon(Enum):
        SUCCESS = ':assets/images/tvlinker.png'
        DEFAULT = ':assets/images/tvlinker.png'

    def init_threads(self, threadtype: str = 'scrape') -> None:
        if threadtype == 'scrape':
            if hasattr(self, 'scrapeThread'):
                if not sip.isdeleted(
                        self.scrapeThread) and self.scrapeThread.isRunning():
                    self.scrapeThread.terminate()
                    del self.scrapeWorker
                    del self.scrapeThread
            self.scrapeThread = QThread(self)
            self.scrapeWorker = ScrapeWorker(self.source_url, self.user_agent,
                                             self.dl_pagecount)
            self.scrapeThread.started.connect(self.show_progress)
            self.scrapeThread.started.connect(self.scrapeWorker.begin)
            self.scrapeWorker.moveToThread(self.scrapeThread)
            self.scrapeWorker.addRow.connect(self.add_row)
            self.scrapeWorker.workFinished.connect(self.scrape_finished)
            self.scrapeWorker.workFinished.connect(
                self.scrapeWorker.deleteLater, Qt.DirectConnection)
            self.scrapeWorker.workFinished.connect(self.scrapeThread.quit,
                                                   Qt.DirectConnection)
            self.scrapeThread.finished.connect(self.scrapeThread.deleteLater,
                                               Qt.DirectConnection)
        elif threadtype == 'unrestrict':
            pass

    @staticmethod
    def load_stylesheet(qssfile: str) -> None:
        if QFileInfo(qssfile).exists():
            qss = QFile(qssfile)
            qss.open(QFile.ReadOnly | QFile.Text)
            qApp.setStyleSheet(QTextStream(qss).readAll())

    def init_styles(self) -> None:
        if sys.platform == 'darwin':
            qss_stylesheet = self.get_path('%s_osx.qss' %
                                           qApp.applicationName().lower())
        else:
            qss_stylesheet = self.get_path('%s.qss' %
                                           qApp.applicationName().lower())
        TVLinker.load_stylesheet(qss_stylesheet)
        QFontDatabase.addApplicationFont(':assets/fonts/opensans.ttf')
        QFontDatabase.addApplicationFont(':assets/fonts/opensans-bold.ttf')
        QFontDatabase.addApplicationFont(':assets/fonts/opensans-semibold.ttf')
        qApp.setFont(QFont('Open Sans',
                           12 if sys.platform == 'darwin' else 10))

    def init_icons(self) -> None:
        self.icon_app = QIcon(
            self.get_path('images/%s.png' % qApp.applicationName().lower()))
        self.icon_faves_off = QIcon(':assets/images/star_off.png')
        self.icon_faves_on = QIcon(':assets/images/star_on.png')
        self.icon_refresh = QIcon(':assets/images/refresh.png')
        self.icon_menu = QIcon(':assets/images/menu.png')
        self.icon_settings = QIcon(':assets/images/cog.png')
        self.icon_updates = QIcon(':assets/images/cloud.png')

    def init_settings(self) -> None:
        self.provider = 'Scene-RLS'
        self.select_provider(0)
        self.user_agent = self.settings.value('user_agent')
        self.dl_pagecount = self.settings.value('dl_pagecount', 20, int)
        self.dl_pagelinks = FixedSettings.linksPerPage
        self.realdebrid_api_token = self.settings.value('realdebrid_apitoken')
        self.realdebrid_api_proxy = self.settings.value('realdebrid_apiproxy')
        self.download_manager = self.settings.value('download_manager')
        self.persepolis_cmd = self.settings.value('persepolis_cmd')
        self.pyload_host = self.settings.value('pyload_host')
        self.pyload_username = self.settings.value('pyload_username')
        self.pyload_password = self.settings.value('pyload_password')
        self.idm_exe_path = self.settings.value('idm_exe_path')
        self.kget_cmd = self.settings.value('kget_cmd')
        self.favorites = self.settings.value('favorites')

    def init_form(self) -> QHBoxLayout:
        self.search_field = QLineEdit(self,
                                      clearButtonEnabled=True,
                                      placeholderText='Enter search criteria')
        self.search_field.setObjectName('searchInput')
        self.search_field.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Fixed)
        self.search_field.setFocus()
        self.search_field.textChanged.connect(self.clear_filters)
        self.search_field.returnPressed.connect(
            lambda: self.filter_table(self.search_field.text()))
        self.favorites_button = QPushButton(parent=self,
                                            flat=True,
                                            cursor=Qt.PointingHandCursor,
                                            objectName='favesButton',
                                            toolTip='Favorites',
                                            checkable=True,
                                            toggled=self.filter_faves,
                                            checked=self.settings.value(
                                                'faves_filter', False, bool))
        self.refresh_button = QPushButton(parent=self,
                                          flat=True,
                                          cursor=Qt.PointingHandCursor,
                                          objectName='refreshButton',
                                          toolTip='Refresh',
                                          clicked=self.start_scraping)
        self.dlpages_field = QComboBox(self,
                                       toolTip='Pages',
                                       editable=False,
                                       cursor=Qt.PointingHandCursor)
        self.dlpages_field.addItems(
            ('10', '20', '30', '40', '50', '60', '70', '80'))
        self.dlpages_field.setCurrentIndex(
            self.dlpages_field.findText(str(self.dl_pagecount),
                                        Qt.MatchFixedString))
        self.dlpages_field.currentIndexChanged.connect(self.update_pagecount)
        self.settings_button = QPushButton(parent=self,
                                           flat=True,
                                           toolTip='Menu',
                                           objectName='menuButton',
                                           cursor=Qt.PointingHandCursor)
        self.settings_button.setMenu(self.settings_menu())
        layout = QHBoxLayout(spacing=10)
        # providerCombo = QComboBox(self, toolTip='Provider', editable=False, cursor=Qt.PointingHandCursor)
        # providerCombo.setObjectName('providercombo')
        # providerCombo.addItem(QIcon(':assets/images/provider-scenerls.png'), '')
        # providerCombo.addItem(QIcon(':assets/images/provider-tvrelease.png'), '')
        # providerCombo.setIconSize(QSize(146, 36))
        # providerCombo.setMinimumSize(QSize(160, 40))
        # providerCombo.setStyleSheet('''
        #     QComboBox, QComboBox::drop-down { background-color: transparent; border: none; margin: 5px; }
        #     QComboBox::down-arrow { image: url(:assets/images/down_arrow.png); }
        #     QComboBox QAbstractItemView { selection-background-color: #DDDDE4; }
        # ''')
        # providerCombo.currentIndexChanged.connect(self.select_provider)
        layout.addWidget(
            QLabel(pixmap=QPixmap(':assets/images/provider-scenerls.png')))
        layout.addWidget(self.search_field)
        layout.addWidget(self.favorites_button)
        layout.addWidget(self.refresh_button)
        layout.addWidget(QLabel('Pages:'))
        layout.addWidget(self.dlpages_field)
        layout.addWidget(self.settings_button)
        return layout

    @pyqtSlot(int)
    def select_provider(self, index: int):
        if index == 0:
            self.provider = 'Scene-RLS'
            self.source_url = 'http://scene-rls.net/releases/index.php?p={0}&cat=TV%20Shows'
        elif index == 1:
            self.provider = 'TV-Release'
            self.source_url = 'http://tv-release.pw/?cat=TV'
        self.setWindowTitle('%s :: %s' %
                            (qApp.applicationName(), self.provider))

    def settings_menu(self) -> QMenu:
        settings_action = QAction(self.icon_settings,
                                  'Settings',
                                  self,
                                  triggered=self.show_settings)
        updates_action = QAction(self.icon_updates,
                                 'Check for updates',
                                 self,
                                 triggered=self.check_update)
        aboutqt_action = QAction('About Qt', self, triggered=qApp.aboutQt)
        about_action = QAction('About %s' % qApp.applicationName(),
                               self,
                               triggered=self.about_app)
        menu = QMenu()
        menu.addAction(settings_action)
        menu.addAction(updates_action)
        menu.addSeparator()
        menu.addAction(aboutqt_action)
        menu.addAction(about_action)
        return menu

    def init_metabar(self) -> QHBoxLayout:
        self.meta_template = 'Total number of links retrieved: <b>%i</b> / <b>%i</b>'
        self.progress = QProgressBar(parent=self,
                                     minimum=0,
                                     maximum=(self.dl_pagecount *
                                              self.dl_pagelinks),
                                     visible=False)
        self.taskbar.setProgress(0.0, True)
        if sys.platform == 'win32':
            self.win_taskbar_button = QWinTaskbarButton(self)

        self.meta_label = QLabel(textFormat=Qt.RichText,
                                 alignment=Qt.AlignRight,
                                 objectName='totals')
        self.update_metabar()
        layout = QHBoxLayout()
        layout.setContentsMargins(10, 5, 10, 10)
        layout.addWidget(self.progress, Qt.AlignLeft)
        layout.addWidget(self.meta_label, Qt.AlignRight)
        return layout

    @pyqtSlot()
    def check_update(self) -> None:
        QDesktopServices.openUrl(QUrl(FixedSettings.latest_release_url))

    @pyqtSlot()
    def show_settings(self) -> None:
        settings_win = Settings(self, self.settings)
        settings_win.exec_()

    def update_metabar(self) -> bool:
        rowcount = self.table.rowCount()
        self.meta_label.setText(
            self.meta_template %
            (rowcount, self.dl_pagecount * self.dl_pagelinks))
        self.progress.setValue(rowcount)
        self.taskbar.setProgress(rowcount / self.progress.maximum())
        if sys.platform == 'win32':
            self.win_taskbar_button.progress().setValue(self.progress.value())
        return True

    def start_scraping(self) -> None:
        self.init_threads('scrape')
        self.rows = 0
        self.table.clearContents()
        self.table.setRowCount(0)
        self.table.setSortingEnabled(False)
        self.update_metabar()
        self.scrapeThread.start()

    @pyqtSlot()
    def about_app(self) -> None:
        about_html = '''<style>
        a { color:#441d4e; text-decoration:none; font-weight:bold; }
        a:hover { text-decoration:underline; }
    </style>
    <p style="font-size:24pt; font-weight:bold; color:#6A687D;">%s</p>
    <p>
        <span style="font-size:13pt;"><b>Version: %s</b></span>
        <span style="font-size:10pt;position:relative;left:5px;">( %s )</span>
    </p>
    <p style="font-size:13px;">
        Copyright &copy; %s <a href="mailto:[email protected]">Pete Alexandrou</a>
        <br/>
        Web: <a href="%s">%s</a>
    </p>
    <p style="font-size:11px;">
        This program is free software; you can redistribute it and/or
        modify it under the terms of the GNU General Public License
        as published by the Free Software Foundation; either version 2
        of the License, or (at your option) any later version.
    </p>''' % (qApp.applicationName(), qApp.applicationVersion(),
               platform.architecture()[0], datetime.now().year,
               qApp.organizationDomain(), qApp.organizationDomain())
        QMessageBox.about(self, 'About %s' % qApp.applicationName(),
                          about_html)

    @pyqtSlot(int)
    def update_pagecount(self, index: int) -> None:
        self.dl_pagecount = int(self.dlpages_field.itemText(index))
        self.scrapeWorker.maxpages = self.dl_pagecount
        self.progress.setMaximum(self.dl_pagecount * self.dl_pagelinks)
        self.settings.setValue('dl_pagecount', self.dl_pagecount)
        if sys.platform == 'win32':
            self.win_taskbar_button.progress().setMaximum(self.dl_pagecount *
                                                          self.dl_pagelinks)
        if self.scrapeThread.isRunning():
            self.scrapeThread.requestInterruption()
        self.start_scraping()

    @pyqtSlot()
    def show_progress(self):
        self.progress.show()
        self.taskbar.setProgress(0.0, True)
        if sys.platform == 'win32':
            self.win_taskbar_button.setWindow(self.windowHandle())
            self.win_taskbar_button.progress().setRange(
                0, self.dl_pagecount * self.dl_pagelinks)
            self.win_taskbar_button.progress().setVisible(True)
            self.win_taskbar_button.progress().setValue(self.progress.value())

    @pyqtSlot()
    def scrape_finished(self) -> None:
        self.progress.hide()
        self.taskbar.setProgress(0.0, False)
        if sys.platform == 'win32':
            self.win_taskbar_button.progress().setVisible(False)
        self.table.setSortingEnabled(True)
        self.filter_table(text='')

    @pyqtSlot(list)
    def add_row(self, row: list) -> None:
        if self.scrapeThread.isInterruptionRequested():
            self.scrapeThread.terminate()
        else:
            self.cols = 0
            self.table.setRowCount(self.rows + 1)
            if self.table.cursor() != Qt.PointingHandCursor:
                self.table.setCursor(Qt.PointingHandCursor)
            for item in row:
                table_item = QTableWidgetItem(item)
                table_item.setToolTip(
                    '%s\n\nDouble-click to view hoster links.' % row[1])
                table_item.setFont(QFont('Open Sans', weight=QFont.Normal))
                if self.cols == 2:
                    if sys.platform == 'win32':
                        table_item.setFont(
                            QFont('Open Sans Semibold', pointSize=10))
                    elif sys.platform == 'darwin':
                        table_item.setFont(
                            QFont('Open Sans Bold', weight=QFont.Bold))
                    else:
                        table_item.setFont(
                            QFont('Open Sans',
                                  weight=QFont.DemiBold,
                                  pointSize=10))
                    table_item.setText('  ' + table_item.text())
                elif self.cols in (0, 3):
                    table_item.setTextAlignment(Qt.AlignCenter)
                self.table.setItem(self.rows, self.cols, table_item)
                self.update_metabar()
                self.cols += 1
            self.rows += 1

    @pyqtSlot(list)
    def add_hosters(self, links: list) -> None:
        self.hosters_win.show_hosters(links)

    @pyqtSlot(QModelIndex)
    def show_hosters(self, index: QModelIndex) -> None:
        qApp.setOverrideCursor(Qt.BusyCursor)
        self.hosters_win = HosterLinks(self)
        self.hosters_win.downloadLink.connect(self.download_link)
        self.hosters_win.copyLink.connect(self.copy_download_link)
        self.links = HostersThread(
            self.table.item(self.table.currentRow(), 1).text(),
            self.user_agent)
        self.links.setHosters.connect(self.add_hosters)
        self.links.noLinks.connect(self.no_links)
        self.links.start()

    @pyqtSlot()
    def no_links(self) -> None:
        self.hosters_win.loading_progress.cancel()
        self.hosters_win.close()
        QMessageBox.warning(
            self, 'No Links Available',
            'No links are available yet for the chosen TV show. ' +
            'This is most likely due to the files still being uploaded. This is normal if the '
            +
            'link was published 30-45 mins ago.\n\nPlease check back again in 10-15 minutes.'
        )

    @pyqtSlot(bool)
    def filter_faves(self, checked: bool) -> None:
        self.settings.setValue('faves_filter', checked)
        # if hasattr(self, 'scrapeWorker') and (sip.isdeleted(self.scrapeWorker) or self.scrapeWorker.complete):
        if not self.firstrun:
            self.filter_table()

    @pyqtSlot(str)
    @pyqtSlot()
    def filter_table(self, text: str = '') -> None:
        filters = []
        if self.favorites_button.isChecked():
            filters = self.favorites
            self.table.sortItems(2, Qt.AscendingOrder)
        else:
            self.table.sortItems(0, Qt.DescendingOrder)
        if len(text):
            filters.append(text)
        if not len(filters) or not hasattr(self, 'valid_rows'):
            self.valid_rows = []
        for search_term in filters:
            for item in self.table.findItems(search_term, Qt.MatchContains):
                self.valid_rows.append(item.row())
        for row in range(0, self.table.rowCount()):
            if not len(filters):
                self.table.showRow(row)
            else:
                if row not in self.valid_rows:
                    self.table.hideRow(row)
                else:
                    self.table.showRow(row)

    @pyqtSlot()
    def clear_filters(self):
        if not len(self.search_field.text()):
            self.filter_table('')

    @pyqtSlot(bool)
    def aria2_confirmation(self, success: bool) -> None:
        qApp.restoreOverrideCursor()
        if success:
            if sys.platform.startswith('linux'):
                self.notify(
                    title=qApp.applicationName(),
                    msg='Your download link has been unrestricted and now ' +
                    'queued in Aria2 RPC Daemon',
                    icon=self.NotifyIcon.SUCCESS)
            else:
                QMessageBox.information(
                    self, qApp.applicationName(),
                    'Download link has been queued in Aria2.', QMessageBox.Ok)
        else:
            QMessageBox.critical(
                self, 'Aria2 RPC Daemon',
                'Could not connect to Aria2 RPC Daemon. ' +
                'Check your %s settings and try again.' %
                qApp.applicationName(), QMessageBox.Ok)

    @pyqtSlot(str)
    def download_link(self, link: str) -> None:
        if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \
            and 'rdeb.io' not in link:
            qApp.setOverrideCursor(Qt.BusyCursor)
            self.unrestrict_link(link, True)
        else:
            if self.download_manager == 'aria2':
                self.aria2 = Aria2Thread(settings=self.settings, link_url=link)
                self.aria2.aria2Confirmation.connect(self.aria2_confirmation)
                self.aria2.start()
                self.hosters_win.close()
            elif self.download_manager == 'pyload':
                self.pyload_conn = PyloadConnection(self.pyload_host,
                                                    self.pyload_username,
                                                    self.pyload_password)
                pid = self.pyload_conn.addPackage(name='TVLinker',
                                                  links=[link])
                qApp.restoreOverrideCursor()
                self.hosters_win.close()
                if sys.platform.startswith('linux'):
                    self.notify(title='Download added to %s' %
                                self.download_manager,
                                icon=self.NotifyIcon.SUCCESS)
                else:
                    QMessageBox.information(
                        self, self.download_manager,
                        'Your link has been queued in %s.' %
                        self.download_manager, QMessageBox.Ok)
                # open_pyload = msgbox.addButton('Open pyLoad', QMessageBox.AcceptRole)
                # open_pyload.clicked.connect(self.open_pyload)
            elif self.download_manager in ('kget', 'persepolis'):
                provider = self.kget_cmd if self.download_manager == 'kget' else self.persepolis_cmd
                cmd = '{0} "{1}"'.format(provider, link)
                if self.cmdexec(cmd):
                    qApp.restoreOverrideCursor()
                    self.hosters_win.close()
                    if sys.platform.startswith('linux'):
                        self.notify(title='Download added to %s' %
                                    self.download_manager,
                                    icon=self.NotifyIcon.SUCCESS)
                    else:
                        QMessageBox.information(
                            self, self.download_manager,
                            'Your link has been queued in %s.' %
                            self.download_manager, QMessageBox.Ok)
            elif self.download_manager == 'idm':
                cmd = '"%s" /n /d "%s"' % (self.idm_exe_path, link)
                if self.cmdexec(cmd):
                    qApp.restoreOverrideCursor()
                    self.hosters_win.close()
                    QMessageBox.information(
                        self, 'Internet Download Manager',
                        'Your link has been queued in IDM.')
                else:
                    print('IDM QProcess error = %s' %
                          self.ProcError(self.idm.error()).name)
                    qApp.restoreOverrideCursor()
                    self.hosters_win.close()
                    QMessageBox.critical(
                        self, 'Internet Download Manager',
                        '<p>Could not connect to your local IDM application instance. '
                        +
                        'Please check your settings and ensure the IDM executable path is correct '
                        +
                        'according to your installation.</p><p>Error Code: %s</p>'
                        % self.ProcError(self.idm.error()).name,
                        QMessageBox.Ok)
            else:
                dlpath, _ = QFileDialog.getSaveFileName(
                    self, 'Save File',
                    link.split('/')[-1])
                if dlpath != '':
                    self.directdl_win = DirectDownload(parent=self)
                    self.directdl = DownloadThread(link_url=link,
                                                   dl_path=dlpath)
                    self.directdl.dlComplete.connect(
                        self.directdl_win.download_complete)
                    if sys.platform.startswith('linux'):
                        self.directdl.dlComplete.connect(
                            lambda: self.notify(qApp.applicationName(
                            ), 'Download complete', self.NotifyIcon.SUCCESS))
                    else:
                        self.directdl.dlComplete.connect(
                            lambda: QMessageBox.information(
                                self, qApp.applicationName(),
                                'Download complete', QMessageBox.Ok))
                    self.directdl.dlProgressTxt.connect(
                        self.directdl_win.update_progress_label)
                    self.directdl.dlProgress.connect(
                        self.directdl_win.update_progress)
                    self.directdl_win.cancelDownload.connect(
                        self.cancel_download)
                    self.directdl.start()
                    self.hosters_win.close()

    def _init_notification_icons(self):
        for icon in self.NotifyIcon:
            icon_file = QPixmap(icon.value, 'PNG')
            icon_file.save(
                os.path.join(FixedSettings.config_path,
                             os.path.basename(icon.value)), 'PNG', 100)

    def notify(self,
               title: str,
               msg: str = '',
               icon: Enum = None,
               urgency: int = 1) -> bool:
        icon_path = icon.value if icon is not None else self.NotifyIcon.DEFAULT.value
        icon_path = os.path.join(FixedSettings.config_path,
                                 os.path.basename(icon_path))
        if not os.path.exists(icon_path):
            self._init_notification_icons()
        notification = notify.Notification(title, msg, icon_path)
        notification.set_urgency(urgency)
        return notification.show()

    def cmdexec(self, cmd: str) -> bool:
        self.proc = QProcess()
        self.proc.setProcessChannelMode(QProcess.MergedChannels)
        if hasattr(self.proc, 'errorOccurred'):
            self.proc.errorOccurred.connect(lambda error: print(
                'Process error = %s' % self.ProcError(error).name))
        if self.proc.state() == QProcess.NotRunning:
            self.proc.start(cmd)
            self.proc.waitForFinished(-1)
            rc = self.proc.exitStatus(
            ) == QProcess.NormalExit and self.proc.exitCode() == 0
            self.proc.deleteLater()
            return rc
        return False

    @pyqtSlot()
    def cancel_download(self) -> None:
        self.directdl.cancel_download = True
        self.directdl.quit()
        self.directdl.deleteLater()

    def open_pyload(self) -> None:
        QDesktopServices.openUrl(QUrl(self.pyload_config.host))

    @pyqtSlot(str)
    def copy_download_link(self, link: str) -> None:
        if len(self.realdebrid_api_token) > 0 and 'real-debrid.com' not in link \
            and 'rdeb.io' not in link:
            qApp.setOverrideCursor(Qt.BusyCursor)
            self.unrestrict_link(link, False)
        else:
            clip = qApp.clipboard()
            clip.setText(link)
            self.hosters_win.close()
            qApp.restoreOverrideCursor()

    def unrestrict_link(self, link: str, download: bool = True) -> None:
        caller = inspect.stack()[1].function
        self.realdebrid = RealDebridThread(
            settings=self.settings,
            api_url=FixedSettings.realdebrid_api_url,
            link_url=link,
            action=RealDebridThread.RealDebridAction.UNRESTRICT_LINK)
        self.realdebrid.errorMsg.connect(self.error_handler)
        if download:
            self.realdebrid.unrestrictedLink.connect(self.download_link)
        else:
            self.realdebrid.unrestrictedLink.connect(self.copy_download_link)
        self.realdebrid.start()

    def closeEvent(self, event: QCloseEvent) -> None:
        if hasattr(self, 'scrapeThread'):
            if not sip.isdeleted(
                    self.scrapeThread) and self.scrapeThread.isRunning():
                self.scrapeThread.requestInterruption()
                self.scrapeThread.quit()
        qApp.quit()

    def error_handler(self, props: list) -> None:
        qApp.restoreOverrideCursor()
        QMessageBox.critical(self, props[0], props[1], QMessageBox.Ok)

    @staticmethod
    def get_path(path: str = None, override: bool = False) -> str:
        if override:
            if getattr(sys, 'frozen', False):
                return os.path.join(sys._MEIPASS, path)
            return os.path.join(QFileInfo(__file__).absolutePath(), path)
        return ':assets/%s' % path

    @staticmethod
    def get_version(filename: str = '__init__.py') -> str:
        with open(TVLinker.get_path(filename, override=True), 'r') as initfile:
            for line in initfile.readlines():
                m = re.match('__version__ *= *[\'](.*)[\']', line)
                if m:
                    return m.group(1)
Esempio n. 26
0
class Paprika:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface

        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)

        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'Paprika_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Paprika')
        self.toolbar = self.iface.addToolBar(u'Paprika')
        self.toolbar.setObjectName(u'Paprika')
        self.pluginIsActive = False
        self.dockwidget = None
        self.raster_info = {}
        self.extent_view = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('Paprika', message)

    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        icon_path = os.path.join(BASE_DIR, 'icon.png')
        self.add_action(
            icon_path,
            text=self.tr('Paprika Toolbox'),
            callback=self.run,
            parent=self.iface.mainWindow())

    def onClosePlugin(self):
        """Cleanup necessary items here when plugin dockwidget is closed"""
        self.dockwidget.closingPlugin.disconnect(self.onClosePlugin)
        self.dockwidget.mMapLayerComboBox_IMPLUVIUM.layerChanged.disconnect(self.update_raster_info)
        if self.extent_view is not None:
            self.iface.mapCanvas().scene().removeItem(self.extent_view)
            self.extent_view = None
        self.pluginIsActive = False

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        if self.extent_view is not None:
            self.iface.mapCanvas().scene().removeItem(self.extent_view)
            self.extent_view = None
        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr('&Paprika'),
                action)
            self.iface.removeToolBarIcon(action)
        del self.toolbar

    def run(self):
        """Run method that loads and starts the plugin"""
        if not self.pluginIsActive:
            self.pluginIsActive = True

            if self.dockwidget is None:
                self.dockwidget = Ui_PaprikaDockWidgetBase()

            self.dockwidget.pushButton_Aide.clicked.connect(self.open_help)
            self.dockwidget.pushButton_methodo.clicked.connect(self.download_methodo)
            self.dockwidget.pushButton_lancerCarteP.clicked.connect(self.carte_p)
            self.dockwidget.pushButton_lancerCarteR.clicked.connect(self.carte_r)
            self.dockwidget.pushButton_lancerCarteI.clicked.connect(self.carte_i)
            self.dockwidget.pushButton_lancerCarteKa.clicked.connect(self.carte_ka)
            self.dockwidget.pushButton_lancerCarteFinale.clicked.connect(self.carte_finale)
            self.dockwidget.toolButton_dossier_travail.clicked.connect(self.open_directory)
            self.dockwidget.lineEdit_dossier_travail.setText(QSettings().value('Paprika_toolbox/current_directory', ''))
            self.dockwidget.pushButton_Apropos.clicked.connect(self.open_a_propos)

            # connect to provide cleanup on closing of dockwidget
            self.dockwidget.closingPlugin.connect(self.onClosePlugin)

            self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dockwidget)
            self.dockwidget.show()
            
            #filtrage et peuplement des ComboBox des layers
            self.dockwidget.mMapLayerComboBox_IMPLUVIUM.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_SOL.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_EPIKARST.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_ROCHE.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_STRUCTURE.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_KARST_FEATURES.setFilters(QgsMapLayerProxyModel.VectorLayer)
            self.dockwidget.mMapLayerComboBox_ZNS.setFilters(QgsMapLayerProxyModel.RasterLayer)
            self.dockwidget.mMapLayerComboBox_PENTE.setFilters(QgsMapLayerProxyModel.RasterLayer)
            self.dockwidget.mMapLayerComboBox_CartePF.setFilters(QgsMapLayerProxyModel.RasterLayer)
            self.dockwidget.mMapLayerComboBox_CarteRF.setFilters(QgsMapLayerProxyModel.RasterLayer)
            self.dockwidget.mMapLayerComboBox_CarteIF.setFilters(QgsMapLayerProxyModel.RasterLayer)
            self.dockwidget.mMapLayerComboBox_CarteKaF.setFilters(QgsMapLayerProxyModel.RasterLayer)
            
            # peuplement de la comboBox du critere de Mangin
            self.dockwidget.comboBox_MANGIN.clear()
            self.dockwidget.comboBox_MANGIN.addItems(['1', '2', '3', '4'])
        
            # peuplement des ComboBox des champs et gestion des criteres optionnels
            self.dockwidget.mMapLayerComboBox_IMPLUVIUM.layerChanged.connect(self.update_raster_info)
            self.dockwidget.cb_show_extent.toggled.connect(self.on_show_extent)
                #SOL
            self.dockwidget.mFieldComboBox_SOL.setFilters(QgsFieldProxyModel.Numeric)
            self.dockwidget.mFieldComboBox_SOL.setLayer(self.dockwidget.mMapLayerComboBox_SOL.currentLayer())
            self.dockwidget.mMapLayerComboBox_SOL.layerChanged.connect(self.dockwidget.mFieldComboBox_SOL.setLayer)
                #EPIKARST
            self.dockwidget.mFieldComboBox_EPIKARST.setFilters(QgsFieldProxyModel.Numeric)
            self.dockwidget.mFieldComboBox_EPIKARST.setLayer(self.dockwidget.mMapLayerComboBox_EPIKARST.currentLayer())
            self.dockwidget.mMapLayerComboBox_EPIKARST.layerChanged.connect(self.dockwidget.mFieldComboBox_EPIKARST.setLayer)
                #SINKING STREAM CATCHMENT
            self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.setFilters(QgsFieldProxyModel.Numeric)
            self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.setLayer(self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.currentLayer())
            self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.layerChanged.connect(self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.setLayer)
                #LITHOLOGY
            self.dockwidget.mFieldComboBox_ROCHE.setFilters(QgsFieldProxyModel.Numeric)
            self.dockwidget.mFieldComboBox_ROCHE.setLayer(self.dockwidget.mMapLayerComboBox_ROCHE.currentLayer())
            self.dockwidget.mMapLayerComboBox_ROCHE.layerChanged.connect(self.dockwidget.mFieldComboBox_ROCHE.setLayer)
                #KARST FEATURES I
            self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.setFilters(QgsFieldProxyModel.Numeric)
            self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.setLayer(self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.currentLayer())
            self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.layerChanged.connect(self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.setLayer)
            # mise a jour du total de la ponderation (Final Map)
            self.dockwidget.spinBox_PondP.setValue(int(QSettings().value('Paprika_toolbox/pondP', 25)))
            self.dockwidget.spinBox_PondR.setValue(int(QSettings().value('Paprika_toolbox/pondR', 20)))
            self.dockwidget.spinBox_PondI.setValue(int(QSettings().value('Paprika_toolbox/pondI', 30)))
            self.dockwidget.spinBox_PondKa.setValue(int(QSettings().value('Paprika_toolbox/pondKa', 25)))
            self.dockwidget.spinBox_PondP.valueChanged.connect(self.calcul_somme_pond)
            self.dockwidget.spinBox_PondR.valueChanged.connect(self.calcul_somme_pond)
            self.dockwidget.spinBox_PondI.valueChanged.connect(self.calcul_somme_pond)
            self.dockwidget.spinBox_PondKa.valueChanged.connect(self.calcul_somme_pond)
            self.calcul_somme_pond()
            self.update_raster_info()

    def calcul_somme_pond(self):
        P = self.dockwidget.spinBox_PondP.value()
        R = self.dockwidget.spinBox_PondR.value()
        I = self.dockwidget.spinBox_PondI.value()
        Ka = self.dockwidget.spinBox_PondKa.value()
        new_value = P + R + I + Ka
        self.dockwidget.label_sum_ponderation.setText(str(new_value) + ' %')
        if new_value == 100:
            self.dockwidget.pushButton_lancerCarteFinale.setEnabled(True)
        else:
            self.dockwidget.pushButton_lancerCarteFinale.setEnabled(False)

    def open_directory(self):
        """choose a directory to save the generated files"""
        directory = QFileDialog.getExistingDirectory(self.dockwidget.toolButton_dossier_travail,
                                                     "Choose a directory to work with",
                                                     QgsProject.instance().fileName(),
                                                     QFileDialog.ShowDirsOnly)
        self.dockwidget.lineEdit_dossier_travail.setText(str(QtCore.QDir.toNativeSeparators(directory)))
        QSettings().setValue('Paprika_toolbox/current_directory', str(QtCore.QDir.toNativeSeparators(directory)))
    
    def update_raster_info(self):
        """Get raster extent and resolution from impluvium user choice and save it in the instance for use"""
        self.raster_info['resolution_x'] = self.dockwidget.spb_resolution.value()
        self.raster_info['resolution_y'] = self.dockwidget.spb_resolution.value()
        layer_impluvium = self.dockwidget.mMapLayerComboBox_IMPLUVIUM.currentLayer()
        if layer_impluvium and layer_impluvium.isValid():
            self.raster_info['projection_wkt'] = layer_impluvium.crs().toWkt()
            feature_impluvium = next(layer_impluvium.getFeatures())
            geom = feature_impluvium.geometry().buffer(1000, 5).boundingBox()
            Xmin = geom.xMinimum()
            Ymin = geom.yMinimum()
            Xmax = geom.xMaximum()
            Ymax = geom.yMaximum()
            self.raster_info['extent'] = {}
            self.raster_info['extent']['Xmin'] = Xmin
            self.raster_info['extent']['Ymin'] = Ymin
            self.raster_info['extent']['Xmax'] = Xmax
            self.raster_info['extent']['Ymax'] = Ymax
            self.raster_info['extent']['str_extent'] = ', '.join([str(Xmin), str(Xmax), str(Ymax), str(Ymin)])
            self.raster_info['size_x'] = int(abs(Xmax - Xmin)/self.raster_info['resolution_x'])
            self.raster_info['size_y'] = int(abs(Ymax - Ymin)/self.raster_info['resolution_y'])
            if self.dockwidget.cb_show_extent.isChecked():
                if self.extent_view is not None:
                    self.iface.mapCanvas().scene().removeItem(self.extent_view)
                    self.extent_view = QgsRubberBand(self.iface.mapCanvas())
                    self.extent_view.addGeometry(QgsGeometry.fromRect(QgsRectangle(Xmin, Ymin, Xmax, Ymax)))
                    self.extent_view.setColor(QColor('#A43C27'))
                else:
                    self.extent_view = QgsRubberBand(self.iface.mapCanvas())
                    self.extent_view.addGeometry(QgsGeometry.fromRect(QgsRectangle(Xmin, Ymin, Xmax, Ymax)))
                    self.extent_view.setColor(QColor('#A43C27'))

    def on_show_extent(self):
        if self.dockwidget.cb_show_extent.isChecked():
            if 'extent' in self.raster_info:
                corner = self.raster_info['extent']
                self.extent_view = QgsRubberBand(self.iface.mapCanvas())
                self.extent_view.addGeometry(QgsGeometry.fromRect(QgsRectangle(corner['Xmin'], corner['Ymin'], corner['Xmax'], corner['Ymax'])))
                self.extent_view.setColor(QColor('#A43C27'))
        else:
            if self.extent_view is not None:
                self.iface.mapCanvas().scene().removeItem(self.extent_view)
                self.extent_view = None

    def carte_p(self):
        """test les parametres et lance la generation de la carte P"""
        if self.dockwidget.mFieldComboBox_SOL.currentField() == u'':
            return self.showdialog('The index Field of Soil Protection Layer is not set...', 'Field issue...')
        # controle la validite des occurences du champ index
        value_sol = [feature.attribute(self.dockwidget.mFieldComboBox_SOL.currentField()) for feature in self.dockwidget.mMapLayerComboBox_SOL.currentLayer().getFeatures()]
        if min(value_sol) < 0 or max(value_sol) > 4 or len(value_sol) == 0 :
            return self.showdialog('The index Field of Soil Cover Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...')

        if self.dockwidget.checkBox_Epikarst.isChecked():
            # controle que la comboBox du champ Epikarst est bien remplie
            if self.dockwidget.mFieldComboBox_EPIKARST.currentField() == u'':
                return self.showdialog('The index Field of Epikarst Layer is not set...', 'Field issue...')
            # controle la validite des occurences du champ index
            value_epikarst = [feature.attribute(self.dockwidget.mFieldComboBox_EPIKARST.currentField()) for feature in self.dockwidget.mMapLayerComboBox_EPIKARST.currentLayer().getFeatures()]
            if min(value_epikarst) < 0 or max(value_epikarst) > 4 or len(value_epikarst) == 0 :
                return self.showdialog('The index Field of Epikarst Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...')

        if self.dockwidget.checkBox_Sinking.isChecked():
            # controle que la comboBox du champ Sinking Stream Catchment est bien remplie
            if self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.currentField() == u'':
                return self.showdialog('The index Field of Sinking Stream Catchment Layer is not set...', 'Field issue...')
            # controle la validite des occurences des champs index
            value_sinking = [feature.attribute(self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.currentField()) for feature in self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.currentLayer().getFeatures()]
            if min(value_sinking) < 0 or max(value_sinking) > 4 or len(value_sinking) == 0 :
                return self.showdialog('The index''s field of Sinking catchment Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...')
                
        sol = self.dockwidget.mMapLayerComboBox_SOL.currentLayer()
        field_sol = self.dockwidget.mFieldComboBox_SOL.currentField()
            #EPIKARST
        if self.dockwidget.checkBox_Epikarst.isChecked():
            epikarst = self.dockwidget.mMapLayerComboBox_EPIKARST.currentLayer()
            field_epikarst = self.dockwidget.mFieldComboBox_EPIKARST.currentField()
        else :
            epikarst = None
            field_epikarst = None
            
            #SINKING CATCHMENT
        if self.dockwidget.checkBox_Sinking.isChecked():
            sinking = self.dockwidget.mMapLayerComboBox_SINKING_CATCHMENT.currentLayer()
            field_sinking = self.dockwidget.mFieldComboBox_SINKING_CATCHMENT.currentField()
        else : 
            sinking = None
            field_sinking = None
        #lance la generation de la carte P
        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name() == "P factor": 
                QgsProject.instance().removeMapLayers( [lyr.id()])

        self.carte_p_worker = WorkerCarteP(self.raster_info,
                            self.dockwidget.lineEdit_dossier_travail.text(),
                            self.dockwidget.mMapLayerComboBox_ZNS.currentLayer(),
                            sol,
                            field_sol,
                            epikarst,
                            field_epikarst,
                            sinking,
                            field_sinking)
        self.carte_p_thread = QThread()
        self.carte_p_worker.results.connect(self.on_carte_p_results)
        self.carte_p_worker.progress.connect(self.on_progress)
        self.carte_p_worker.error.connect(self.on_error)
        self.carte_p_worker.finished.connect(self.on_carte_p_finished)

        self.carte_p_worker.moveToThread(self.carte_p_thread)
        self.carte_p_thread.started.connect(self.carte_p_worker.run)
        self.carte_p_thread.start()

    def on_carte_p_results(self):
        #genere le style et charge le tif dans QGIS avec un message
        lay_carteP = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/P_factor.tif','P factor')
        self.set_raster_style(lay_carteP)
        QgsProject.instance().addMapLayer(lay_carteP)
        self.showdialog('P factor map created wih success!', 'Well done!')

    def on_carte_p_finished(self):
        self.carte_p_thread.quit()
        self.carte_p_thread.wait()

    def carte_r(self):
        """teste les parametres et lance la generation de la carte R"""
        if self.dockwidget.mFieldComboBox_ROCHE.currentField() == u'':
                return self.showdialog('The index Field of Lithology Layer is not set...', 'Field issue...')
        value_lithology = [feature.attribute(self.dockwidget.mFieldComboBox_ROCHE.currentField()) for feature in self.dockwidget.mMapLayerComboBox_ROCHE.currentLayer().getFeatures()]
        if min(value_lithology) < 0 or max(value_lithology) > 4 or len(value_lithology) == 0 :
            return self.showdialog('The index Field of Lithology Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...')

        lithology = self.dockwidget.mMapLayerComboBox_ROCHE.currentLayer()
        field_lithology = self.dockwidget.mFieldComboBox_ROCHE.currentField()
        if self.dockwidget.checkBox_STRUCTURE.isChecked():
            structure = self.dockwidget.mMapLayerComboBox_STRUCTURE.currentLayer()
        else:
            structure = None

        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name() == "R factor": 
                QgsProject.instance().removeMapLayers( [lyr.id()] )

        self.carte_r_worker = WorkerCarteR(self.dockwidget.lineEdit_dossier_travail.text(),
                                           self.raster_info,
                                           lithology,
                                           field_lithology,
                                           structure)
        self.carte_r_thread = QThread()
        self.carte_r_worker.results.connect(self.on_carte_r_results)
        self.carte_r_worker.progress.connect(self.on_progress)
        self.carte_r_worker.error.connect(self.on_error)
        self.carte_r_worker.finished.connect(self.on_carte_r_finished)

        self.carte_r_worker.moveToThread(self.carte_r_thread)
        self.carte_r_thread.started.connect(self.carte_r_worker.run)
        self.carte_r_thread.start()

    def on_carte_r_results(self):
        lay_carteR = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text()) + '/R_factor.tif',
                                    'R factor')
        self.set_raster_style(lay_carteR)
        QgsProject.instance().addMapLayer(lay_carteR)
        self.showdialog('R factor map created wih success!', 'Well done!')

    def on_carte_r_finished(self):
        self.carte_p_thread.quit()
        self.carte_p_thread.wait()
            
    def carte_i(self):
        """teste les parametres et lance la generation de la carte I"""
        if self.dockwidget.checkBox_OBJETS_EXOKARSTIQUES.isChecked():
            if self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.currentField() == '':
                return self.showdialog('The index Field of Karst features Layer is not set...', 'Field issue...')

            value_objets_exokarstiques = [feature.attribute(self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.currentField()) for feature in self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.currentLayer().getFeatures()]
            if min(value_objets_exokarstiques) < 0 or max(value_objets_exokarstiques) > 4 :
                return self.showdialog('The index Field of Karst features Layer has wrong value... (not between 0 and 4 or null)', 'Index issue...')

        rules = self.generate_reclass_rules_slope(self.dockwidget.spinBox_first_threshold.value(),self.dockwidget.spinBox_second_threshold.value(),self.dockwidget.spinBox_third_threshold.value())
        pente = self.dockwidget.mMapLayerComboBox_PENTE.currentLayer()

        if self.dockwidget.checkBox_OBJETS_EXOKARSTIQUES.isChecked():
            exokarst = self.dockwidget.mMapLayerComboBox_OBJETS_EXOKARSTIQUES.currentLayer()
            field_exokarst = self.dockwidget.mFieldComboBox_OBJETS_EXOKARSTIQUES.currentField()
        else:
            exokarst = None
            field_exokarst = None

        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name() == "I Factor": 
                QgsProject.instance().removeMapLayers( [lyr.id()] )

        self.carte_i_worker = WorkerCarteI(self.dockwidget.lineEdit_dossier_travail.text(),
                                self.raster_info,
                                pente,
                                rules,
                                exokarst,
                                field_exokarst)
        self.carte_i_thread = QThread()
        self.carte_i_worker.results.connect(self.on_carte_i_results)
        self.carte_i_worker.progress.connect(self.on_progress)
        self.carte_i_worker.error.connect(self.on_error)
        self.carte_i_worker.finished.connect(self.on_carte_i_finished)

        self.carte_i_worker.moveToThread(self.carte_i_thread)
        self.carte_i_thread.started.connect(self.carte_i_worker.run)
        self.carte_i_thread.start()

    def on_carte_i_results(self):
        lay_carteI = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/I_factor.tif', 'I factor')

        self.set_raster_style(lay_carteI)
        QgsProject.instance().addMapLayer(lay_carteI)
        self.showdialog('I factor map created wih success!', 'Well done!')

    def on_carte_i_finished(self):
        self.carte_i_thread.quit()
        self.carte_i_thread.wait()

    def carte_ka(self):
        """teste les parametres et lance la generation de la carte Ka"""
        if self.dockwidget.checkBox_KARST_FEATURES.isChecked():
            karst_features = self.dockwidget.mMapLayerComboBox_KARST_FEATURES.currentLayer()
            
        else:
            karst_features = None
                
        #genere le tif
        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name() == "Ka factor": 
                QgsProject.instance().removeMapLayers([lyr.id()])

        self.carte_ka_worker = WorkerCarteKa(self.dockwidget.lineEdit_dossier_travail.text(),
                                self.raster_info,
                                int(self.dockwidget.comboBox_MANGIN.currentText()),
                                karst_features)

        self.carte_ka_thread = QThread()
        self.carte_ka_worker.results.connect(self.on_carte_ka_results)
        self.carte_ka_worker.progress.connect(self.on_progress)
        self.carte_ka_worker.error.connect(self.on_error)
        self.carte_ka_worker.finished.connect(self.on_carte_ka_finished)

        self.carte_ka_worker.moveToThread(self.carte_ka_thread)
        self.carte_ka_thread.started.connect(self.carte_ka_worker.run)
        self.carte_ka_thread.start()

    def on_carte_ka_results(self):
        lay_carteKa = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/Ka_factor.tif','Ka factor')
        self.set_raster_style(lay_carteKa)
        QgsProject.instance().addMapLayer(lay_carteKa)
        self.showdialog('Ka factor map created wih success!', 'Well done!')

    def on_carte_ka_finished(self):
        self.carte_ka_thread.quit()
        self.carte_ka_thread.wait()
    
    def carte_finale(self):
        pP=self.dockwidget.spinBox_PondP.value()
        pR=self.dockwidget.spinBox_PondR.value()
        pI=self.dockwidget.spinBox_PondI.value()
        pKa=self.dockwidget.spinBox_PondKa.value()
        QSettings().setValue('Paprika_toolbox/pondP', pP)
        QSettings().setValue('Paprika_toolbox/pondR', pR)
        QSettings().setValue('Paprika_toolbox/pondI', pI)
        QSettings().setValue('Paprika_toolbox/pondKa', pKa)

        if pI + pKa + pP + pR != 100:
            return self.showdialog('weight sum must be egal at 100%!', 'invalid weight...')
            
        #supprime la couche si elle est deja chargee et genere le tif
        for lyr in QgsProject.instance().mapLayers().values():
            if lyr.name() == "Vulnerability Map": 
                QgsProject.instance().removeMapLayers( [lyr.id()] )

        self.carte_finale_worker = WorkerCarteFinale(self.dockwidget.lineEdit_dossier_travail.text(),
                                        self.raster_info,
                                        self.dockwidget.spinBox_PondP.value(),
                                        self.dockwidget.spinBox_PondR.value(),
                                        self.dockwidget.spinBox_PondI.value(),
                                        self.dockwidget.spinBox_PondKa.value(),
                                        self.dockwidget.mMapLayerComboBox_CartePF.currentLayer(),
                                        self.dockwidget.mMapLayerComboBox_CarteRF.currentLayer(),
                                        self.dockwidget.mMapLayerComboBox_CarteIF.currentLayer(),
                                        self.dockwidget.mMapLayerComboBox_CarteKaF.currentLayer())

        self.carte_finale_thread = QThread()
        self.carte_finale_worker.results.connect(self.on_carte_finale_results)
        self.carte_finale_worker.progress.connect(self.on_progress)
        self.carte_finale_worker.error.connect(self.on_error)
        self.carte_finale_worker.finished.connect(self.on_carte_finale_finished)

        self.carte_finale_worker.moveToThread(self.carte_finale_thread)
        self.carte_finale_thread.started.connect(self.carte_finale_worker.run)
        self.carte_finale_thread.start()

    def on_carte_finale_results(self):
        lay_carteFinale = QgsRasterLayer(str(self.dockwidget.lineEdit_dossier_travail.text())+'/Vulnerability_Map.tif','Vulnerability Map')
        self.set_raster_style(lay_carteFinale)
        QgsProject.instance().addMapLayer(lay_carteFinale)
        return self.showdialog('Final map created wih success!', 'Well done!')

    def on_carte_finale_finished(self):
        self.carte_finale_thread.quit()
        self.carte_finale_thread.wait()

    def on_error(self, e):
        """If something wrong happen in the thread, show what..."""
        self.showdialog(str(e), 'Oups!')

    def on_progress(self, value, tot):
        """Update the progress bar from thread"""
        self.dockwidget.progress.setMaximum(100)
        self.dockwidget.progress.setValue((value*100/tot) + 1)
    
    def showdialog (self, text, title):
        """Just a wrapper to show message popup to user"""
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Warning)
        msg.setText(text)
        msg.setWindowTitle(title)
        msg.setStandardButtons(QMessageBox.Ok)
        msg.exec_()

    def set_raster_style(self, raster_layer):
        """Set the style for output"""
        s = QgsRasterShader()
        c = QgsColorRampShader()
        c.setColorRampType(QgsColorRampShader.Exact)
        i = []
        i.append(QgsColorRampShader.ColorRampItem(0, QColor('#FFFFFF'), '0'))
        i.append(QgsColorRampShader.ColorRampItem(1, QColor('#0040FF'), '1'))
        i.append(QgsColorRampShader.ColorRampItem(2, QColor('#A8D990'), '2'))
        i.append(QgsColorRampShader.ColorRampItem(3, QColor('#F6F085'), '3'))
        i.append(QgsColorRampShader.ColorRampItem(4, QColor('#E6A55B'), '4'))
        i.append(QgsColorRampShader.ColorRampItem(5, QColor('#A43C27'), '5'))
        c.setColorRampItemList(i)
        s.setRasterShaderFunction(c)
        ps = QgsSingleBandPseudoColorRenderer(raster_layer.dataProvider(), 1, s)
        raster_layer.setRenderer(ps)
    
    def open_a_propos(self):
        """Open About dialog"""
        a_propos = Ui_A_propos()
        a_propos.exec()
    
    def download_methodo(self):
        """Open webbrowser on official methodology page"""
        webbrowser.open_new('http://infoterre.brgm.fr/rapports/RP-57527-FR.pdf')
        webbrowser.open_new_tab('http://link.springer.com/article/10.1007/s10040-010-0688-8')
    
    def open_help(self):
        """Open pdf docs. Platform dependent"""
        if os.name == 'nt':
            os.startfile(os.path.dirname(os.path.abspath(__file__))+'/doc/Paprika_Toolbox_User_guide.pdf')
        elif os.name == 'posix':
            subprocess.call(["xdg-open", os.path.dirname(os.path.abspath(__file__))+'/doc/Paprika_Toolbox_User_guide.pdf'])
    
    def generate_reclass_rules_slope(self,first,second,third):
        """Generate the list that contains rules for slope reclassify"""
        return [-1, first, 4, first, second, 3, second, third, 2, third, 99999999, 1]
Esempio n. 27
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. 28
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. 29
0
class XAnoS_Reducer(QWidget):
    """
    This widget is developed to reduce on the fly 2D SAXS data to azimuthally averaged 1D SAXS data
    """
    def __init__(self,poniFile=None,dataFile=None, darkFile=None, maskFile=None,extractedFolder='/tmp', npt=1000, azimuthalRange=(-180.0,180.0), parent=None):
        """
        poniFile is the calibration file obtained after Q-calibration
        """
        QWidget.__init__(self,parent)
        self.setup_dict=json.load(open('./SetupData/reducer_setup.txt','r'))
        if poniFile is not None:
            self.poniFile=poniFile
        else:
            self.poniFile=self.setup_dict['poniFile']
        if maskFile is not None:
            self.maskFile=maskFile
        else:
            self.maskFile=self.setup_dict['maskFile']
        self.dataFile=dataFile
        if darkFile is None:
            self.dark_corrected=False
            self.darkFile=''
        else:
            self.darkFile=darkFile
            self.dark_corrected=True
       
        self.curDir=os.getcwd()
        
        self.extractedBaseFolder=extractedFolder
        self.npt=npt
        self.set_externally=False
        #ai=AIWidget()
        #self.layout.addWidget(ai)
        self.azimuthalRange=azimuthalRange
        self.create_UI()
        if os.path.exists(self.poniFile):
            self.openPoniFile(file=self.poniFile)
        if os.path.exists(self.maskFile):
            self.openMaskFile(file=self.maskFile)   
        self.clientRunning=False     
        
        
    def create_UI(self):
        """
        Creates the widget user interface
        """
        loadUi('UI_Forms/Data_Reduction_Client.ui',self)
        self.poniFileLineEdit.setText(str(self.poniFile))
        self.maskFileLineEdit.setText(str(self.maskFile))
        self.darkFileLineEdit.setText(str(self.darkFile))
        self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder)
        self.radialPointsLineEdit.setText(str(self.npt))
        self.openDataPushButton.clicked.connect(self.openDataFiles)
        self.reducePushButton.clicked.connect(self.reduce_multiple)
        self.openDarkPushButton.clicked.connect(self.openDarkFile)
        self.openPoniPushButton.clicked.connect(lambda x: self.openPoniFile(file=None))
        self.calibratePushButton.clicked.connect(self.calibrate)
        self.maskFileLineEdit.returnPressed.connect(self.maskFileChanged)
        self.openMaskPushButton.clicked.connect(lambda x: self.openMaskFile(file=None))
        self.createMaskPushButton.clicked.connect(self.createMask)
        self.extractedFolderPushButton.clicked.connect(self.openFolder)
        self.extractedFolderLineEdit.textChanged.connect(self.extractedFolderChanged)
        self.polCorrComboBox.currentIndexChanged.connect(self.polarizationChanged)
        self.polarizationChanged()
        self.radialPointsLineEdit.returnPressed.connect(self.nptChanged)
        self.azimuthalRangeLineEdit.returnPressed.connect(self.azimuthalRangeChanged)
        self.azimuthalRangeChanged()
        #self.statusLabel.setStyleSheet("color:rgba(0,1,0,0)")
        self.imageWidget=Image_Widget(zeros((100,100)))
        self.cakedImageWidget=Image_Widget(zeros((100,100)))
        imgNumberLabel=QLabel('Image number')
        self.imgNumberSpinBox=QSpinBox()
        self.imgNumberSpinBox.setSingleStep(1)
        self.imageWidget.imageLayout.addWidget(imgNumberLabel,row=2,col=1)
        self.imageWidget.imageLayout.addWidget(self.imgNumberSpinBox,row=2,col=2)
        self.imageView=self.imageWidget.imageView.getView()
        self.plotWidget=PlotWidget()
        self.plotWidget.setXLabel('Q, &#8491;<sup>-1</sup>',fontsize=5)
        self.plotWidget.setYLabel('Intensity',fontsize=5)
        self.tabWidget.addTab(self.plotWidget,'Reduced 1D-data')
        self.tabWidget.addTab(self.imageWidget,'Masked 2D-data')
        self.tabWidget.addTab(self.cakedImageWidget,'Reduced Caked Data')
        
        self.serverAddress=self.serverAddressLineEdit.text()
        self.startClientPushButton.clicked.connect(self.startClient)
        self.stopClientPushButton.clicked.connect(self.stopClient)
        self.serverAddressLineEdit.returnPressed.connect(self.serverAddressChanged)
        
        self.startServerPushButton.clicked.connect(self.startServer)
        self.stopServerPushButton.clicked.connect(self.stopServer)
        
    def startServer(self):
        serverAddr=self.serverAddressLineEdit.text()
        dataDir=QFileDialog.getExistingDirectory(self,'Select data folder',options=QFileDialog.ShowDirsOnly)
        self.serverStatusLabel.setText('<font color="Red">Transmitting</font>')
        QApplication.processEvents()
        self.serverThread=QThread()
        self.zeromq_server=ZeroMQ_Server(serverAddr,dataDir)
        self.zeromq_server.moveToThread(self.serverThread)
        self.serverThread.started.connect(self.zeromq_server.loop)
        self.zeromq_server.messageEmitted.connect(self.updateServerMessage)
        self.zeromq_server.folderFinished.connect(self.serverDone)
        QTimer.singleShot(0,self.serverThread.start)

    
    def updateServerMessage(self,mesg):
        #self.serverStatusLabel.setText('<font color="Red">Transmitting</font>')
        self.serverMessageLabel.setText('Server sends: %s'%mesg)
        QApplication.processEvents()
        
    def serverDone(self):
        self.serverStatusLabel.setText('<font color="Green">Idle</font>')
        self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint)
        self.serverThread.quit()
        self.serverThread.wait()
        self.serverThread.deleteLater()
        self.zeromq_server.deleteLater()
        
    def stopServer(self):
        try:
            self.zeromq_server.running=False
            self.serverStatusLabel.setText('<font color="Green">Idle</font>')
            self.zeromq_server.socket.unbind(self.zeromq_server.socket.last_endpoint)
            self.serverThread.quit()
            self.serverThread.wait()
            self.serverThread.deleteLater()
            self.zeromq_server.deleteLater()
        except:
            QMessageBox.warning(self,'Server Error','Start the server before stopping it')
        
    def enableClient(self,enable=True):
        self.startClientPushButton.setEnabled(enable)
        self.stopClientPushButton.setEnabled(enable)
        
    def enableServer(self,enable=True):
        self.startServerPushButton.setEnabled(enable)
        self.stopServerPushButton.setEnabled(enable)
        
        
    def startClient(self):
        if self.clientRunning:
            self.stopClient()
        else:
            self.clientFree=True
            self.clientRunning=True
            self.files=[]
            self.listenerThread = QThread()
            addr=self.clientAddressLineEdit.text()
            self.zeromq_listener = ZeroMQ_Listener(addr)
            self.zeromq_listener.moveToThread(self.listenerThread)
            self.listenerThread.started.connect(self.zeromq_listener.loop)
            self.zeromq_listener.messageReceived.connect(self.signal_received)
            QTimer.singleShot(0, self.listenerThread.start)
            QTimer.singleShot(0,self.clientReduce)
            self.clientStatusLabel.setText('<font color="red">Connected</font>')
            
    def stopClient(self):
        try:
            self.clientRunning=False
            self.clientFree=False
            self.zeromq_listener.messageReceived.disconnect()
            self.zeromq_listener.running=False
            self.listenerThread.quit()
            self.listenerThread.wait()
            self.listenerThread.deleteLater()
            self.zeromq_listener.deleteLater()
            self.clientStatusLabel.setText('<font color="green">Idle</font>')
        except:
            QMessageBox.warning(self,'Client Error', 'Please start the client first before closing.',QMessageBox.Ok)
        
        
    def serverAddressChanged(self):
        if self.clientRunning:
            self.startClient()
        
        
    def signal_received(self, message):
        self.clientMessageLabel.setText('Client receives: %s'%message)
        if 'dark.edf' not in message:
            self.files.append(message)
            
            
    def clientReduce(self):
        while self.clientFree:
            QApplication.processEvents()
            if len(self.files)>0:
                message=self.files[0]
                self.dataFiles=[message]
                self.dataFileLineEdit.setText(str(self.dataFiles))
                self.extractedBaseFolder=os.path.dirname(message)
                self.extractedFolder=os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text())
                if not os.path.exists(self.extractedFolder):
                    os.makedirs(self.extractedFolder)
                self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder)
                self.set_externally=True
                self.reduce_multiple()
                self.set_externally=False
                self.files.pop(0)
        
            
    def closeEvent(self, event):
        if self.clientRunning:
            self.stopClient()
        event.accept()
       
    def polarizationChanged(self):
        if self.polCorrComboBox.currentText()=='Horizontal':
            self.polarization_factor=1
        elif self.polCorrComboBox.currentText()=='Vertical':
            self.polarization_factor=-1
        elif self.polCorrComboBox.currentText()=='Circular':
            self.polarization_factor=0
        else:
            self.polarization_factor=None
            
    def createMask(self):
        """
        Opens a mask-widget to create mask file
        """
        fname=str(QFileDialog.getOpenFileName(self,'Select an image file', directory=self.curDir,filter='Image file (*.edf *.tif)')[0])
        if fname is not None or fname!='':
            img=fb.open(fname).data
            self.maskWidget=MaskWidget(img)
            self.maskWidget.saveMaskPushButton.clicked.disconnect()
            self.maskWidget.saveMaskPushButton.clicked.connect(self.save_mask)
            self.maskWidget.show()
        else:
            QMessageBox.warning(self,'File error','Please import a data file first for creating the mask',QMessageBox.Ok)
            
    def maskFileChanged(self):
        """
        Changes the mask file
        """
        maskFile=str(self.maskFileLineEdit.text())
        if str(maskFile)=='':
            self.maskFile=None
        elif os.path.exists(maskFile):
            self.maskFile=maskFile
        else:
            self.maskFile=None
            
    def save_mask(self):
        """
        Saves the entire mask combining all the shape ROIs
        """
        fname=str(QFileDialog.getSaveFileName(filter='Mask Files (*.msk)')[0])
        name,extn=os.path.splitext(fname)
        if extn=='':
            fname=name+'.msk'
        elif extn!='.msk':
            QMessageBox.warning(self,'File extension error','Please donot provide file extension other than ".msk". Thank you!')
            return
        else:
            tmpfile=fb.edfimage.EdfImage(data=self.maskWidget.full_mask_data.T,header=None)
            tmpfile.save(fname)
            self.maskFile=fname
            self.maskFileLineEdit.setText(self.maskFile)
            
    def calibrate(self):
        """
        Opens a calibartion widget to create calibration file
        """
        fname=str(QFileDialog.getOpenFileName(self,'Select calibration image',directory=self.curDir, filter='Calibration image (*.edf *.tif)')[0])
        if fname is not None:
            img=fb.open(fname).data
            if self.maskFile is not None:
                try:
                    mask=fb.open(self.maskFile).data
                except:
                    QMessageBox.warning(self,'Mask File Error','Cannot open %s.\n No masking will be done.'%self.maskFile)
                    mask=None
            else:
                mask=None
            pixel1=79.0
            pixel2=79.0
            self.calWidget=CalibrationWidget(img,pixel1,pixel2,mask=mask)
            self.calWidget.saveCalibrationPushButton.clicked.disconnect()
            self.calWidget.saveCalibrationPushButton.clicked.connect(self.save_calibration)
            self.calWidget.show()
        else:
            QMessageBox.warning(self,'File error','Please import a data file first for creating the calibration file',QMessageBox.Ok)
            
    def save_calibration(self):
        fname=str(QFileDialog.getSaveFileName(self,'Calibration file',directory=self.curDir,filter='Clibration files (*.poni)')[0])
        tfname=os.path.splitext(fname)[0]+'.poni'
        self.calWidget.applyPyFAI()
        self.calWidget.geo.save(tfname)      
        self.poniFile=tfname
        self.poniFileLineEdit.setText(self.poniFile)
        self.openPoniFile(file=self.poniFile)
        
    def openPoniFile(self,file=None):
        """
        Select and imports the calibration file
        """
        if file is None:
            self.poniFile=QFileDialog.getOpenFileName(self,'Select calibration file',directory=self.curDir,filter='Calibration file (*.poni)')[0]
            self.poniFileLineEdit.setText(self.poniFile)
        else:
            self.poniFile=file
        if os.path.exists(self.poniFile):
            self.setup_dict['poniFile']=self.poniFile
            json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w'))
            fh=open(self.poniFile,'r')
            lines=fh.readlines()
            self.calib_data={}
            for line in lines:
                if line[0]!='#':
                    key,val=line.split(': ')
                    self.calib_data[key]=float(val)
            self.dist=self.calib_data['Distance']
            self.pixel1=self.calib_data['PixelSize1']
            self.pixel2=self.calib_data['PixelSize2']
            self.poni1=self.calib_data['Poni1']
            self.poni2=self.calib_data['Poni2']
            self.rot1=self.calib_data['Rot1']
            self.rot2=self.calib_data['Rot2']
            self.rot3=self.calib_data['Rot3']
            self.wavelength=self.calib_data['Wavelength']
            self.ai=AzimuthalIntegrator(dist=self.dist,poni1=self.poni1,poni2=self.poni2,pixel1=self.pixel1,pixel2=self.pixel2,rot1=self.rot1,rot2=self.rot2,rot3=self.rot3,wavelength=self.wavelength)
            #pos=[self.poni2/self.pixel2,self.poni1/self.pixel1]
            #self.roi=cake(pos,movable=False)
            #self.roi.sigRegionChangeStarted.connect(self.endAngleChanged)
            
            #self.imageView.addItem(self.roi)
        else:
            QMessageBox.warning(self,'File error','The calibration file '+self.poniFile+' doesnot exists.',QMessageBox.Ok)                
        
    def endAngleChanged(self,evt):
        print(evt.pos())
        
        
    def nptChanged(self):
        """
        Changes the number of radial points
        """
        try:
            self.npt=int(self.radialPointsLineEdit.text())
        except:
            QMessageBox.warning(self,'Value error', 'Please input positive integers only.',QMessageBox.Ok)
            
    def azimuthalRangeChanged(self):
        """
        Changes the azimuth angular range
        """
        try:
            self.azimuthalRange=tuple(map(float, self.azimuthalRangeLineEdit.text().split(':')))
        except:
            QMessageBox.warning(self,'Value error','Please input min:max angles in floating point numbers',QMessageBox.Ok)
        
    def openDataFile(self):
        """
        Select and imports one data file
        """
        dataFile=QFileDialog.getOpenFileName(self,'Select data file',directory=self.curDir,filter='Data file (*.edf *.tif)')[0]
        if dataFile!='':
            self.dataFile=dataFile
            self.curDir=os.path.dirname(self.dataFile)
            self.dataFileLineEdit.setText(self.dataFile)
            self.data2d=fb.open(self.dataFile).data
            if self.darkFile is not None:
                self.applyDark()
            if self.maskFile is not None:
                self.applyMask()    
            self.imageWidget.setImage(self.data2d,transpose=True)
            self.tabWidget.setCurrentWidget(self.imageWidget)
            if not self.set_externally:
                self.extractedFolder=os.path.join(self.curDir,self.extractedFolderLineEdit.text())
                if not os.path.exists(self.extractedFolder):
                    os.makedirs(self.extractedFolder)
                    
    def openDataFiles(self):
        """
        Selects and imports multiple data files
        """
        self.dataFiles=QFileDialog.getOpenFileNames(self,'Select data files', directory=self.curDir,filter='Data files (*.edf *.tif)')[0]
        if len(self.dataFiles)!=0:
            self.imgNumberSpinBox.valueChanged.connect(self.imageChanged)
            self.imgNumberSpinBox.setMinimum(0)
            self.imgNumberSpinBox.setMaximum(len(self.dataFiles)-1)
            self.dataFileLineEdit.setText(str(self.dataFiles))
            self.curDir=os.path.dirname(self.dataFiles[0])
            self.extractedBaseFolder=self.curDir
            self.extractedFolder=os.path.abspath(os.path.join(self.extractedBaseFolder,self.extractedFolderLineEdit.text()))
            if not os.path.exists(self.extractedFolder):
                os.makedirs(self.extractedFolder)
            self.extractedBaseFolderLineEdit.setText(self.extractedBaseFolder)
            self.imgNumberSpinBox.setValue(0)
            self.imageChanged()
            
    def imageChanged(self):
        self.data2d=fb.open(self.dataFiles[self.imgNumberSpinBox.value()]).data
        if self.darkFile is not None:
            self.applyDark()
        if self.maskFile is not None:
            self.applyMask()    
        self.imageWidget.setImage(self.data2d,transpose=True)
            

                  
            
                
    def applyDark(self):
        if not self.dark_corrected and self.darkFile!='':
            self.dark2d=fb.open(self.darkFile).data
            self.data2d=self.data2d-self.dark2d
            self.dark_corrected=True
                
    def applyMask(self):
        self.mask2d=fb.open(self.maskFile).data
        self.data2d=self.data2d*(1+self.mask2d)/2.0
        self.mask_applied=True

    def openDarkFile(self):
        """
        Select and imports the dark file
        """
        self.darkFile=QFileDialog.getOpenFileName(self,'Select dark file',directory=self.curDir,filter='Dark file (*.edf)')[0]
        if self.darkFile!='':
            self.dark_corrected=False
            self.darkFileLineEdit.setText(self.darkFile)
            if self.dataFile is not None:
                self.data2d=fb.open(self.dataFile).data
                self.applyDark()
        
    
    def openMaskFile(self,file=None):
        """
        Select and imports the Mask file
        """
        if file is None:
            self.maskFile=QFileDialog.getOpenFileName(self,'Select mask file',directory=self.curDir,filter='Mask file (*.msk)')[0]
        else:
            self.maskFile=file
        if self.maskFile!='':
            self.mask_applied=False
            if os.path.exists(self.maskFile):
                self.curDir=os.path.dirname(self.maskFile)
                self.maskFileLineEdit.setText(self.maskFile)
                self.setup_dict['maskFile']=self.maskFile
                self.setup_dict['poniFile']=self.poniFile
                json.dump(self.setup_dict,open('./SetupData/reducer_setup.txt','w'))
            else:
                self.openMaskFile(file=None)
            if self.dataFile is not None:
                self.applyMask()
        else:
            self.maskFile=None
            self.maskFileLineEdit.clear()
            
            
        
    def openFolder(self):
        """
        Select the folder to save the reduce data
        """
        oldfolder=self.extractedBaseFolder.text()
        folder=QFileDialog.getExistingDirectory(self,'Select extracted directory',directory=self.curDir)
        if folder!='':
            self.extractedBaseFolder=folder
            self.extractedBaseFolderLineEdit.setText(folder)
            self.extractedFolder=os.path.join(folder,self.extractedFolderLineEdit.text())
            self.set_externally=True
        else:
            self.extractedBaseFolder=oldfolder
            self.extractedBaseFolderLineEdit.setText(oldfolder)
            self.extractedFolder = os.path.join(oldfolder, self.extractedFolderLineEdit.text())
            self.set_externally = True


    def extractedFolderChanged(self,txt):
        self.extractedFolder=os.path.join(self.extractedBaseFolder,txt)
        self.set_externally=True

        
        
    def reduceData(self):
        """
        Reduces the 2d data to 1d data
        """
        if (self.dataFile is not None) and (os.path.exists(self.dataFile)):
            if (self.poniFile is not None) and (os.path.exists(self.poniFile)):
#                self.statusLabel.setText('Busy')
#                self.progressBar.setRange(0, 0)
                imageData=fb.open(self.dataFile)
                #self.data2d=imageData.data
                #if self.maskFile is not None:
                #    self.applyMask()    
                #self.imageWidget.setImage(self.data2d,transpose=True)
                #self.tabWidget.setCurrentWidget(self.imageWidget)
                
                self.header=imageData.header
                try:
                    self.ai.set_wavelength(float(self.header['Wavelength'])*1e-10)
                except:
                    self.ai.set_wavelength(self.wavelength)
                #print(self.darkFile)
                if os.path.exists(self.dataFile.split('.')[0]+'_dark.edf') and self.darkCheckBox.isChecked():
                    self.darkFile=self.dataFile.split('.')[0]+'_dark.edf'
                    dark=fb.open(self.darkFile)
                    self.darkFileLineEdit.setText(self.darkFile)
                    imageDark=dark.data                                     
                    self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))])
                    self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))])
                    print("Dark File read from existing dark files")                    
                elif self.darkFile is not None and self.darkFile!='' and self.darkCheckBox.isChecked():
                    dark=fb.open(self.darkFile)
                    imageDark=dark.data                                     
                    self.header['BSDiode_corr']=max([1.0,(float(imageData.header['BSDiode'])-float(dark.header['BSDiode']))])
                    self.header['Monitor_corr']=max([1.0,(float(imageData.header['Monitor'])-float(dark.header['Monitor']))])
                    print("Dark File from memory subtracted")                
                else:
                    imageDark=None
                    try:
                        self.header['BSDiode_corr']=float(imageData.header['BSDiode'])
                        self.header['Monitor_corr']=float(imageData.header['Monitor'])
                        self.header['Transmission'] = float(imageData.header['Transmission'])
                    except:
                        self.normComboBox.setCurrentText('None')
                    print("No dark correction done")
                if str(self.normComboBox.currentText())=='BSDiode':
                    norm_factor=self.header['BSDiode_corr']#/self.header['Monitor_corr']#float(self.header[
                    # 'count_time'])
                elif str(self.normComboBox.currentText())=='TransDiode':
                    norm_factor=self.header['Transmission']*self.header['Monitor_corr']
                elif str(self.normComboBox.currentText())=='Monitor':
                    norm_factor=self.header['Monitor_corr']
                elif str(self.normComboBox.currentText())=='Image Sum':
                    norm_factor=sum(imageData.data)
                else:
                    norm_factor=1.0
                    
                if self.maskFile is not None:
                    imageMask=fb.open(self.maskFile).data
                else:
                    imageMask=None
#                QApplication.processEvents()
                #print(self.azimuthalRange)
                self.q,self.I,self.Ierr=self.ai.integrate1d(imageData.data,self.npt,error_model='poisson',mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,azimuth_range=self.azimuthalRange,polarization_factor=self.polarization_factor)
                self.plotWidget.add_data(self.q,self.I,yerr=self.Ierr,name='Reduced data')
                if not self.set_externally:
                    cakedI,qr,phir=self.ai.integrate2d(imageData.data,self.npt,mask=imageMask,dark=imageDark,unit='q_A^-1',normalization_factor=norm_factor,polarization_factor=self.polarization_factor)
                    self.cakedImageWidget.setImage(cakedI,xmin=qr[0],xmax=qr[-1],ymin=phir[0],ymax=phir[-1],transpose=True,xlabel='Q ', ylabel='phi ',unit=['&#8491;<sup>-1</sup>','degree'])
                    self.cakedImageWidget.imageView.view.setAspectLocked(False)
                    try:
                        self.azimuthalRegion.setRegion(self.azimuthalRange)
                    except:
                        self.azimuthalRegion=pg.LinearRegionItem(values=self.azimuthalRange,orientation=pg.LinearRegionItem.Horizontal,movable=True,bounds=[-180,180])
                        self.cakedImageWidget.imageView.getView().addItem(self.azimuthalRegion)
                        self.azimuthalRegion.sigRegionChanged.connect(self.azimuthalRegionChanged)
                self.plotWidget.setTitle(self.dataFile,fontsize=3)
#                self.progressBar.setRange(0,100)
#                self.progressBar.setValue(100)
#                self.statusLabel.setText('Idle')
#                QApplication.processEvents()
                self.saveData()
                #self.tabWidget.setCurrentWidget(self.plotWidget)
            else:
                QMessageBox.warning(self,'Calibration File Error','Data reduction failed because either no calibration file provided or the provided file or path do not exists',QMessageBox.Ok)
                
        else:
            QMessageBox.warning(self,'Data File Error','No data file provided', QMessageBox.Ok)
            
    def azimuthalRegionChanged(self):
        minp,maxp=self.azimuthalRegion.getRegion()
        self.azimuthalRangeLineEdit.setText('%.1f:%.1f'%(minp,maxp))
        self.azimuthalRange=[minp,maxp]
        self.set_externally=True
        
        
            
    def reduce_multiple(self):
        """
        Reduce multiple files
        """
        try:
            i=0
            self.progressBar.setRange(0,len(self.dataFiles))
            self.progressBar.setValue(i)
            self.statusLabel.setText('<font color="red">Busy</font>')
            for file in self.dataFiles:
                self.dataFile=file
                QApplication.processEvents()
                self.reduceData()
                i=i+1
                self.progressBar.setValue(i)
                QApplication.processEvents()
            self.statusLabel.setText('<font color="green">Idle</font>')
            self.progressBar.setValue(0)
        except:
            QMessageBox.warning(self,'File error','No data files to reduce',QMessageBox.Ok)
        
    def saveData(self):
        """
        saves the extracted data into a file
        """
        if not os.path.exists(self.extractedFolder):
            os.makedirs(self.extractedFolder)
        filename=os.path.join(self.extractedFolder,os.path.splitext(os.path.basename(self.dataFile))[0]+'.txt')
        headers='File extracted on '+time.asctime()+'\n'
        headers='Files used for extraction are:\n'
        headers+='Data file: '+self.dataFile+'\n'
        if self.darkFile is not None:
            headers+='Dark file: '+self.darkFile+'\n'
        else:
            headers+='Dark file: None\n'
        headers+='Poni file: '+self.poniFile+'\n'
        if self.maskFile is not None:
            headers+='mask file: '+self.maskFile+'\n'
        else:
            headers+='mask file: None\n'
        for key in self.header.keys():
            headers+=key+'='+str(self.header[key])+'\n'
        headers+="col_names=['Q (inv Angs)','Int','Int_err']\n"
        headers+='Q (inv Angs)\tInt\tInt_err'
        data=vstack((self.q,self.I,self.Ierr)).T
        savetxt(filename,data,header=headers,comments='#')
Esempio n. 30
0
class HeapTrace(object):
    def __init__(self, mainWindow):
        self.mainWindow = mainWindow
        self.reader = None
        self.thread = None
        self.proc = None
        self.blocks = []

    def run(self):
        self.log = []
        self.bits = int(settings.get('new trace', 'BITS'))
        self.mainWindow.textLog.clear()
        invocation = PopenAndCall(getcmd(self.bits), shell=False)
        invocation.finished.connect(self.on_proc_finished)
        invocation.started.connect(self.on_proc_started)
        invocation.start(self)

    def kill(self):
        if self.thread:
            self.thread.quit()
        if self.proc:
            self.proc.terminate()
        try:
            os.kill(self.pin_proc, signal.SIGKILL)
        except Exception:
            pass

    def find_block_by_addr(self, addr):
        for i, block in enumerate(self.heapView.layoutHeapView.children()):
            print block
            if block.base_addr == addr:
                return i, block
        return None, None

    def on_got_heap_op(self, packet):
        if packet.code == heap_op_type_t.PKT_FREE:
            i, freed = self.find_block_by_addr(packet.args[0])
            if freed == None:
                print "Hacking is not nice."
            else:
                freed.new_packet(packet)
        else:
            i, old = self.find_block_by_addr(packet.return_value)
            if not old:
                block = Block(packet)
                self.heapView.push_new_block(block)
                self.blocks.append(block)
            else:
                if old.packet.chunk.size != packet.chunk.size:
                    self.heapView.layoutHeapView.removeWidget(old)
                else:
                    old.new_packet(packet)

        self.log.append(packet)
        self.mainWindow.textLog.append(packet.text_dump() + "\n")

    def on_proc_started(self):
        self.mainWindow.status("Process started")
        self.events = Queue(maxsize=0)
        self.reader = PinCommunication('localhost', 12345, self.bits, self.events)
        self.thread = QThread()
        self.reader.moveToThread(self.thread)
        self.reader.got_heap_op.connect(self.on_got_heap_op)
        self.reader.pin_PID.connect(self.on_pin_pid_received)
        self.thread.started.connect(self.reader.event_loop)
        self.reader.finished.connect(self.on_reader_finished)
        self.thread.finished.connect(self.on_thread_finished)
        self.thread.start()

    def on_pin_pid_received(self, pid):
        self.pin_proc = pid
        self.heapView = HeapWindow(self.mainWindow)
        self.heapView.show()

    def on_proc_finished(self):
        if self.thread:
            self.thread.quit()
        self.proc = None
        self.kill()
        self.mainWindow.status("Process finished ({} lines)".format(len(self.log)))

    def on_reader_finished(self):
        self.kill()
        self.reader.deleteLater()

    def on_thread_finished(self):
        self.thread.deleteLater()
        self.thread = None
Esempio n. 31
0
    def terminal(self):
        Worm.stop(self)
        QThread.quit(self)

        # Stop the thread
        self.is_listening = False
Esempio n. 32
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. 33
0
class CodeCompletionWidget(QFrame):
    def __init__(self, neditor):
        super().__init__(None, Qt.FramelessWindowHint | Qt.ToolTip)
        self._neditor = neditor
        # Code completion worker
        self._cc_worker = QThread()
        self._cc = code_completion.CodeCompletion()
        self._cc.moveToThread(self._cc_worker)
        box = QVBoxLayout(self)
        box.setContentsMargins(0, 0, 0, 0)
        self._completion_list = QListWidget()
        self._completion_list.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        delegate = completion_delegate.CompletionDelegate()
        # self._completion_list.setItemDelegate(delegate)
        box.addWidget(self._completion_list)

        self._icons = {
            'class': ':img/class',
            'function': ':img/function',
            'attr': ':img/attr',
            'instance': ':img/instance',
            'statement': ':img/statement'
        }
        # Key operations
        self._key_operations = {
            Qt.Key_Down: self.next_item,
            Qt.Key_Up: self.previous_item,
            Qt.Key_Escape: self.hide_completer,
            Qt.Key_Tab: self.insert_completion,
            Qt.Key_Return: self.insert_completion
        }
        self.__desktop = QApplication.instance().desktop()
        # Connections
        self._neditor.post_key_press.connect(self.process_key_event)
        self._cc_worker.started.connect(self._cc.collect_completions)
        self._cc.completionsReady.connect(self.__show_completions)

    def __show_completions(self, completions):
        self._add_proposals(completions)
        self.set_geometry()
        self.show()

    def _add_proposals(self, proposals):
        self._completion_list.clear()

        for proposal in proposals:
            item = QListWidgetItem()
            item.setText(proposal['name'])
            item.setIcon(QIcon(self._icons[proposal['type']]))
            # item.setData(Qt.DisplayRole, proposal['name'])
            # item.setData(Qt.UserRole + 1, proposal['desc'])
            # item.setData(Qt.DecorationRole, self._icons.get(proposal['type']))
            self._completion_list.addItem(item)
        self._completion_list.setCurrentRow(0)

    def set_geometry(self):
        cursor_rect = self._neditor.cursorRect()
        desktop_geo = self.__desktop.availableGeometry(self._neditor)
        point = self._neditor.mapToGlobal(cursor_rect.topLeft())
        cursor_rect.moveTopLeft(point)
        width = 450
        # Calculate the height
        max_visible_items = 10
        visible_items = min(self._completion_list.model().rowCount(),
                            max_visible_items)
        opt = self._completion_list.viewOptions()
        model = self._completion_list.model()
        height = self._completion_list.itemDelegate().sizeHint(
            opt, model.index(0)).height()
        height *= visible_items
        orientation = (point.y() + height) < desktop_geo.height()
        if orientation:
            cursor_rect.moveTop(cursor_rect.bottom())
        cursor_rect.setWidth(width)
        cursor_rect.setHeight(height)
        self.setFixedHeight(height)
        if not orientation:
            cursor_rect.moveBottom(cursor_rect.top())
        xpos = desktop_geo.width() - (point.x() + width)
        if xpos < 0:
            cursor_rect.moveLeft(cursor_rect.left() + xpos)
        # else:
        #     cursor_rect.moveRight(cursor_rect.right() + 50)
        self.setGeometry(cursor_rect)

    def process_pre_key_event(self, event):
        if not self.isVisible():
            return
        key = event.key()
        operation = self._key_operations.get(key, lambda: False)()
        if operation is None:
            return True
        return False

    def process_key_event(self, event):
        if self.isVisible():
            # Collect new proposals based on prefix
            self.__prefix = self._neditor._text_under_cursor()
            if self.__prefix is None:
                return
            proposals = []
            for completion in self._cc.proposals:
                name = completion['name']
                len_prefix = len(self.__prefix)
                if name[:len_prefix] == self.__prefix:
                    proposals.append(completion)
            if proposals:
                self._add_proposals(proposals)
                self.set_geometry()
            else:
                self.hide_completer()
        key = event.key()
        if key == Qt.Key_Period:
            self.complete()

    def insert_completion(self):
        to_insert = self._completion_list.currentItem().text()
        if self.__prefix:
            to_insert = to_insert[len(self.__prefix):]
        self._neditor.textCursor().insertText(to_insert)
        self.hide_completer()

    def complete(self):
        self._cc.clean_up()
        # Get data from editor
        source = self._neditor.text
        lineno, offset = self._neditor.cursor_position
        self.__prefix = self._neditor._text_under_cursor()
        # Prepare the worker and run thread
        self._cc.prepare(source, lineno, offset)
        self._cc_worker.start()

    def hide_completer(self):
        self.hide()
        # Stop thread
        self.__prefix = ''
        self._cc_worker.quit()
        self._cc_worker.wait(10)

    def previous_item(self):
        new_row = self._completion_list.currentRow() - 1
        if new_row >= 0:
            self._completion_list.setCurrentRow(new_row)
        else:
            self._completion_list.setCurrentRow(self._completion_list.count() -
                                                1)

    def next_item(self):
        new_row = self._completion_list.currentRow() + 1
        if new_row < self._completion_list.count():
            self._completion_list.setCurrentRow(new_row)
        else:
            self._completion_list.setCurrentRow(0)
Esempio n. 34
0
class _POSIXUserscriptRunner(_BaseUserscriptRunner):

    """Userscript runner to be used on POSIX. Uses _BlockingFIFOReader.

    The OS must have support for named pipes and select(). Commands are
    executed immediately when they arrive in the FIFO.

    Attributes:
        _reader: The _BlockingFIFOReader instance.
        _thread: The QThread where reader runs.
    """

    def __init__(self, parent=None):
        super().__init__(parent)
        self._reader = None
        self._thread = None

    def run(self, cmd, *args, env=None):
        rundir = utils.get_standard_dir(QStandardPaths.RuntimeLocation)
        # tempfile.mktemp is deprecated and discouraged, but we use it here to
        # create a FIFO since the only other alternative would be to create a
        # directory and place the FIFO there, which sucks. Since os.kfifo will
        # raise an exception anyways when the path doesn't exist, it shouldn't
        # be a big issue.
        self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
        os.mkfifo(self._filepath)  # pylint: disable=no-member

        self._reader = _BlockingFIFOReader(self._filepath)
        self._thread = QThread(self)
        self._reader.moveToThread(self._thread)
        self._reader.got_line.connect(self.got_cmd)
        self._thread.started.connect(self._reader.read)
        self._reader.finished.connect(self.on_reader_finished)
        self._thread.finished.connect(self.on_thread_finished)

        self._run_process(cmd, *args, env=env)
        self._thread.start()

    def on_proc_finished(self):
        """Interrupt the reader when the process finished."""
        log.procs.debug("proc finished")
        self._thread.requestInterruption()

    def on_proc_error(self, error):
        """Interrupt the reader when the process had an error."""
        super().on_proc_error(error)
        self._thread.requestInterruption()

    def on_reader_finished(self):
        """Quit the thread and clean up when the reader finished."""
        log.procs.debug("reader finished")
        self._thread.quit()
        self._reader.fifo.close()
        self._reader.deleteLater()
        super()._cleanup()
        self.finished.emit()

    def on_thread_finished(self):
        """Clean up the QThread object when the thread finished."""
        log.procs.debug("thread finished")
        self._thread.deleteLater()
Esempio n. 35
0
class Serial(QObject):
    '''
    Simple serial communications class.
    NOTICE! RXD and TXD are the only pins used.

    Usage:
        import sys
        from PyQt5.QtCore import QCoreApplication
        from qserial.serial import Serial
        from qserial.io import IOException

        application = QCoreApplication(sys.argv)

        def onRead(data):
            print("Read {} byte(s): {}".format(len(data), data))

        serial = Serial()
        serial.port = "/dev/ttyUSB0"
        serial.baudRate = 19200
        serial.byteSize = Serial.DATABITS_SEVEN
        serial.parity = Serial.PARITY_EVEN
        serial.stopBits = Serial.STOPBITS_ONE
        serial.onRead = onRead

        try:
            serial.open()

        except IOException as exception:
            print(exception)
            serial.close()

        else:
            data = b"The quick brown fox jumps over the lazy dog"
            print("Write {} byte(s):{}".format(len(data), data))
            serial.write(data)

        application.exec_()
    '''

    PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = (
        IO.PARITY_NONE, IO.PARITY_EVEN, IO.PARITY_ODD, IO.PARITY_MARK,
        IO.PARITY_SPACE)

    STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (
        IO.STOPBITS_ONE, IO.STOPBITS_ONE_POINT_FIVE, IO.STOPBITS_TWO)

    DATABITS_FIVE, DATABITS_SIX, DATABITS_SEVEN, DATABITS_EIGHT = (
        IO.DATABITS_FIVE, IO.DATABITS_SIX, IO.DATABITS_SEVEN,
        IO.DATABITS_EIGHT)

    send = pyqtSignal(bytes)
    quit = pyqtSignal()

    def __init__(self, parent=None):
        QObject.__init__(self, parent)

        self.__io = IO()

        class Reader(QObject):
            '''
            Synchronous data reader.
            '''

            received = pyqtSignal(bytes)
            done = pyqtSignal()

            def __init__(self, io):
                QObject.__init__(self)
                self.io = io
                self.proceedToExit = None

            pyqtSlot()

            def read(self):
                '''
                Infinitely wait for incoming data in an infinite loop. All the incoming data is passed outside with the
                signal.

                :return data: bytes, incoming data
                '''

                self.proceedToExit = False

                while not self.proceedToExit:
                    data = self.io.read()

                    QCoreApplication.processEvents(
                    )  # need to process events, and delivered signals

                    if not data:
                        continue

                    self.received.emit(data)

                self.io.close()
                self.done.emit()

            @pyqtSlot()
            def quit(self):
                self.proceedToExit = True

        class Writer(QObject):
            '''
            Synchronous data writer.
            '''

            done = pyqtSignal()

            def __init__(self, io):
                QObject.__init__(self)
                self.io = io
                self.proceedToExit = False
                self.dataToWrite = []

            @pyqtSlot(bytes)
            def write(self, data):
                '''
                Write all the characters of <data> one by one.

                :param data: bytes, outgoing data
                :return: None
                '''

                self.proceedToExit = False

                self.dataToWrite.append(data)

                while len(self.dataToWrite):
                    chunk = self.dataToWrite.pop(0)

                    self.io.write(chunk)

                if self.proceedToExit:
                    self.done.emit()

            @pyqtSlot()
            def quit(self):
                self.proceedToExit = True

        self.__on_read = None  # on-read callback

        self.__readingThread = QThread(self)
        self.__writingThread = QThread(self)

        self.__reader = Reader(self.__io)
        self.__writer = Writer(self.__io)

        self.__reader.moveToThread(self.__readingThread)
        self.__writer.moveToThread(self.__writingThread)

        # TOUCH THE READER ONLY WITH SIGNALS !!
        self.__reader.received.connect(self.__onReadyRead)
        # self.__reader.done.connect(self.__readingThread.quit)
        self.__reader.done.connect(self.deleteLater)

        self.quit.connect(self.__reader.quit)

        self.__readingThread.started.connect(self.__reader.read)
        # self.__readingThread.finished.connect(self.deleteLater)

        #TOUCH THE WRITER ONLY WITH SIGNALS !!
        # self.__writer.done.connect(self.__writingThread.quit)
        self.__writer.done.connect(self.deleteLater)

        self.send.connect(self.__writer.write)
        self.quit.connect(self.__writer.quit)

        # self.__writingThread.finished.connect(self.deleteLater)

    def open(self):
        self.__io.open()

        self.__readingThread.start()
        self.__writingThread.start()

    def close(self):

        self.quit.emit()

        self.__writingThread.wait(2)
        self.__readingThread.wait(2)

        self.__writingThread.quit()
        self.__readingThread.quit()

    @pyqtSlot(bytes)
    def __onReadyRead(self, data):
        if self.__on_read:
            self.__on_read(data)

    def write(self, data):
        self.send.emit(data)

    @property
    def port(self):
        return self.__io.port

    @port.setter
    def port(self, port):
        self.__io.port = port

    @property
    def baudRate(self):
        return self.__io.baudRate

    @baudRate.setter
    def baudRate(self, baudRate):
        self.__io.baudRate = baudRate

    @property
    def byteSize(self):
        return self.__io.byteSize

    @byteSize.setter
    def byteSize(self, byteSize):
        self.__io.byteSize = byteSize

    @property
    def parity(self):
        return self.__io.parity

    @parity.setter
    def parity(self, parity):
        self.__io.parity = parity

    @property
    def stopBits(self):
        return self.__io.stopBits

    @stopBits.setter
    def stopBits(self, stopBits):
        self.__io.stopBits = stopBits

    @property
    def onRead(self):
        return self.__on_read

    @onRead.setter
    def onRead(self, callback):
        self.__on_read = callback

    @property
    def isOpen(self):
        return self.__io.isOpen
Esempio n. 36
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. 37
0
class Ui(QWidget):
    # 开启的线程数量
    signal_thread_number = pyqtSignal(int)
    signal_draw_model = pyqtSignal(bool)
    signal_reset_draw_base_data = pyqtSignal(bool)

    def __init__(self):
        """
            1.载入UI
                1.1 显示内存情况
                1.2给工具栏添加日志文本框
            2.配置界面基本信息
                2.1 导入项目名称
                2.2 初始化导入数据
                2.3 初始化选择项目
                2.4 初始化模式
                2.5 绘图数据
                2.6 配置进度条初始值
                2.7 初始化解压界面
                2.8 是否绘图按钮
            3.绑定事件
                3.1 绑定左击事件
                    3.1.1 绑定导入数据事件
                    3.1.2 绑定选择项目事件
                    3.1.3 绑定运行按钮
                    3.1.4 绑定解压事件
                    3.1.5 绑定添加项目事件
                    3.1.6 绑定修改项目事件
                    3.1.7 绑定帮助事件
                    3.1.8 绑定关于事件
                    3.1.9 绑定列表与tab对应事件
                    3.1.10 绑定是否绘图事件
                3.2绑定右击事件
                3.3 退出事件
            4.自定义信号反馈
                4.1 gearbox区
                    4.1.1 创建gearbox区的绘图
                4.2 generator区
                4.1 pitch区
                4.2 converter区
                4.1 hydraulic区
                4.1 sensor区
            5.创建线程
                5.1 定义新线程
                5.2 定义需要创建线程数量的信号
        """
        super(Ui, self).__init__()
        # from PyQt5.QtWidgets import QGraphicsView
        # QGraphicsView.update()
        # 1 ----------------------------------------载入UI
        self.window = uic.loadUi("UI/main.ui")
        # # 1.1 显示内存情况
        self.window.memory_label = QLabel(self.window)
        self.window.memory_label.setStyleSheet("color:blue")
        self.window.memory_label.setText("")
        self.window.toolBar.addWidget(self.window.memory_label)
        self.timer = QTimer()
        self.refresh_ui()
        # # >>> 1.2 工具栏
        """
        创建log窗口
        默认情况不显示
        绑定工具栏action
        """
        self.log = log_window.UI()
        self.show_log = False
        self.window.Log.triggered.connect(self.open_log)
        # # <<<工具栏结束
        # 2 ----------------------------------------载入界面基础数据开始
        self.base_setting = base_setting.AutoSelectTickets(os.getcwd() + r"/config/tickets.json")
        self.window.select_project_comboBox.addItems(self.base_setting.tickets_data_project_name)
        # # 2.1 设置默认情况下不选择项目,不选择模式
        self.window.select_project_comboBox.setCurrentIndex(-1)
        self.window.mode_comboBox.setCurrentIndex(-1)
        # # 2.2 导入数据初始化
        self.need_deal_data = None
        # # 2.3 选择项目
        self.select_project_index = None
        # # 2.4 初始化模式
        self.mode = 0
        # # 2.5 配置进度条初始值
        self.gb_process = 0
        self.ge_process = 0
        self.pitch_process = 0
        self.co_process = 0
        self.hy_process = 0
        self.se_process = 0
        # # 2.7 初始化解压界面
        # #2.8
        self.is_plot = False
        self.unpack_ui = seven_zip_window.UI()
        # # 2.9 定义结束线程变量
        self.over = 0
        # 3----------------------------------------绑定事件
        # # >>>3.1 左击事件
        # # # 3.1.1 绑定导入数据事件
        self.window.import_data_action.triggered.connect(self.load_data)
        # # # 3.1.2 绑定选择项目事件,选择模式事件
        self.window.select_project_comboBox.currentIndexChanged.connect(self.select_project)
        self.window.mode_comboBox.currentIndexChanged.connect(self.select_mode)
        # # # 3.1.3 绑定运行按钮
        self.window.run_pushButton.clicked.connect(self.run)
        # # # >>> 3.1.4 绑定解压
        self.window.unpack_action.triggered.connect(self.load_unpack)
        # # # >>> 3.1.5 绑定添加项目事件
        self.window.add_project_action.triggered.connect(self.add_project)
        # # # >>> 3.1.6 绑定修改项目事件
        self.window.refactor_project_action.triggered.connect(self.refactor_project)
        # # # >>> 3.1.7 绑定帮助事件
        self.window.help_action.triggered.connect(self.help)
        # # # >>> 3.1.8 绑定关于事件
        self.window.about_action.triggered.connect(self.about)

        # # # >>> 3.1.9 绑定左击列表与页列表对应事件
        self.window.gb_listWidget.itemClicked.connect(self.select_gb_tabWidget_items)
        self.window.ge_listWidget.itemClicked.connect(self.select_ge_tabWidget_items)
        self.window.pitch_listWidget.itemClicked.connect(self.select_pitch_tabWidget_items)
        self.window.co_listWidget.itemClicked.connect(self.select_co_tabWidget_items)
        self.window.hy_listWidget.itemClicked.connect(self.select_hy_tabWidget_items)
        self.window.se_listWidget.itemClicked.connect(self.select_se_tabWidget_items)
        # # # >>> 3.1.10是否绘图
        self.window.plot_comboBox.currentIndexChanged.connect(self.is_draw)
        self.window.plot_comboBox.setCurrentIndex(-1)

        # # <<< 左击事件结束

        # # 3.2 >>>绑定右击事件
        self.window.gb_listWidget.setContextMenuPolicy(3)  # 设置菜单
        # # 3.3 >>> 绑定退出事件
        self.window.closeEvent = self.closeEvent

        # self.window.gb_listWidget.customContextMenuRequested[QPoint].connect(self.change_gearbox_list_color)  # 设置菜单

        # 4 ----------------------------------------自定义信号区
        # # 模型管理
        self.model_manager = model_manager.Manager()
        # # 4.1 gearbox 区
        # # # 4.2.1 gearbox绘图区
        self.gb_graphicsViews = None
        # # # 4.1.2 >>>初始化gearbox线程
        """
            1. 初始化空gearbox
            2.绑定gearbox模块的信号:修改列表框颜色,更新进度
        """
        self.gearbox = grGearBox.GearBox1()
        self.gearbox.signal_gb_progress.connect(self.progress)
        self.gearbox.signal_gb_color.connect(self.change_gearbox_list_color)
        self.gearbox.signal_gb_write_log.connect(self.write_log)
        self.gearbox.signal_gb_over.connect(self.over_threading)

        # # 4.2 generator 区
        # # # 4.2.1 generator绘图区
        self.ge_graphicsViews = None
        # # # 4.2.2 >>>初始化generator线程
        self.generator = grGenerator.Generator1()
        self.generator.signal_ge_progress.connect(self.progress)
        self.generator.signal_ge_color.connect(self.change_generator_list_color)
        self.generator.signal_ge_write_log.connect(self.write_log)
        self.generator.signal_ge_over.connect(self.over_threading)

        # # 4.3 pitch 区
        # # # 4.3.1 pitch绘图区
        self.pitch_graphicsViews = None
        # # # 4.3.2 >>>初始化pitch线程
        self.pitch = grPitch.Pitch1()
        self.pitch.signal_pitch_progress.connect(self.progress)
        self.pitch.signal_pitch_color.connect(self.change_pitch_list_color)
        self.pitch.signal_pitch_write_log.connect(self.write_log)
        self.pitch.signal_pitch_over.connect(self.over_threading)

        # # 4.4 converter 区
        # # # 4.4.1 converter绘图区
        self.co_graphicsViews = None
        # # # 4.4.2 >>>初始化converter线程
        self.converter = grConverter.Converter1()
        self.converter.signal_co_progress.connect(self.progress)
        self.converter.signal_co_color.connect(self.change_converter_list_color)
        self.converter.signal_co_write_log.connect(self.write_log)
        self.converter.signal_co_over.connect(self.over_threading)

        # # 4.5 hydraulic 区
        # # # 4.5.1 hydraulic绘图区
        self.hy_graphicsViews = None
        # # # 4.5.2 >>>初始化hydraulic线程
        self.hydraulic = grHydraulic.Hydraulic1()
        self.hydraulic.signal_hy_progress.connect(self.progress)
        self.hydraulic.signal_hy_color.connect(self.change_hydraulic_list_color)
        self.hydraulic.signal_hy_write_log.connect(self.write_log)
        self.hydraulic.signal_hy_over.connect(self.over_threading)

        # # 4.6 sensor 区
        # # # 4.6.1 sensor绘图区
        self.se_graphicsViews = None
        # # # 4.6.2 >>>初始化sensor线程
        self.sensor = grSensor.Sensor1()
        self.sensor.signal_se_progress.connect(self.progress)
        self.sensor.signal_se_color.connect(self.change_sensor_list_color)
        self.sensor.signal_se_write_log.connect(self.write_log)
        self.sensor.signal_se_over.connect(self.over_threading)

        self.load_draw_timer = QTimer()
        # # # <<<初始化结束

        # # ------------------------------>>>测试区域
        # self.window.run_pushButton.setEnabled(True)
        # self.window.run_pushButton.setEnabled(True)
        self.window.statusBar.showMessage("主界面加载完成")
        # self.load_draw_timer.singleShot(1000, self.load_plot)
        self.window.statusBar.showMessage("请等待绘图界面加载!")
        # # ------------------------------>>>测试区域结束
        # 5 创建线程
        # # 5.1 定义新线程
        # *********************************************************************************###
        # self.gearbox_thread = self.generator_thread = self.pitch_thread = \                #
        #     self.converter_thread = self.hydraulic_thread = self.sensor_thread = QThread() #
        #               此处不能用这种创建方式,不然创建的是同一个进程!!!只是引用了多次               #
        # *********************************************************************************###
        self.gearbox_thread = QThread()
        self.generator_thread = QThread()
        self.pitch_thread = QThread()
        self.converter_thread = QThread()
        self.hydraulic_thread = QThread()
        self.sensor_thread = QThread()
        # # 5.2 定义需要创建线程数量的信号
        self.signal_thread_number.connect(self.start_thread)

        # # 测试区
        # self.window.test_pushButton.clicked.connect(self.closeEvent)
        # self.gb_graphicsViews = list(self.window.gb_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        # self.gb_graphicsViews.reverse()
        # self.drawing(self.gb_graphicsViews[0],
        #              canvas_setting={"title": "ss"},
        #              data=[
        #                  {"x": [1, 2, 3, 4, 5], "y": [6, 7, 8, 9, 10], 'pen': 'g', "name": "o"},
        #                  {"x": [1, 2, 3, 4, 5], "y": [2, 4, 9, 16, 25]}
        #              ])

    def is_draw(self):
        self.window.run_pushButton.setEnabled(True)
        if self.window.plot_comboBox.currentIndex() == 0:
            self.is_plot = True
        else:
            self.is_plot = False

    def load_plot(self):
        """
        1.分模块获取绘图对象
        2.为绘图对象添加画布
        :return:
        """
        self.gb_graphicsViews = list(self.window.gb_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        self.gb_graphicsViews.reverse()
        self.ge_graphicsViews = list(self.window.ge_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        self.ge_graphicsViews.reverse()
        self.pitch_graphicsViews = list(self.window.pitch_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        self.pitch_graphicsViews.reverse()
        self.co_graphicsViews = list(self.window.co_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        self.co_graphicsViews.reverse()
        self.hy_graphicsViews = list(self.window.hy_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        self.hy_graphicsViews.reverse()
        self.se_graphicsViews = list(self.window.se_tabWidget.findChildren((pg.GraphicsLayoutWidget,)))
        self.se_graphicsViews.reverse()
        self.window.statusBar.showMessage("绘图模块加载完成!!!")

    # 载入解压界面
    def load_unpack(self):
        self.unpack_ui.window.show()

    def mouse_moved(self, evt):
        # gearbox
        if self.window.tabWidget.currentIndex() == 0:
            i = self.window.gb_tabWidget.currentIndex() + 1  # 得到当前页的序号+1 用于绑定十字
            mousePoint = self.vb.mapSceneToView(evt)
            print(self.vLine)
            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())
        # generator
        elif self.window.tabWidget.currentIndex() == 1:
            i = self.window.ge_tabWidget.currentIndex() + 1  # 得到当前页的序号+1 用于绑定十字
            mousePoint = self.ge_vb[i].mapSceneToView(evt)
            self.ge_vLine[i].setPos(mousePoint.x())
            self.ge_hLine[i].setPos(mousePoint.y())
        # pitch
        elif self.window.tabWidget.currentIndex() == 2:
            i = self.window.pitch_tabWidget.currentIndex() + 1  # 得到当前页的序号+1 用于绑定十字
            mousePoint = self.pitch_vb[i].mapSceneToView(evt)
            self.pitch_vLine[i].setPos(mousePoint.x())
            self.pitch_hLine[i].setPos(mousePoint.y())
        # converter
        elif self.window.tabWidget.currentIndex() == 3:
            i = self.window.co_tabWidget.currentIndex() + 1  # 得到当前页的序号+1 用于绑定十字
            mousePoint = self.co_vb[i].mapSceneToView(evt)
            self.co_vLine[i].setPos(mousePoint.x())
            self.co_hLine[i].setPos(mousePoint.y())
        # hydraulic
        elif self.window.tabWidget.currentIndex() == 4:
            i = self.window.hy_tabWidget.currentIndex() + 1  # 得到当前页的序号+1 用于绑定十字
            mousePoint = self.hy_vb[i].mapSceneToView(evt)
            self.hy_vLine[i].setPos(mousePoint.x())
            self.hy_hLine[i].setPos(mousePoint.y())
        # sensor
        elif self.window.tabWidget.currentIndex() == 5:
            i = self.window.se_tabWidget.currentIndex() + 1  # 得到当前页的序号+1 用于绑定十字
            mousePoint = self.se_vb[i].mapSceneToView(evt)
            self.se_vLine[i].setPos(mousePoint.x())
            self.se_hLine[i].setPos(mousePoint.y())

    def refresh_ui(self):
        self.timer.start(1000)
        self.timer.timeout.connect(self.get_memory)

    def get_memory(self):
        mem = psutil.virtual_memory()
        # round方法进行四舍五入,然后转换成字符串 字节/1024得到kb 再/1024得到M
        total = str(round(mem.total / 1024 / 1024))
        used = str(round(mem.used / 1024 / 1024))
        use_per = str(round(mem.percent))
        free = str(round(mem.free / 1024 / 1024))
        process = psutil.Process(os.getpid())
        memInfo = process.memory_info()
        me = str(round(memInfo.rss / 1024 / 1024))
        self.window.memory_label.setText(
            "本机内存:{}M,已使用:{}M({}%),本程序占用:{}M,可使用:{}M)".format(total, used, use_per, me, free))

    # ----------------------->>>载入基本数据事件区
    def load_data(self):
        self.window.mode_comboBox.setItemData(0, QVariant(1 | 32), Qt.UserRole - 1)
        self.window.mode_comboBox.setItemData(1, QVariant(1 | 32), Qt.UserRole - 1)
        self.window.mode_comboBox.setItemData(2, QVariant(1 | 32), Qt.UserRole - 1)
        Dialog = QFileDialog()
        file_names, filetype = Dialog.getOpenFileNames(self.window,
                                                       "选取文件",
                                                       # 获得当前路径
                                                       os.getcwd(),  # 起始路径
                                                       "CSV文件 (*.csv);;所有文件 (*)")  # 设置文件扩展名过滤,用双分号间隔
        if file_names == []:
            print("\n取消选择")
            Dialog.deleteLater()
            sip.delete(Dialog)
            gc.collect()

        else:
            """
            如果选择单个文件则判断是合并的周级数据还是天级数据
                周级数据:大文件模式
                天级数据:小文件绘图模式
            如果选择多个文件
                多个文件:多文件模式
            """
            if len(file_names) == 1:
                """
               得到文件的大小 单位 kb
               1M = 1024 kb
               300M = 307200 kb
               如果文件小于300M则选择小文件绘图模式 否则大文件模式
               """
                self.need_deal_data = file_names[0]
                self.window.select_project_comboBox.setEnabled(True)
                file_size = os.path.getsize(self.need_deal_data)
                file_size = float(file_size) / float(1024 * 1024)  # M
                if file_size <= 300:
                    # 设置模式为小文件绘图模式,1,2 选项不可选
                    self.window.mode_comboBox.setItemData(1, 0, Qt.UserRole - 1)
                    self.window.mode_comboBox.setItemData(2, 0, Qt.UserRole - 1)
                    # 设置模式1
                    self.mode = 1
                    self.window.mode_comboBox.setCurrentIndex(0)
                    print("mode1")

                else:
                    self.need_deal_data = file_names[0]
                    self.window.select_project_comboBox.setEnabled(True)
                    # 设置模式为大文件,0,2 选项不可选
                    self.window.mode_comboBox.setItemData(0, 0, Qt.UserRole - 1)
                    self.window.mode_comboBox.setItemData(2, 0, Qt.UserRole - 1)
                    # 设置模式2
                    self.mode = 2
                    self.window.mode_comboBox.setCurrentIndex(1)
                    print("mode2")

            else:
                self.window.select_project_comboBox.setEnabled(True)
                # 还未设计多文件选择模式
                self.need_deal_data = file_names
                # 设置模式为多文件模式,0,2 选项不可选
                self.window.mode_comboBox.setItemData(0, 0, Qt.UserRole - 1)
                self.window.mode_comboBox.setItemData(1, 0, Qt.UserRole - 1)
                # 设置模式3
                self.mode = 3
                print("mode3")
            # 设置选择下拉框可见
            Dialog.deleteLater()
            sip.delete(Dialog)
            gc.collect()

    def select_project(self):
        # self.window.run_pushButton.setEnabled(True)
        self.select_project_index = self.window.select_project_comboBox.currentIndex()
        # 管家
        self.model_manager = model_manager.Manager(mode=self.mode,
                                                   tickets_file_path='config/tickets.json',
                                                   import_data_path=self.need_deal_data,
                                                   project_index=self.select_project_index,
                                                   plot_setting={})
        # 开始管家线程
        self.model_manager.signal_return_data.connect(self.start_analyse)
        self.model_manager.signal_manager_over.connect(self.over_threading)

        self.window.plot_comboBox.setEnabled(True)

    def select_mode(self):
        # print(self.mode)

        # print(self.window.mode_comboBox.currentIndex())
        if self.mode == 1:
            self.load_plot()
        elif self.mode == 2:
            self.load_plot()
        else:
            pass

    # 功能区事件
    # --------------------->>>绑定列表与tab界面事件区
    def select_gb_tabWidget_items(self):
        QApplication.processEvents()
        self.window.gb_tabWidget.setCurrentIndex(self.window.gb_listWidget.currentRow())

    def select_ge_tabWidget_items(self):
        self.window.ge_tabWidget.setCurrentIndex(self.window.ge_listWidget.currentRow())

    def select_pitch_tabWidget_items(self):
        self.window.pitch_tabWidget.setCurrentIndex(self.window.pitch_listWidget.currentRow())

    def select_co_tabWidget_items(self):
        self.window.co_tabWidget.setCurrentIndex(self.window.co_listWidget.currentRow())

    def select_hy_tabWidget_items(self):
        self.window.hy_tabWidget.setCurrentIndex(self.window.hy_listWidget.currentRow())

    def select_se_tabWidget_items(self):
        self.window.se_tabWidget.setCurrentIndex(self.window.se_listWidget.currentRow())

    # --------------------->>>改变list颜色类事件区
    def change_gearbox_list_color(self, li):
        """
        :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items
        :return:
        """
        item = li[1]
        for i in range(35):
            if i == item:
                if li[0] == -1:
                    self.window.gb_listWidget.item(i).setBackground(QColor('gray'))
                    self.window.gb_listWidget.item(i).setCheckState(1)
                elif li[0] == 0:
                    self.window.gb_listWidget.item(i).setBackground(QColor('red'))
                    self.window.gb_listWidget.item(i).setCheckState(2)
                else:
                    self.window.gb_listWidget.item(i).setBackground(QColor('green'))
                    self.window.gb_listWidget.item(i).setCheckState(0)
            else:
                pass

    def change_generator_list_color(self, li):
        """
        :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items
        :return:
        """
        item = li[1]
        for i in range(14):
            if i == item:
                if li[0] == -1:
                    self.window.ge_listWidget.item(i).setBackground(QColor('gray'))
                    self.window.ge_listWidget.item(i).setCheckState(1)
                elif li[0] == 0:
                    self.window.ge_listWidget.item(i).setBackground(QColor('red'))
                    self.window.ge_listWidget.item(i).setCheckState(2)
                else:
                    self.window.ge_listWidget.item(i).setBackground(QColor('green'))
                    self.window.ge_listWidget.item(i).setCheckState(0)
            else:
                pass

    def change_pitch_list_color(self, li):
        """
        :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items
        :return:
        """
        item = li[1]
        for i in range(9):
            if i == item:
                if li[0] == -1:
                    self.window.pitch_listWidget.item(i).setBackground(QColor('gray'))
                    self.window.pitch_listWidget.item(i).setCheckState(1)
                elif li[0] == 0:
                    self.window.pitch_listWidget.item(i).setBackground(QColor('red'))
                    self.window.pitch_listWidget.item(i).setCheckState(2)
                else:
                    self.window.pitch_listWidget.item(i).setBackground(QColor('green'))
                    self.window.pitch_listWidget.item(i).setCheckState(0)
            else:
                pass

    def change_converter_list_color(self, li):
        """
        :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items
        :return:
        """
        item = li[1]
        for i in range(8):
            if i == item:
                if li[0] == -1:
                    self.window.co_listWidget.item(i).setBackground(QColor('gray'))
                    self.window.co_listWidget.item(i).setCheckState(1)
                elif li[0] == 0:
                    self.window.co_listWidget.item(i).setBackground(QColor('red'))
                    self.window.co_listWidget.item(i).setCheckState(2)
                else:
                    self.window.co_listWidget.item(i).setBackground(QColor('green'))
                    self.window.co_listWidget.item(i).setCheckState(0)
            else:
                pass

    def change_hydraulic_list_color(self, li):
        """
        :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items
        :return:
        """
        item = li[1]
        for i in range(12):
            if i == item:
                if li[0] == -1:
                    self.window.hy_listWidget.item(i).setBackground(QColor('gray'))
                    self.window.hy_listWidget.item(i).setCheckState(1)
                elif li[0] == 0:
                    self.window.hy_listWidget.item(i).setBackground(QColor('red'))
                    self.window.hy_listWidget.item(i).setCheckState(2)
                else:
                    self.window.hy_listWidget.item(i).setBackground(QColor('green'))
                    self.window.hy_listWidget.item(i).setCheckState(0)
            else:
                pass

    def change_sensor_list_color(self, li):
        """
        :param li: li[0]: 返回一个int 类型数据 -1:数据标签点丢失,0 预警 1 正常 ; li[1]:序号,需要修改的items
        :return:
        """
        item = li[1]
        for i in range(9):
            if i == item:
                if li[0] == -1:
                    self.window.se_listWidget.item(i).setBackground(QColor('gray'))
                    self.window.se_listWidget.item(i).setCheckState(1)
                elif li[0] == 0:
                    self.window.se_listWidget.item(i).setBackground(QColor('red'))
                    self.window.se_listWidget.item(i).setCheckState(2)
                else:
                    self.window.se_listWidget.item(i).setBackground(QColor('green'))
                    self.window.se_listWidget.item(i).setCheckState(0)
            else:
                pass

    # --------------------->>>选择list项目类事件区

    # -------------------->>>日志区
    # 写日志区
    def write_log(self, li):
        """
        :param li: li[0]:用于判断部件
                    li[1]:li
                            li[0]:判断正常或异常
                            li[1]:日志
        :return:
        """
        if li[0] == 0:
            self.log.window.summary_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
        elif li[0] == 1:
            if li[1][0] == 1:
                self.log.window.gb_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1]))
            elif li[1][0] == 0:
                self.log.window.gb_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
            else:
                self.log.window.gb_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1]))

        elif li[0] == 2:
            if li[1][0] == 1:
                self.log.window.ge_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1]))
            elif li[1][0] == 0:
                self.log.window.ge_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
            else:
                self.log.window.ge_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1]))

        elif li[0] == 3:
            if li[1][0] == 1:
                self.log.window.pitch_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1]))
            elif li[1][0] == 0:
                self.log.window.pitch_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
            else:
                self.log.window.pitch_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1]))
        elif li[0] == 4:
            if li[1][0] == 1:
                self.log.window.co_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1]))
            elif li[1][0] == 0:
                self.log.window.co_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
            else:
                self.log.window.co_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1]))

        elif li[0] == 5:
            if li[1][0] == 1:
                self.log.window.hy_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1]))
            elif li[1][0] == 0:
                self.log.window.hy_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
            else:
                self.log.window.hy_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1]))

        elif li[0] == 6:
            if li[1][0] == 1:
                self.log.window.se_plainTextEdit.appendHtml("<p><font color='green'>{}</font></p>".format(li[1][1]))
            elif li[1][0] == 0:
                self.log.window.se_plainTextEdit.appendHtml("<p><font color='red'>{}</font></p>".format(li[1][1]))
            else:
                self.log.window.se_plainTextEdit.appendHtml("<p><font color='gray'>{}</font></p>".format(li[1][1]))

    def open_log(self):
        if not self.show_log:
            self.log.window.show()
            self.show_log = True
        else:
            self.log.window.hide()
            self.show_log = False

    # ----------------------->>>运行区
    # 分析启动几个线程
    def start_analyse(self, signal):
        """
        :param signal: signal 是一个列表
            li[0]:何种模式,大文件还是小文件
            li[1]:df 还是None, 小文件df 大文件None
            li[2]:项目序号
            li[3]: 开启的线程数量,小文件6个线程 0 表示6个全部开启,大文件1个线程
        如果是小文件模式则启动所有的线程,如果是大文件模式则只启动一个线程,等待上一个线程结束再启动下一个线程
        :return:
        """
        # 如果模式1
        if signal[0] == int(1):
            self.window.statusBar.showMessage("读取数据成功!正在计算...")
            # >>> gearbox
            self.gearbox = grGearBox.GearBox1(tickets_file_path='config/tickets.json',
                                              import_data_path=self.need_deal_data,
                                              df=signal[1],
                                              project_index=signal[2],
                                              plt_list=self.gb_graphicsViews,
                                              draw=self.is_plot)

            # >>> generator
            self.generator = grGenerator.Generator1(tickets_file_path='config/tickets.json',
                                                    import_data_path=self.need_deal_data,
                                                    df=signal[1],
                                                    project_index=signal[2],
                                                    plt_list=self.ge_graphicsViews,
                                                    draw=self.is_plot)

            # >>> pitch
            self.pitch = grPitch.Pitch1(tickets_file_path='config/tickets.json',
                                        import_data_path=self.need_deal_data,
                                        df=signal[1],
                                        project_index=signal[2],
                                        plt_list=self.pitch_graphicsViews,
                                        draw=self.is_plot)

            #  >>> converter
            self.converter = grConverter.Converter1(tickets_file_path='config/tickets.json',
                                                    import_data_path=self.need_deal_data,
                                                    df=signal[1],
                                                    project_index=signal[2],
                                                    plt_list=self.co_graphicsViews,
                                                    draw=self.is_plot)

            # >>> hydraulic
            self.hydraulic = grHydraulic.Hydraulic1(tickets_file_path='config/tickets.json',
                                                    import_data_path=self.need_deal_data,
                                                    df=signal[1],
                                                    project_index=signal[2],
                                                    plt_list=self.hy_graphicsViews,
                                                    draw=self.is_plot)

            # >>> sensor
            self.sensor = grSensor.Sensor1(tickets_file_path='config/tickets.json',
                                           import_data_path=self.need_deal_data,
                                           df=signal[1],
                                           project_index=signal[2],
                                           plt_list=self.se_graphicsViews,
                                           draw=self.is_plot)

            # ----------------开启线程
        elif signal[0] == int(2):
            self.gearbox = grGearBox.GearBox1(tickets_file_path='config/tickets.json',
                                              import_data_path=self.need_deal_data,
                                              df=None,
                                              project_index=self.select_project_index,
                                              plt_list=self.gb_graphicsViews,
                                              draw=self.is_plot)
            self.generator = grGenerator.Generator1(tickets_file_path='config/tickets.json',
                                                    import_data_path=self.need_deal_data,
                                                    df=None,
                                                    project_index=self.select_project_index,
                                                    plt_list=self.ge_graphicsViews,
                                                    draw=self.is_plot)
            self.pitch = grPitch.Pitch1(tickets_file_path='config/tickets.json',
                                        import_data_path=self.need_deal_data,
                                        df=None,
                                        project_index=self.select_project_index,
                                        plt_list=self.pitch_graphicsViews,
                                        draw=self.is_plot)
            self.converter = grConverter.Converter1(tickets_file_path='config/tickets.json',
                                                    import_data_path=self.need_deal_data,
                                                    df=None,
                                                    project_index=self.select_project_index,
                                                    plt_list=self.co_graphicsViews,
                                                    draw=self.is_plot)
            self.hydraulic = grHydraulic.Hydraulic1(tickets_file_path='config/tickets.json',
                                                    import_data_path=self.need_deal_data,
                                                    df=None,
                                                    project_index=self.select_project_index,
                                                    plt_list=self.hy_graphicsViews,
                                                    draw=self.is_plot)
            self.sensor = grSensor.Sensor1(tickets_file_path='config/tickets.json',
                                           import_data_path=self.need_deal_data,
                                           df=None,
                                           project_index=self.select_project_index,
                                           plt_list=self.se_graphicsViews,
                                           draw=self.is_plot)
        # ----------------------信号
        self.gearbox.signal_gb_progress.connect(self.progress)
        self.gearbox.signal_gb_color.connect(self.change_gearbox_list_color)
        self.gearbox.signal_gb_write_log.connect(self.write_log)
        self.gearbox.signal_gb_over.connect(self.over_threading)

        self.generator.signal_ge_progress.connect(self.progress)
        self.generator.signal_ge_color.connect(self.change_generator_list_color)
        self.generator.signal_ge_write_log.connect(self.write_log)
        self.generator.signal_ge_over.connect(self.over_threading)

        self.pitch.signal_pitch_progress.connect(self.progress)
        self.pitch.signal_pitch_color.connect(self.change_pitch_list_color)
        self.pitch.signal_pitch_write_log.connect(self.write_log)
        self.pitch.signal_pitch_over.connect(self.over_threading)

        self.converter.signal_co_progress.connect(self.progress)
        self.converter.signal_co_color.connect(self.change_converter_list_color)
        self.converter.signal_co_write_log.connect(self.write_log)
        self.converter.signal_co_over.connect(self.over_threading)

        self.hydraulic.signal_hy_progress.connect(self.progress)
        self.hydraulic.signal_hy_color.connect(self.change_hydraulic_list_color)
        self.hydraulic.signal_hy_write_log.connect(self.write_log)
        self.hydraulic.signal_hy_over.connect(self.over_threading)

        self.sensor.signal_se_progress.connect(self.progress)
        self.sensor.signal_se_color.connect(self.change_sensor_list_color)
        self.sensor.signal_se_write_log.connect(self.write_log)
        self.sensor.signal_se_over.connect(self.over_threading)

        # ------------------绑定线程
        self.gearbox.moveToThread(self.gearbox_thread)
        self.gearbox_thread.started.connect(self.gearbox.run)

        self.generator.moveToThread(self.generator_thread)
        self.generator_thread.started.connect(self.generator.run)

        self.pitch.moveToThread(self.pitch_thread)
        self.pitch_thread.started.connect(self.pitch.run)

        self.converter.moveToThread(self.converter_thread)
        self.converter_thread.started.connect(self.converter.run)

        self.hydraulic.moveToThread(self.hydraulic_thread)
        self.hydraulic_thread.started.connect(self.hydraulic.run)

        self.sensor.moveToThread(self.sensor_thread)
        self.sensor_thread.started.connect(self.sensor.run)

        # 判断需要开启的线程数量
        if signal[3] == int(6):
            self.signal_thread_number.emit(0)
        elif signal[3] == int(1):
            self.signal_thread_number.emit(1)

    def start_thread(self, thread_number):
        print("thread_number:{}".format(thread_number))
        """
        :param thread_number: 值为int 类型,
            thread_number == -1 结束所有线程
            thread_number == 0 开启所有线程
            thread_number == 1 开启第一个线程
            thread_number == 2 退出第一个线程后,开启第二个线程
            thread_number == 3 退出上一个线程后,开启下一个线程
            ...
        :return:
        """
        if thread_number == int(0):

            self.gearbox_thread.start()
            self.generator_thread.start()
            self.hydraulic_thread.start()
            self.sensor_thread.start()
            self.pitch_thread.start()
            self.converter_thread.start()
            for i in range(self.window.gb_tabWidget.count()):
                self.window.gb_tabWidget.setCurrentIndex(i)
            self.window.gb_tabWidget.setCurrentIndex(0)
            for i in range(self.window.ge_tabWidget.count()):
                self.window.ge_tabWidget.setCurrentIndex(i)
            self.window.ge_tabWidget.setCurrentIndex(0)
            for i in range(self.window.pitch_tabWidget.count()):
                self.window.pitch_tabWidget.setCurrentIndex(i)
            self.window.pitch_tabWidget.setCurrentIndex(0)
            for i in range(self.window.co_tabWidget.count()):
                self.window.co_tabWidget.setCurrentIndex(i)
            self.window.co_tabWidget.setCurrentIndex(0)
            for i in range(self.window.hy_tabWidget.count()):
                self.window.hy_tabWidget.setCurrentIndex(i)
            self.window.hy_tabWidget.setCurrentIndex(0)
            for i in range(self.window.se_tabWidget.count()):
                self.window.se_tabWidget.setCurrentIndex(i)
            self.window.se_tabWidget.setCurrentIndex(0)

        elif thread_number == int(1):
            self.gearbox_thread.start()
        elif thread_number == int(2):
            self.generator_thread.start()
        elif thread_number == int(3):
            self.pitch_thread.start()
        elif thread_number == int(4):
            self.converter_thread.start()
        elif thread_number == int(5):
            self.hydraulic_thread.start()
        elif thread_number == int(6):
            self.sensor_thread.start()

    def over_threading(self, li):

        """
        :param li: li[0]:第几个线程 ,li[1]:是否结束
        总共7个子线程,用变量over记录结束线程的数量,每结束一个子线程over增加1
        :return:
        """
        if self.mode == int(2):
            if li[0] == int(0):

                self.model_manager.exec_()
                self.over += 1
            elif li[0] == int(1):
                print("齿轮箱结束,发电机开始")
                self.over += 1
                # 齿轮箱结束,开启发电机
                self.signal_thread_number.emit(2)
                self.gearbox_thread.quit()

            elif li[0] == 2:
                self.over += 1
                # 发电机结束,开启变桨
                print("发电机结束,开启变桨")
                self.signal_thread_number.emit(3)
                self.generator_thread.quit()
            elif li[0] == 3:
                self.over += 1
                # 变桨结束,开启变频
                print("变桨结束,开启变频")
                self.signal_thread_number.emit(4)
                self.pitch_thread.quit()
            elif li[0] == 4:
                self.over += 1
                # 变频结束,开启液压
                print("变频结束,开启液压")
                self.signal_thread_number.emit(5)
                self.converter_thread.quit()
            elif li[0] == 5:
                self.over += 1
                # 液压结束,开启传感器
                print("液压结束,开启传感器")
                self.signal_thread_number.emit(6)
                self.hydraulic_thread.quit()
            elif li[0] == 6:
                self.over += 1
                self.sensor_thread.quit()
            if self.over == 7:
                # 线程结束,开始写日志
                self.save_log()
                self.window.run_file_name_label.setText("None")
        elif self.mode == int(1):
            if li[0] == 0:
                print("manager")
                # self.model_manager.quit()
                self.over += 1
            elif li[0] == 1:
                print("gb")
                # self.gearbox_thread.quit()
                self.over += 1
            elif li[0] == 2:
                print("ge")
                # self.generator_thread.quit()
                self.over += 1
            elif li[0] == 3:
                print("pi")
                # self.pitch_thread.quit()
                self.over += 1
            elif li[0] == 4:
                print("co")
                # self.converter_thread.quit()
                self.over += 1
            elif li[0] == 5:
                print("hy")
                # self.hydraulic_thread.quit()
                self.over += 1
            elif li[0] == 6:
                print("se")
                # self.sensor_thread.quit()
                self.over += 1
            if self.over == 7:
                # 线程结束,开始写日志
                self.save_log()
                self.window.run_file_name_label.setText("None")

        print(self.over)

    def run(self):

        print("run")
        """
        :多进程
        """
        # 选择模式
        self.model_manager.start()
        self.window.statusBar.showMessage("正在读取数据请稍等!")
        self.window.run_file_name_label.setText(str(self.need_deal_data))

    # ----------------------->>> 进度条区
    def progress(self, dictionary):
        if "gearbox" in dictionary.keys():
            self.gb_process = dictionary['gearbox']
        elif "generator" in dictionary.keys():
            self.ge_process = dictionary['generator']
        elif "pitch" in dictionary.keys():
            self.pitch_process = dictionary['pitch']
        elif "converter" in dictionary.keys():
            self.co_process = dictionary['converter']
        elif "hydraulic" in dictionary.keys():
            self.hy_process = dictionary['hydraulic']
        elif "sensor" in dictionary.keys():
            self.se_process = dictionary['sensor']

        process = int((
                              self.ge_process + self.gb_process + self.pitch_process + self.co_process + self.hy_process + self.se_process) / 6)
        # process = int(self.gb_process)
        # print(process)
        self.window.progressBar.setValue(process)

    # # 3.3 >>> 退出事件
    def closeEvent(self, QCloseEvent):
        res = QMessageBox.question(self, '消息', '是否清除合并后的数据?', QMessageBox.Yes | QMessageBox.No,
                                   QMessageBox.No)  # 两个按钮是否, 默认No则关闭这个提示框
        if res == QMessageBox.Yes:
            QCloseEvent.accept()
            # shutil.rmtree('data')
        else:
            QCloseEvent.accept()
            # QCloseEvent.ignore()

    # ----------------------->>>测试区

    def save_log(self):
        """
        保存日志
        :return:
        """
        import csv

        try:
            file_name = r'/log/' + str(self.need_deal_data).split('/')[-1].split('.')[0]
        except IndexError:
            file_name = r'/log'
        try:
            f = open(os.getcwd() + file_name + r"/日志.txt", mode="w", encoding="gbk")
            f.write(self.log.window.summary_plainTextEdit.toPlainText())
            f.close()

            lines = self.log.window.summary_plainTextEdit.document().lineCount()
            f = open(os.getcwd() + file_name + r"/日志.csv", mode="w", encoding="gbk", newline='')
            csv_writer = csv.writer(f)
            csv_writer.writerow(['模型', '报警次数'])
            for i in range(lines):
                str1 = self.log.window.summary_plainTextEdit.document().findBlockByLineNumber(i).text()
                str1 = str(str1).split(':')[-1]
                result = str1.split('报警次数')
                csv_writer.writerow(result)
            f.close()

            f = open(os.getcwd() + file_name + r"/日志.txt", mode="a", encoding="gbk")
            f.writelines('\r\n')
            f.write(self.log.window.gb_plainTextEdit.toPlainText())
            f.writelines('\r\n')
            f.write(self.log.window.ge_plainTextEdit.toPlainText())
            f.writelines('\r\n')
            f.write(self.log.window.pitch_plainTextEdit.toPlainText())
            f.writelines('\r\n')
            f.write(self.log.window.co_plainTextEdit.toPlainText())
            f.writelines('\r\n')
            f.write(self.log.window.hy_plainTextEdit.toPlainText())
            f.writelines('\r\n')
            f.write(self.log.window.se_plainTextEdit.toPlainText())
            f.close()
            self.window.statusBar.showMessage("日志写入成功!")
        except Exception as e:
            self.window.statusBar.showMessage("日志写入失败!")

    def add_project(self):
        text, okPressed = QInputDialog.getText(self, "添加项目", "输入密码:", QLineEdit.Password, "")
        if okPressed and text == '12345':
            try:
                subprocess.call(['notepad.exe', 'config/tickets.json'])
            except IOError as e:
                QMessageBox.warning(self, '警告', '文件丢失', QMessageBox.Yes)
        elif okPressed and text != '12345':
            QMessageBox.warning(self, '警告', '密码错误!', QMessageBox.Yes)
        else:
            pass

    def refactor_project(self):
        text, okPressed = QInputDialog.getText(self, "添加项目", "输入密码:", QLineEdit.Password, "")
        if okPressed and text == '12345':
            try:
                subprocess.call(['notepad.exe', 'config/tickets.json'])
            except IOError as e:
                QMessageBox.warning(self, '警告', '文件丢失', QMessageBox.Yes)
        elif okPressed and text != '12345':
            QMessageBox.warning(self, '警告', '密码错误!', QMessageBox.Yes)
        else:
            pass

    def help(self):
        pass

    def about(self):
        msg = "预警模型1.0版本\n海上工程技术部"
        QMessageBox.about(self.window, "关于", msg)

    def test1(self):
        print(123)
Esempio n. 38
0
class DebugExporter(QDialog):
    def __init__(self, core, parent=None):
        super().__init__(parent=None)
        self.core = core
        self.parent = parent

        self.log_loader = LogLoader(self.core)
        self.log_loader_thread = QThread()
        self.log_loader.moveToThread(self.log_loader_thread)
        self.log_loader.done.connect(self.on_loaded)
        self.log_loader_thread.started.connect(self.log_loader.load)

        self.setMinimumSize(800, 600)
        self.setWindowTitle("{} - Debug Information".format(APP_NAME))

        self.plaintextedit = QPlainTextEdit(self)
        self.plaintextedit.setPlainText("Loading logs; please wait...")
        self.plaintextedit.setReadOnly(True)
        font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        self.plaintextedit.setFont(font)
        self.plaintextedit.setStyleSheet(
            "QPlainTextEdit { background-color: black; color: lime }")

        self.scrollbar = self.plaintextedit.verticalScrollBar()

        self.reload_button = QPushButton("Reload")
        self.reload_button.clicked.connect(self.load)

        self.checkbox = QCheckBox(
            "Conceal potentially-identifying information", self)
        self.checkbox.setCheckState(Qt.Checked)
        self.checkbox.stateChanged.connect(self.on_checkbox_state_changed)

        self.filter_info_text = (
            "When enabled, {} will filter some information that could be used "
            "to identify a user or computer. This feature is not perfect, "
            "however, nor is it a substitute for manually checking logs for "
            "sensitive information before sharing.".format(APP_NAME))
        self.filter_info_button = QPushButton()
        self.filter_info_button.setToolTip(self.filter_info_text)
        self.filter_info_button.setFlat(True)
        self.filter_info_button.setFocusPolicy(Qt.NoFocus)
        self.filter_info_button.setIcon(QIcon(resource("question")))
        self.filter_info_button.setIconSize(QSize(13, 13))
        if sys.platform == "darwin":
            self.filter_info_button.setFixedSize(16, 16)
        else:
            self.filter_info_button.setFixedSize(13, 13)
        self.filter_info_button.clicked.connect(
            self.on_filter_info_button_clicked)

        self.copy_button = QPushButton("Copy to clipboard")
        self.copy_button.clicked.connect(self.copy_to_clipboard)

        self.export_button = QPushButton("Export to file...")
        self.export_button.setDefault(True)
        self.export_button.clicked.connect(self.export_to_file)

        checkbox_layout = QGridLayout()
        checkbox_layout.setHorizontalSpacing(0)
        checkbox_layout.addWidget(self.checkbox, 1, 1)
        checkbox_layout.addWidget(self.filter_info_button, 1, 2)

        buttons_layout = QGridLayout()
        buttons_layout.addWidget(self.reload_button, 1, 1)
        buttons_layout.addWidget(self.copy_button, 1, 2)
        buttons_layout.addWidget(self.export_button, 1, 3)

        bottom_layout = QGridLayout()
        bottom_layout.addLayout(checkbox_layout, 1, 1)
        bottom_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, 0), 1,
                              2)
        bottom_layout.addLayout(buttons_layout, 1, 3)

        layout = QGridLayout(self)
        layout.addWidget(self.plaintextedit, 1, 1)
        layout.addLayout(bottom_layout, 2, 1)

    def on_checkbox_state_changed(self, state):
        scrollbar_position = self.scrollbar.value()
        if state == Qt.Checked:
            self.plaintextedit.setPlainText(self.log_loader.filtered_content)
        else:
            self.plaintextedit.setPlainText(self.log_loader.content)
        # Needed on some platforms to maintain scroll step accuracy/consistency
        self.scrollbar.setValue(self.scrollbar.maximum())
        self.scrollbar.setValue(scrollbar_position)

    def on_filter_info_button_clicked(self):
        msgbox = QMessageBox(self)
        msgbox.setIcon(QMessageBox.Information)
        if sys.platform == "darwin":
            msgbox.setText("About Log Filtering")
            msgbox.setInformativeText(self.filter_info_text)
        else:
            msgbox.setWindowTitle("About Log Filtering")
            msgbox.setText(self.filter_info_text)
        msgbox.show()

    def on_loaded(self):
        self.on_checkbox_state_changed(self.checkbox.checkState())
        self.log_loader_thread.quit()
        self.log_loader_thread.wait()

    def load(self):
        if self.log_loader_thread.isRunning():
            logging.warning("LogLoader thread is already running; returning")
            return
        self.log_loader_thread.start()

    def copy_to_clipboard(self):
        for mode in get_clipboard_modes():
            set_clipboard_text(self.plaintextedit.toPlainText(), mode)
        self.close()

    def export_to_file(self):
        dest, _ = QFileDialog.getSaveFileName(
            self,
            "Select a destination",
            os.path.join(os.path.expanduser("~"),
                         APP_NAME + " Debug Information.txt"),
        )
        if not dest:
            return
        try:
            with atomic_write(dest, mode="w", overwrite=True) as f:
                f.write(self.plaintextedit.toPlainText())
        except Exception as e:  # pylint: disable=broad-except
            logging.error("%s: %s", type(e).__name__, str(e))
            error(
                self,
                "Error exporting debug information",
                str(e),
            )
            return
        self.close()
Esempio n. 39
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. 40
0
class Ui_Window(QDialog, Ui_Dialog):
    def __init__(self, parent=None):
        super(Ui_Window, self).__init__(parent=parent)
        self.setupUi(self)

        font = QFont()
        font.setFamily("微软雅黑")
        font.setPointSize(9)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        self.buttonBox.button(QDialogButtonBox.Ok).setFont(font)
        self.buttonBox.button(QDialogButtonBox.Ok).setText("确定")
        self.buttonBox.button(QDialogButtonBox.Cancel).setFont(font)
        self.buttonBox.button(QDialogButtonBox.Cancel).setText("取消")

        vlayout = QVBoxLayout(self)
        vlayout.addWidget(self.splitter)
        vlayout.setContentsMargins(0, 0, 10, 10)
        self.splitter.setGeometry(0, 0, self.width(), self.height())
        self.splitter.setOrientation(Qt.Horizontal)
        self.splitter.setProperty("Stretch", SplitterState.collapsed)
        self.splitter.setProperty("Dock", Dock.right)
        self.splitter.setProperty("WidgetToHide", self.txt_log)
        self.splitter.setProperty("ExpandParentForm", True)

        self.splitter.setSizes([600, self.splitter.width() - 590])
        self.resize(self.splitter.width(), self.splitter.height())

        self.paras = {}  # 存储参数信息
        self.selIndex = QModelIndex()
        self.table_init()
        log.setLogViewer(parent=self, logViewer=self.txt_log)
        self.txt_log.setReadOnly(True)

        self.btn_addressFile.clicked.connect(self.open_addressFile)
        self.buttonBox.clicked.connect(self.buttonBox_clicked)

        self.btn_addRow.clicked.connect(self.btn_addRow_Clicked)
        self.btn_removeRow.clicked.connect(self.btn_removeBtn_clicked)
        self.btn_obtainMeta.clicked.connect(self.btn_obtainMeta_clicked)
        self.btn_saveMetaFile.clicked.connect(self.btn_saveMetaFile_clicked)
        self.splitter.splitterMoved.connect(self.splitterMoved)
        self.splitter.handle(1).handleClicked.connect(self.handleClicked)
        self.tbl_address.clicked.connect(self.table_index_clicked)
        self.tbl_address.verticalHeader().sectionClicked.connect(
            self.table_section_clicked)

        #  最后运算过程放至到另一个线程避免GUI卡住
        self.thread = QThread(self)
        self.crawlVectorThread = crawlVectorWorker()
        self.crawlVectorThread.moveToThread(self.thread)
        self.crawlVectorThread.crawl.connect(
            self.crawlVectorThread.crawlVector)
        self.crawlVectorThread.crawlBatch.connect(
            self.crawlVectorThread.crawlVectorBatch)
        self.crawlVectorThread.finished.connect(self.threadStop)

    def showEvent(self, a0: QShowEvent) -> None:
        log.setLogViewer(parent=self, logViewer=self.txt_log)
        self.table_layout()

    def resizeEvent(self, a0: QResizeEvent) -> None:
        self.table_layout()

    def splitterMoved(self):
        self.table_layout()

    def handleClicked(self):
        self.table_layout()

    def table_layout(self):
        self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4)
        self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2)
        self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.2)
        self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2)

    def threadStop(self):
        self.thread.quit()

    @Slot(QAbstractButton)
    def buttonBox_clicked(self, button: QAbstractButton):
        if button == self.buttonBox.button(QDialogButtonBox.Ok):
            if not self.check_paras():
                return

            self.thread.start()
            self.run_process()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.close()

    def run_process(self):
        rows = range(0, self.tbl_address.model().rowCount(QModelIndex()))
        for row in rows:
            url_index, service_index, url, service = self.return_url_and_level(
                row)
            # layername_index = self.tbl_address.model().index(row, 2, QModelIndex())
            output_index = self.tbl_address.model().index(
                row, 3, QModelIndex())
            # layername = str(self.tbl_address.model().data(layername_index, Qt.DisplayRole)).strip()
            output = str(self.tbl_address.model().data(
                output_index, Qt.DisplayRole)).strip()

            if service != "*":
                key = url + "_" + str(service)
                sr = self.paras[key]['spatialReference']
                service = self.paras[key]['service']
                service_name = self.paras[key]['service_name']
                layername = self.paras[key]['new_layername']
                res_url = url + "/" + str(service)

                if self.paras[key]['output'] != "":
                    output = self.paras[key]['output']

                if layername == "":
                    layername = self.paras[key]['old_layername']

                # crawl_vector(res_url, service_name=service_name, layer_order=service, layer_name=layername, output_path=output, sr=sr)
                self.crawlVectorThread.crawl.emit(res_url, service_name,
                                                  str(service), layername,
                                                  output, sr)
            else:
                key_all = url + "_*"
                if self.paras[key_all]['output'] != "":
                    output = self.paras[key_all]['output']

                self.crawlVectorThread.crawlBatch.emit(url, key_all, output,
                                                       self.paras)

    def check_paras(self):
        rows = range(0, self.tbl_address.model().rowCount(QModelIndex()))
        for row in rows:
            url_index, service_index, url, service = self.return_url_and_level(
                row)
            layername_index = self.tbl_address.model().index(
                row, 2, QModelIndex())
            output_index = self.tbl_address.model().index(
                row, 3, QModelIndex())
            layername = str(self.tbl_address.model().data(
                layername_index, Qt.DisplayRole)).strip()
            output = str(self.tbl_address.model().data(
                output_index, Qt.DisplayRole)).strip()

            if url == "":
                log.error('第{}行缺失必要参数"地址",请补全!'.format(row), dialog=True)
                return False
            if service == "":
                log.error('第{}行缺失必要参数"服务号",请补全!'.format(row), dialog=True)
                return False

            # in_format = get_suffix(in_path)
            # if in_format is None:
            #     log.error('第{}行的输入数据格式不支持!目前只支持csv, excel和dbf'.format(row), dialog=True)
            #     return False
            # else:
            #     in_wks = workspaceFactory().get_factory(in_format)
            #     if in_wks.driver is None:
            #         log.error("缺失图形文件引擎{}!".format(in_wks.driverName))
            #         return False

            out_format = get_suffix(output)
            out_wks = workspaceFactory().get_factory(DataType.fileGDB)

            if out_wks.driver is None:
                log.error("缺失图形文件引擎{}!".format(out_wks.driverName))
                return False

            if service != "*":
                key = url + "_" + str(service)
                url_lst = url.split(r'/')
                service_name = url_lst[-2]
                # filepath, filename = os.path.split(output)

                if layername == "":
                    layername = self.paras[key]['old_layername']
                    log.warning('第{}行缺失非必要参数"输出图层名",将使用默认值"{}".'.format(
                        row + 1, layername))

                if out_format != DataType.fileGDB:
                    # if os.path.splitext(filename)[1] != '.gdb':
                    gdb_name = service_name + ".gdb"
                    if output == "":
                        if not os.path.exists("res"):
                            os.makedirs("res")
                        output = os.path.join(os.path.abspath("res"), gdb_name)
                        self.paras[key]['output'] = output
                        log.warning('第{}行缺失非必要参数"输出路径",将使用默认值"{}".'.format(
                            row + 1, output))
                    else:
                        output = os.path.join(output, gdb_name)
                        self.paras[key]['output'] = output
                        log.warning(
                            '第{}行"输出路径"参数缺失输出gdb数据库名,将使用默认值"{}".'.format(
                                row + 1, gdb_name))
            else:
                filepath, filename = os.path.split(output)
                key_all = url + "_*"
                # if os.path.splitext(filename)[1] != '.gdb':
                if out_format != DataType.fileGDB:
                    gdb_name = urlEncodeToFileName(url) + ".gdb"
                    output = os.path.join(output, gdb_name)
                    self.paras[key_all]['output'] = output
                    log.warning('第{}行缺失非必要参数"输出路径",将使用默认值"{}".'.format(
                        row + 1, output))
        return True

    @Slot(int)
    def table_section_clicked(self, section):
        self.row_clicked(section)

    @Slot(QModelIndex)
    def table_index_clicked(self, index):
        self.row_clicked(index.row())

    @Slot()
    def btn_addRow_Clicked(self):
        selModel = self.tbl_address.selectionModel()
        if selModel is None:
            return
        if len(selModel.selectedIndexes()) == 0:
            self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0)
            next_index = self.model.index(
                self.model.rowCount(QModelIndex()) - 1, 0)
        elif len(selModel.selectedRows()) == 1:
            next_row = selModel.selectedRows()[0].row() + 1
            self.model.addEmptyRow(next_row, 1, 0)
            next_index = self.model.index(next_row, 0)

        selModel.select(
            next_index,
            QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
        self.tbl_address.setFocus()

    @Slot()
    def btn_removeBtn_clicked(self):
        index_list = []
        selModel = self.tbl_address.selectionModel()
        if selModel is None:
            return
        if len(selModel.selectedRows()) < 1:
            return
        for model_index in selModel.selectedRows():
            index = QPersistentModelIndex(model_index)
            index_list.append(index)

        oldrow = index.row()
        for index in index_list:
            self.model.removeRows(index.row(), 1, 0)

        if self.model.rowCount(QModelIndex()) == 1:
            next_index = self.model.index(0, 0)
        else:
            next_index = self.model.index(oldrow, 0)
        # next_index = self.model.index(self.model.rowCount(QModelIndex()) - 1, 0)
        selModel.select(
            next_index,
            QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
        self.tbl_address.setFocus()

    @Slot()
    def open_addressFile(self):
        fileName, fileType = QFileDialog.getOpenFileName(
            self, "选择矢量服务参数文件", os.getcwd(), "All Files(*)")
        self.txt_addressFile.setText(fileName)

        if fileName == "":
            return

        try:
            with open(fileName, 'r', encoding='utf-8') as f:
                self.paras = json.load(f)

            self.add_address_rows_from_paras()
        except:
            if self.model.rowCount(QModelIndex()) > 0:
                self.model.removeRows(
                    self.model.rowCount(QModelIndex()) - 1, 1, QModelIndex())
            log.error("读取参数文件失败!", dialog=True)

    def add_address_rows_from_paras(self):
        keys = self.paras["exports"]
        for key in keys:
            if key in self.paras:
                row = self.model.rowCount(QModelIndex())
                self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1,
                                       0)

                url = self.paras[key]['url']
                service = self.paras[key]['service']
                output = self.paras[key]['output']
                layername = self.paras[key]['new_layername']

                url_index = self.tbl_address.model().index(row, 0)
                self.tbl_address.model().setData(url_index, url)
                service_index = self.tbl_address.model().index(row, 1)
                self.tbl_address.model().setData(service_index, service)
                index = self.tbl_address.model().index(row, 2)
                self.tbl_address.model().setData(index, layername)
                index = self.tbl_address.model().index(row, 3)
                self.tbl_address.model().setData(index, output)

                editor_delegate = self.tbl_address.itemDelegate(service_index)
                key_all = url + "_*"

                if key_all in self.paras:
                    services = self.paras[key_all]['services']
                    if isinstance(editor_delegate, vectorTableDelegate):
                        self.model.setLevelData(key, services)

    @Slot()
    def btn_saveMetaFile_clicked(self):
        datas = self.tbl_address.model().datas

        bHasData = False
        for i in datas:
            for j in i:
                if j != '':
                    bHasData = True
                    break
            else:
                continue
            break

        rows = range(0, len(datas))
        if bHasData and bool(self.paras):
            fileName, fileType = QFileDialog.getSaveFileName(
                self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)")

            self.paras['exports'] = []

            for row in rows:
                url = datas[row][self.url_no]
                service = datas[row][self.service_no]

                if service == "":
                    continue

                key = str(url) + "_" + str(service)

                if key in self.paras:
                    self.paras[key]['new_layername'] = datas[row][2]
                    self.paras[key]['output'] = datas[row][3]
                    self.paras['exports'].append(key)
        try:
            if fileName != '':
                with open(fileName, 'w', encoding='UTF-8') as f:
                    json.dump(self.paras, f, ensure_ascii=False)
        except:
            log.error("文件存储路径错误,无法保存!", parent=self, dialog=True)

    def table_init(self):
        self.tbl_address.setStyle(mTableStyle())

        self.tbl_address.horizontalHeader().setStretchLastSection(True)
        self.tbl_address.verticalHeader().setDefaultSectionSize(20)
        self.tbl_address.verticalHeader().setSectionResizeMode(
            QHeaderView.Fixed)  # 行高固定

        color = self.palette().color(QPalette.Button)
        self.tbl_address.horizontalHeader().setStyleSheet(
            "QHeaderView::section {{ background-color: {}}}".format(
                color.name()))
        self.tbl_address.verticalHeader().setStyleSheet(
            "QHeaderView::section {{ background-color: {}}}".format(
                color.name()))
        self.tbl_address.setStyleSheet(
            "QTableCornerButton::section {{ color: {}; border: 1px solid; border-color: {}}}"
            .format(color.name(), color.name()))

        self.tbl_address.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.tbl_address.setEditTriggers(QAbstractItemView.SelectedClicked
                                         | QAbstractItemView.DoubleClicked)
        self.tbl_address.DragDropMode(QAbstractItemView.InternalMove)
        self.tbl_address.setSelectionBehavior(QAbstractItemView.SelectRows
                                              | QAbstractItemView.SelectItems)
        self.tbl_address.setDefaultDropAction(Qt.MoveAction)

        self.tbl_address.horizontalHeader().setSectionsMovable(False)
        self.tbl_address.setDragEnabled(True)
        self.tbl_address.setAcceptDrops(True)

        self.service_no = 1  # 服务名字段的序号
        self.url_no = 0  # url地址的序号

        self.model = TableModel()

        self.model.setHeaderData(0, Qt.Horizontal, "地址", Qt.DisplayRole)
        self.model.setHeaderData(1, Qt.Horizontal, "服务号", Qt.DisplayRole)
        self.model.setHeaderData(2, Qt.Horizontal, "输出图层名", Qt.DisplayRole)
        self.model.setHeaderData(3, Qt.Horizontal, "输出路径", Qt.DisplayRole)

        delegate = vectorTableDelegate(
            self,
            [None, {
                'type': 'c'
            }, None, {
                'text': "请选择或创建文件数据库",
                'type': "d"
            }])
        self.tbl_address.setModel(self.model)
        self.tbl_address.setItemDelegate(delegate)

    @Slot()
    def btn_obtainMeta_clicked(self):
        selModel = self.tbl_address.selectionModel()

        if selModel is None:
            return

        indexes = selModel.selectedIndexes()

        ## 如果有被选中的行,则只获取被选中行的信息
        if len(indexes) > 0:
            rows = sorted(
                set(index.row()
                    for index in self.tbl_address.selectedIndexes()))
        else:
            rows = range(0, self.tbl_address.model().rowCount(QModelIndex()))

        for row in rows:
            url_index, service_index, url, service = self.return_url_and_level(
                row)
            editor_delegate = self.tbl_address.itemDelegate(service_index)

            if url == "": continue

            getInfo = get_paraInfo(url)

            key = url + "_*"
            if getInfo is None:
                log.error(url + "无法获取远程参数信息,请检查地址是否正确以及网络是否连通!")
                continue
            else:
                log.info(url + "参数信息获取成功!")

            self.setAllParaToMemory(url, getInfo)

            services = self.paras[key]['services']
            if isinstance(editor_delegate, vectorTableDelegate):
                self.model.setLevelData(key, services)

            if service != "*" and service != "":
                layername_index = self.tbl_address.model().index(row, 2)
                if url + "_" + str(service) in self.paras:
                    layername = self.paras[url + "_" +
                                           str(service)]['old_layername']
                    self.tbl_address.model().setData(layername_index,
                                                     layername)

    def setAllParaToMemory(self, url, getInfo):
        xmin = xmax = ymin = ymax = sp = ""

        services = ["*"]
        if 'layers' in getInfo.keys():
            layers = getInfo['layers']
            for layer in layers:
                if 'subLayerIds' in layer:
                    if layer['subLayerIds'] is None:
                        if 'id' in layer:
                            service = layer['id']
                            services.append(service)
                            new_url = url + "/" + str(service)
                            layer_getInfo = get_paraInfo(new_url)
                            self.setParaToMemory(url, str(service),
                                                 layer_getInfo)

        if 'fullExtent' in getInfo.keys():
            if 'xmin' in getInfo['fullExtent']:
                xmin = getInfo['fullExtent']['xmin']
            if 'xmax' in getInfo['fullExtent']:
                xmax = getInfo['fullExtent']['xmax']
            if 'ymin' in getInfo['fullExtent']:
                ymin = getInfo['fullExtent']['ymin']
            if 'ymax' in getInfo['fullExtent']:
                ymax = getInfo['fullExtent']['ymax']

        if 'spatialReference' in getInfo.keys():
            if 'wkid' in getInfo['spatialReference']:
                sp = getInfo['spatialReference']['wkid']

        url_encodeStr = urlEncodeToFileName(url)
        self.paras[url + "_*"] = {
            'url': url,
            'service': "*",
            'code': url_encodeStr,
            'services': services,
            'xmin': xmin,
            'xmax': xmax,
            'ymin': ymin,
            'ymax': ymax,
            'spatialReference': sp,
            "output": ""
        }

    def setParaToMemory(self, url, service, getInfo):
        xmin = xmax = ymin = ymax = sp = layername = ""

        key = url + "_" + service

        if 'id' in getInfo.keys():
            service = getInfo['id']
            if 'name' in getInfo.keys():
                layername = getInfo['name']

        if service == "": return

        if 'extent' in getInfo.keys():
            if 'xmin' in getInfo['extent']:
                xmin = getInfo['extent']['xmin']
            if 'xmax' in getInfo['extent']:
                xmax = getInfo['extent']['xmax']
            if 'ymin' in getInfo['extent']:
                ymin = getInfo['extent']['ymin']
            if 'ymax' in getInfo['extent']:
                ymax = getInfo['extent']['ymax']
            if 'spatialReference' in getInfo['extent']:
                if 'wkid' in getInfo['extent']['spatialReference']:
                    sp = getInfo['extent']['spatialReference']['wkid']

        url_encodeStr = urlEncodeToFileName(url)
        url_lst = url.split(r'/')
        service_name = url_lst[-3]
        self.paras[key] = {
            'url': url,
            'service': service,
            'service_name': service_name,
            'code': url_encodeStr,
            'old_layername': layername,
            'xmin': xmin,
            'xmax': xmax,
            'ymin': ymin,
            'ymax': ymax,
            'spatialReference': sp,
            'new_layername': "",
            'output': ""
        }

    def row_clicked(self, logicRow):
        sel_index = self.tbl_address.model().index(logicRow, 0, QModelIndex())
        self.update_txt_info(sel_index)
        self.selIndex = sel_index

    def return_url_and_level(self, logicRow):
        url_index = self.tbl_address.model().index(logicRow, self.url_no)
        level_index = self.tbl_address.model().index(logicRow, self.service_no)
        url = self.tbl_address.model().data(url_index, Qt.DisplayRole)
        level = self.tbl_address.model().data(level_index, Qt.DisplayRole)
        return url_index, level_index, url, level

    def update_txt_info(self, index: QModelIndex, level=-1):
        key1_index = self.tbl_address.model().index(index.row(), 0)
        key1 = self.tbl_address.model().data(key1_index, Qt.DisplayRole)
        key2_index = self.tbl_address.model().index(index.row(), 1)
        key2 = self.tbl_address.model().data(key2_index, Qt.DisplayRole)

        self.txt_xmin.setText("")
        self.txt_xmax.setText("")
        self.txt_ymin.setText("")
        self.txt_ymax.setText("")
        self.txt_spatialReference.setText("")
        self.lbl_layer.setText("")

        if level == -1:
            key = key1 + "_" + str(key2)
        else:
            key = key1 + "_" + str(level)

        if key not in self.paras:
            return

        getInfo = self.paras[key]

        if 'old_layername' in getInfo:
            self.lbl_layer.setText(str(getInfo['old_layername']))
            layername_index = self.tbl_address.model().index(index.row(), 2)
            if key1 + "_" + str(level) in self.paras:
                layername = self.paras[key1 + "_" +
                                       str(level)]['old_layername']
                self.tbl_address.model().setData(key2_index, level)
                self.tbl_address.model().setData(layername_index, layername)
        if 'xmin' in getInfo:
            self.txt_xmin.setText(str(getInfo['xmin']))
            self.txt_xmin.home(False)
        if 'xmax' in getInfo:
            self.txt_xmax.setText(str(getInfo['xmax']))
            self.txt_xmax.home(False)
        if 'ymin' in getInfo:
            self.txt_ymin.setText(str(getInfo['ymin']))
            self.txt_ymin.home(False)
        if 'ymax' in getInfo:
            self.txt_ymax.setText(str(getInfo['ymax']))
            self.txt_ymax.home(False)
        if 'spatialReference' in getInfo:
            self.txt_spatialReference.setText(str(getInfo['spatialReference']))
            self.txt_spatialReference.home(False)
Esempio n. 41
0
class Window(QMainWindow):
    # 播放控制信号
    pause = pyqtSignal()
    resume = pyqtSignal()
    stop = pyqtSignal()
    # 传递片段到已移动到子线程的视频、音频对象的信号
    set_video = pyqtSignal(object)
    set_audio = pyqtSignal(object, int, int, int)

    # 界面设定
    def setupUi(self):
        self.setWindowTitle("VideoGenerator")
        self.centralwidget = QWidget(self)

        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()

        self.horizontalSlider = QtWidgets.QSlider(self.centralwidget)
        self.horizontalSlider.setOrientation(Qt.Horizontal)
        self.horizontalSlider.setRange(0, 0)
        self.horizontalLayout_2.addWidget(self.horizontalSlider)

        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setText("")
        self.horizontalLayout_2.addWidget(self.label)

        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed,
                                           QtWidgets.QSizePolicy.Fixed)
        self.label_target = QtWidgets.QLabel(self.centralwidget)
        self.label_target.setText("目标")
        self.label_target.setSizePolicy(sizePolicy)
        self.horizontalLayout_3.addWidget(self.label_target)
        self.gridLayout.addLayout(self.horizontalLayout_3, 3, 0, 1, 1)

        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.label_background = QtWidgets.QLabel(self.centralwidget)
        self.label_background.setSizePolicy(sizePolicy)
        self.label_background.setText("背景")
        self.horizontalLayout_4.addWidget(self.label_background)
        self.gridLayout.addLayout(self.horizontalLayout_4, 4, 0, 1, 1)

        self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
        self.label_audio = QtWidgets.QLabel(self.centralwidget)
        self.label_audio.setSizePolicy(sizePolicy)
        self.label_audio.setText("音频")
        self.horizontalLayout_5.addWidget(self.label_audio)
        self.gridLayout.addLayout(self.horizontalLayout_5, 5, 0, 1, 1)

        self.gridLayout.addLayout(self.horizontalLayout_2, 2, 0, 1, 1)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)

        self.pushButton_7 = QtWidgets.QPushButton(self.centralwidget)
        self.horizontalLayout.addWidget(self.pushButton_7)
        self.pushButton_6 = QtWidgets.QPushButton(self.centralwidget)
        self.horizontalLayout.addWidget(self.pushButton_6)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setText("")
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.horizontalLayout.addWidget(self.pushButton_4)
        self.pushButton_8 = QtWidgets.QPushButton(self.centralwidget)
        self.horizontalLayout.addWidget(self.pushButton_8)
        self.pushButton_10 = QtWidgets.QPushButton(self.centralwidget)
        self.horizontalLayout.addWidget(self.pushButton_10)
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)

        self.pushButton_2.setIcon(self.style().standardIcon(
            QStyle.SP_MediaPlay))
        self.pushButton_6.setText("打开背景")
        self.pushButton_7.setText("打开目标")
        self.pushButton.setText("打开音频")
        self.pushButton_4.setText("设置切分起始时间")
        self.pushButton_8.setText("原地切分")
        self.pushButton_10.setText("重复")
        self.pushButton_3.setText("生成")
        self.pushButton_3.setEnabled(False)
        self.pushButton_2.setEnabled(False)
        self.pushButton_4.setEnabled(False)
        self.pushButton_10.setEnabled(False)
        self.pushButton_8.setEnabled(False)

        self.openGLWidget = MyOpenGLWidget(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                           QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.openGLWidget.sizePolicy().hasHeightForWidth())
        self.openGLWidget.setSizePolicy(sizePolicy)

        self.gridLayout.addWidget(self.openGLWidget, 0, 0, 1, 1)
        self.horizontalLayout.addWidget(self.pushButton_3)
        self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)

        self.listWidget_3 = QtWidgets.QListWidget(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                           QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.listWidget_3.sizePolicy().hasHeightForWidth())
        self.listWidget_3.setSizePolicy(sizePolicy)
        self.listWidget_3.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.listWidget_3.setDragEnabled(True)
        self.listWidget_3.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove)
        self.listWidget_3.setDefaultDropAction(Qt.MoveAction)
        self.listWidget_3.setFlow(QtWidgets.QListView.LeftToRight)
        self.listWidget_3.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listWidget_3.setFixedHeight(listHeight)
        self.listWidget_3.setHorizontalScrollMode(QListWidget.ScrollPerPixel)
        self.horizontalLayout_5.addWidget(self.listWidget_3)

        self.listWidget = QtWidgets.QListWidget(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                           QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.listWidget.sizePolicy().hasHeightForWidth())
        self.listWidget.setSizePolicy(sizePolicy)
        self.listWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.listWidget.setDragEnabled(True)
        self.listWidget.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove)
        self.listWidget.setDefaultDropAction(Qt.MoveAction)
        self.listWidget.setFlow(QtWidgets.QListView.LeftToRight)
        self.listWidget.setLayoutMode(QtWidgets.QListView.SinglePass)
        self.listWidget.setFixedHeight(listHeight)
        self.listWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listWidget.setHorizontalScrollMode(QListWidget.ScrollPerPixel)
        self.horizontalLayout_3.addWidget(self.listWidget)

        self.listWidget_2 = QtWidgets.QListWidget(self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
                                           QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.listWidget_2.sizePolicy().hasHeightForWidth())
        self.listWidget_2.setSizePolicy(sizePolicy)
        self.listWidget_2.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.listWidget_2.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.listWidget_2.setDragEnabled(True)
        self.listWidget_2.setDragDropMode(
            QtWidgets.QAbstractItemView.InternalMove)
        self.listWidget_2.setDefaultDropAction(Qt.MoveAction)
        self.listWidget_2.setFlow(QtWidgets.QListView.LeftToRight)
        self.listWidget_2.setFixedHeight(listHeight)
        self.listWidget_2.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listWidget_2.setHorizontalScrollMode(QListWidget.ScrollPerPixel)
        self.horizontalLayout_4.addWidget(self.listWidget_2)

        self.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(self)
        self.menubar.setGeometry(QRect(0, 0, 905, 26))
        self.setMenuBar(self.menubar)
        self.statusbar = QStatusBar(self)
        self.progressBar = QProgressBar()
        self.progressBar.setVisible(False)
        self.progressBar.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Fixed)
        self.progressBar.setFixedHeight(20)
        self.statusbar.addPermanentWidget(self.progressBar)
        self.setStatusBar(self.statusbar)

    # 保存该片段到本地
    def saveItem(self):
        self.returnIDLE()
        item = self.cur_listWidget.currentItem()
        self.saveitem = item
        if item.video:
            self.saveObject = SaveTemp(item.video.copy())
        else:
            self.saveObject = SaveTemp(item.audio.copy())
        item.setFlags(item.flags()
                      & ((Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff))
        self.saveThread = QThread()
        self.saveObject.moveToThread(self.saveThread)
        self.saveObject.finish_process.connect(self.finishSave)
        self.saveObject.message.connect(self.thread_message)
        self.saveObject.progress.connect(self.thread_progress)
        self.saveThread.started.connect(self.saveObject.process)
        self.progressBar.setVisible(True)
        self.saveThread.start()

    def setupData(self):
        # 记录当前播放视频、音频
        self.audio_backend = None
        self.video_backend = None
        # 切分起始结束时刻
        self.cutter_end = 0
        self.cutter_start = 0
        # 当前选中的行
        self.cur_listWidget = None

        # 播放、处理音视频的子线程及相应对象
        self.video = None
        self.audio = None
        self.calthread = None
        self.audioThread = None
        self.compositeObject = None
        self.compositeThread = None
        # 状态变量
        self.flag = 0
        self.state = State.IDLE
        self.saveObject = None
        self.saveThread = None

        # 要处理的所有片段
        self.items, self.items_2, self.items_3 = [], [], []
        self.saveitem = None

    # 各信号与触发的相应函数绑定
    def setupSlot(self):
        self.pushButton.clicked.connect(self.openAudio)
        self.pushButton_7.clicked.connect(self.openTarget)
        self.pushButton_6.clicked.connect(self.openBackground)
        self.pushButton_8.clicked.connect(self.cut)
        self.pushButton_10.clicked.connect(self.copySelected)
        self.pushButton_3.clicked.connect(self.setupPara)
        self.pushButton_2.clicked.connect(self.play)
        self.pushButton_4.clicked.connect(self.setStart)
        self.horizontalSlider.sliderMoved.connect(self.setPosition)
        self.horizontalSlider.sliderMoved.connect(self.setApos)
        self.horizontalSlider.sliderReleased.connect(self.openGLWidget.update)
        self.listWidget.itemDoubleClicked.connect(self.setupTarget)
        self.listWidget_2.itemDoubleClicked.connect(self.setupTarget)
        self.listWidget_3.itemDoubleClicked.connect(self.setupTarget)
        self.listWidget.customContextMenuRequested.connect(
            self.createContextMenu)
        self.listWidget_2.customContextMenuRequested.connect(
            self.createContextMenu2)
        self.listWidget_3.customContextMenuRequested.connect(
            self.createContextMenu3)

    def __init__(self):
        super(Window, self).__init__()
        self.setupData()
        self.setupUi()
        self.setupSlot()

    # 设置切分起始时间并显示
    def setStart(self):
        self.cutter_start = self.video.t
        duration = self.video_backend.duration if self.video_backend else self.audio_backend.duration
        currentTime = QTime(
            int(self.cutter_start / 3600) % 60,
            int(self.cutter_start / 60) % 60, self.cutter_start % 60,
            (self.cutter_start * 1000) % 1000)
        format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
        tStr = currentTime.toString(format)
        self.statusbar.showMessage("设置起始时间%s" % tStr, 1000)

    # 关闭窗口前清理子线程
    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
        self.stop.emit()
        if self.calthread:
            self.calthread.quit()
            self.calthread.wait()
        if self.audioThread:
            self.audioThread.quit()
            self.audioThread.wait()
        super().closeEvent(a0)

    # 重复添加选中的item
    def copySelected(self):
        self.cur_listWidget.addItem(self.cur_listWidget.currentItem().copy())

    # 在对话框里设置处理参数
    def setupPara(self):
        def choose():
            if self.gauss.isChecked():
                self.gauss_target.setEnabled(True)
                self.gauss_background.setEnabled(True)
            else:
                self.gauss_target.setEnabled(False)
                self.gauss_background.setEnabled(False)

        # 至少选择了目标和背景才能在对话框里设置处理参数
        if self.listWidget.count() and self.listWidget_2.count():
            self.dialog = QtWidgets.QDialog(self)
            self.dialog.setAttribute(Qt.WA_DeleteOnClose)
            self.dialog.setWindowModality(Qt.WindowModal)
            self.dialog.setWindowTitle("准备合成")
            gridLayout = QtWidgets.QGridLayout(self.dialog)
            self.dialog.setLayout(gridLayout)
            self.check = QtWidgets.QCheckBox("全局分身效果", self.dialog)
            self.gauss = QtWidgets.QCheckBox("高斯模糊", self.dialog)
            self.gauss_target = QtWidgets.QCheckBox("目标", self.dialog)
            self.gauss_background = QtWidgets.QCheckBox("背景", self.dialog)
            self.tiktok = QtWidgets.QCheckBox("抖音效果", self.dialog)

            sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed,
                                               QtWidgets.QSizePolicy.Fixed)

            self.gauss_target.setSizePolicy(sizePolicy)
            self.gauss_background.setSizePolicy(sizePolicy)
            self.check.setSizePolicy(sizePolicy)
            self.gauss.setSizePolicy(sizePolicy)
            self.tiktok.setSizePolicy(sizePolicy)
            self.gauss.stateChanged.connect(choose)
            self.gauss_target.setEnabled(False)
            self.gauss_background.setEnabled(False)
            dialogbtns = QDialogButtonBox(QDialogButtonBox.Cancel
                                          | QDialogButtonBox.Ok)
            dialogbtns.button(QDialogButtonBox.Cancel).setText("取消")
            dialogbtns.button(QDialogButtonBox.Ok).setText("开始")
            dialogbtns.rejected.connect(self.dialog.reject)
            dialogbtns.accepted.connect(self.dialog.accept)
            dialogbtns.accepted.connect(self.composite)
            dialogbtns.setSizePolicy(sizePolicy)
            label = QtWidgets.QLabel(self.dialog)
            label.setText("默认分辨率 1280x720")

            gridLayout.addWidget(self.check, 0, 0, 1, 1)
            gridLayout.addWidget(dialogbtns, 4, 0, 1, 3)
            gridLayout.addWidget(label, 3, 0, 1, 1)
            gridLayout.addWidget(self.gauss, 1, 0, 1, 1)
            gridLayout.addWidget(self.tiktok, 2, 0, 1, 1)
            gridLayout.addWidget(self.gauss_target, 1, 1, 1, 1)
            gridLayout.addWidget(self.gauss_background, 1, 2, 1, 1)

            self.dialog.resize(400, 300)
            self.dialog.show()

    def finishSave(self):
        self.progressBar.setVisible(False)
        self.saveitem.setFlags(self.saveitem.flags() | Qt.ItemIsEnabled
                               | Qt.ItemIsSelectable)
        self.saveThread.quit()
        self.saveThread.wait()

    # 若当前进度大于切分起始时间,则对当前播放片段原地切分
    def cut(self):
        if self.video.t <= self.cutter_start:
            QMessageBox.critical(self, "警告", "请在起始时间之后切分")
        elif self.video.t > self.cutter_start:

            if self.video_backend and self.audio_backend:
                self.video_backend = self.video_backend.subclip(
                    self.cutter_start, self.video.t)
                self.audio_backend = self.video_backend.audio
                self.set_video.emit(self.video_backend)
                self.set_audio.emit(
                    self.audio_backend, self.audio_backend.fps,
                    int(1.0 / self.video_backend.fps * self.audio_backend.fps),
                    2)
                self.cur_listWidget.currentItem().setClip(
                    self.video_backend, self.audio_backend)
                self.durationChanged(self.video_backend.duration)
            elif self.video_backend and not self.audio_backend:
                self.video_backend = self.video_backend.subclip(
                    self.cutter_start, self.video.t)
                self.set_video.emit(self.video_backend)
                self.cur_listWidget.currentItem().setClip(
                    self.video_backend, self.audio_backend)
                self.durationChanged(self.video_backend.duration)
            elif not self.video_backend and self.audio_backend:
                self.audio_backend = self.audio_backend.subclip(
                    self.cutter_start, self.video.t)
                self.set_video.emit(self.video_backend)
                self.set_audio.emit(
                    self.audio_backend, self.audio_backend.fps,
                    int(1.0 / self.video.fps * self.audio_backend.fps), 2)
                self.cur_listWidget.currentItem().setClip(
                    self.video_backend, self.audio_backend)
                self.durationChanged(self.audio_backend.duration)

        self.cutter_start = 0

    # 准备生成
    def composite(self):
        # 为防止读写冲突,保护所有待处理片段,先重置回初始状态
        self.returnIDLE()
        targets, backgrounds, audios = [], [], []
        # 将所有待处理片段的item置为不可选中且不可用,并存储这些片段准备让子线程处理
        for i in range(self.listWidget.count()):
            item = self.listWidget.item(i)
            targets.append(item.video)
            self.items.append(item)
            item.setFlags(item.flags()
                          & ((Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff))
        for i in range(self.listWidget_2.count()):
            item_2 = self.listWidget_2.item(i)
            backgrounds.append(item_2.video)
            self.items_2.append(item_2)
            item_2.setFlags(item.flags() & (
                (Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff))
        for i in range(self.listWidget_3.count()):
            item_3 = self.listWidget_3.item(i)
            audios.append(item_3.audio)
            self.items_3.append(item_3)
            item_3.setFlags(item.flags() & (
                (Qt.ItemIsSelectable | Qt.ItemIsEnabled) ^ 0xff))

        # 传递待处理片段及相关参数,绑定开始、结束、处理进度的函数
        self.compositeObject = CompositeObject(
            targets,
            backgrounds,
            audios,
            triple=self.check.isChecked(),
            gauss=self.gauss.isChecked(),
            tiktok=self.tiktok.isChecked(),
            gauss_target=self.gauss_target.isChecked(),
            gauss_background=self.gauss_background.isChecked())
        self.compositeThread = QThread()
        self.compositeObject.moveToThread(self.compositeThread)
        self.compositeThread.started.connect(self.compositeObject.process)
        self.compositeObject.message.connect(self.thread_message)
        self.compositeObject.progress.connect(self.thread_progress)
        self.progressBar.setVisible(True)
        self.compositeObject.finish_process.connect(self.finishComposite)
        self.compositeThread.start()

    # 显示消息和进度
    def thread_message(self, value):
        self.statusbar.showMessage(value)

    def thread_progress(self, value):
        self.progressBar.setValue(value)

    # 状态改变时需要的操作
    def fitState(self):
        if self.state == State.PLAYING:
            self.pushButton_2.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPause))
            self.pushButton_8.setEnabled(False)
        elif self.state == State.FINISHED:
            self.pushButton_2.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))
        elif self.state == State.PAUSE:
            self.pushButton_2.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))
            self.pushButton_8.setEnabled(True)
        elif self.state == State.READY:
            self.pushButton_2.setIcon(self.style().standardIcon(
                QStyle.SP_MediaPlay))
            self.pushButton_2.setEnabled(True)
            self.pushButton_3.setEnabled(True)
            self.pushButton_8.setEnabled(True)
            self.pushButton_10.setEnabled(True)
            self.pushButton_4.setEnabled(True)
        elif self.state == State.IDLE:
            self.audio_backend = None
            self.video_backend = None
            self.horizontalSlider.setRange(0, 0)
            self.updateDurationInfo(0)
            self.pushButton_2.setEnabled(False)
            self.pushButton_3.setEnabled(False)
            self.pushButton_4.setEnabled(False)
            self.pushButton_10.setEnabled(False)
            self.pushButton_8.setEnabled(False)

    # 各片段右键菜单所能进行的简单预处理
    def createContextMenu(self, pos):
        self.cur_listWidget = self.listWidget
        curItem = self.listWidget.itemAt(pos)
        if not curItem or self.state == State.PLAYING:
            return
        popMenu = QMenu(self)
        deleteAction = QAction("删除", self)
        titokAction = QAction("tiktok效果", self)
        popMenu.addAction(deleteAction)
        popMenu.addAction(titokAction)
        deleteAction.triggered.connect(self.deleteItem)
        titokAction.triggered.connect(self.video.tiktok)
        saveAction = QAction("保存本地", self)
        popMenu.addAction(saveAction)
        saveAction.triggered.connect(self.saveItem)
        if curItem.audio:
            AddMusic = QAction("提取音频")
            popMenu.addAction(AddMusic)
            AddMusic.triggered.connect(self.addToAudio)
        popMenu.exec(QCursor.pos())

    def createContextMenu2(self, pos):
        self.cur_listWidget = self.listWidget_2
        curItem = self.listWidget_2.itemAt(pos)
        if not curItem or self.state == State.PLAYING:
            return
        popMenu = QMenu(self)
        deleteAction = QAction("删除", self)
        titokAction = QAction("tiktok效果", self)
        popMenu.addAction(deleteAction)
        popMenu.addAction(titokAction)
        deleteAction.triggered.connect(self.deleteItem)
        titokAction.triggered.connect(self.video.tiktok)
        saveAction = QAction("保存本地", self)
        popMenu.addAction(saveAction)
        saveAction.triggered.connect(self.saveItem)
        if curItem.audio:
            AddMusic = QAction("提取音频")
            popMenu.addAction(AddMusic)
            AddMusic.triggered.connect(self.addToAudio)
        popMenu.exec(QCursor.pos())

    def createContextMenu3(self, pos):
        self.cur_listWidget = self.listWidget_3
        curItem = self.listWidget_3.itemAt(pos)
        if not curItem or self.state == State.PLAYING:
            return
        popMenu = QMenu(self)
        deleteAction = QAction("删除", self)
        popMenu.addAction(deleteAction)
        saveAction = QAction("保存本地", self)
        popMenu.addAction(saveAction)
        saveAction.triggered.connect(self.saveItem)
        deleteAction.triggered.connect(self.deleteItem)
        popMenu.exec(QCursor.pos())

    # 回退到初始状态
    def returnIDLE(self):
        if self.calthread.isRunning() and self.video.timer.isActive():
            self.stop.emit()
        self.state = State.IDLE
        self.fitState()

    # 删除item,若删除的是现在正在播放的,回退到初始状态以防止错误
    def deleteItem(self):
        if self.video_backend:
            if self.cur_listWidget.currentItem().video == self.video_backend:
                self.returnIDLE()
        elif self.audio_backend:
            if self.cur_listWidget.currentItem().audio == self.audio_backend:
                self.returnIDLE()
        ditem = self.cur_listWidget.takeItem(
            self.cur_listWidget.row(self.cur_listWidget.currentItem()))
        del ditem

    # 将目标或背景的音频添加为音频行的item
    def addToAudio(self):
        newName = "(Audio)" + self.cur_listWidget.currentItem().text
        item = MyListWidgetItem(clipName=newName,
                                video=None,
                                audio=self.cur_listWidget.currentItem().audio)
        self.listWidget_3.addItem(item)
        self.setupMedia(item)

    # 结束生成后,使相关item重新可交互并清除工作线程
    def finishComposite(self):
        self.progressBar.setVisible(False)
        for item in self.items:
            item.setFlags(item.flags() | Qt.ItemIsEnabled
                          | Qt.ItemIsSelectable)
        for item in self.items_2:
            item.setFlags(item.flags() | Qt.ItemIsEnabled
                          | Qt.ItemIsSelectable)
        for item in self.items_3:
            item.setFlags(item.flags() | Qt.ItemIsEnabled
                          | Qt.ItemIsSelectable)
        self.compositeThread.quit()
        self.compositeThread.wait()

    # 播放结束
    def processFinish(self):
        self.state = State.FINISHED
        self.fitState()

    # 播放进度改变时移动进度条滑块并记录时间
    def positionChanged(self, position):
        self.horizontalSlider.setValue(position)
        self.updateDurationInfo(position / 10)

    # 播放对象改变时重设时间信息
    def durationChanged(self, duration):
        self.horizontalSlider.setRange(0, duration * 10)
        self.updateDurationInfo(0)

    # 控制视频播放进度
    def setPosition(self, position):
        self.video.setT(position / 10)

    # 控制音频播放进度
    def setApos(self, position):
        if self.audio_backend:
            self.audio.setIndex(
                int(position / 10 * self.audio.fps / self.audio.buffersize))

    # 更新时间信息
    def updateDurationInfo(self, currentInfo):
        # 正确计算时长
        if self.video_backend:
            duration = self.video_backend.duration
        elif self.audio_backend:
            duration = self.audio_backend.duration
        else:
            duration = 0
        # 计算当前时间和总时间并显示
        if currentInfo or duration:
            currentTime = QTime((currentInfo / 3600) % 60,
                                (currentInfo / 60) % 60, currentInfo % 60,
                                (currentInfo * 1000) % 1000)
            totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60,
                              duration % 60, (duration * 1000) % 1000)

            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
            tStr = currentTime.toString(format) + " / " + totalTime.toString(
                format)
        else:
            tStr = ""
        self.label.setText(tStr)

    # 切换要播放的item
    def setupTarget(self, item):
        if self.state != State.IDLE:
            self.stop.emit()
        self.setupMedia(item)
        self.fitState()
        self.cur_listWidget = item.listWidget()

    # 打开目标文件
    def openTarget(self):
        fileName, _ = QFileDialog.getOpenFileName(
            self, "Open Video", QDir.homePath(),
            "Video(*.mp4;*.wmv;*.rmvb;*.avi;*.mkv;*.mov;*.mpeg;*.flv;*.rm;*.mpeg;*.mpg)"
        )
        if fileName != '':
            if self.state != State.IDLE:
                self.stop.emit()

            v = VideoFileClip(fileName)

            item = MyListWidgetItem(fileName.split("/")[-1], v, v.audio)
            self.listWidget.addItem(item)
            self.listWidget.setCurrentItem(item)
            self.cur_listWidget = self.listWidget
            self.setupMedia(item)

    # 打开背景文件
    def openBackground(self):
        fileName, _ = QFileDialog.getOpenFileName(
            self, "Open Video", QDir.homePath(),
            "Video(*.mp4;*.wmv;*.rmvb;*.avi;*.mkv;*.mov;*.mpeg;*.flv;*.rm;*.mpeg;*.mpg)"
        )
        if fileName != '':
            if self.state != State.IDLE:
                self.stop.emit()

            v = VideoFileClip(fileName)
            item = MyListWidgetItem(fileName.split("/")[-1], v, v.audio)

            self.listWidget_2.addItem(item)
            self.listWidget_2.setCurrentItem(item)
            self.cur_listWidget = self.listWidget_2
            self.setupMedia(item)

    # 打开音频文件
    def openAudio(self):
        fileName, _ = QFileDialog.getOpenFileName(
            self, "Open Audio", QDir.homePath(),
            "Audio(*.mp3;*.wav;*.ogg;*.wma;)")
        if fileName != '':
            if self.state != State.IDLE:
                self.stop.emit()
            a = AudioFileClip(fileName)
            item = MyListWidgetItem(clipName=fileName.split("/")[-1],
                                    video=None,
                                    audio=a)

            self.listWidget_3.addItem(item)
            self.listWidget_3.setCurrentItem(item)
            self.cur_listWidget = self.listWidget_3
            self.setupMedia(item)

    # 设置要播放的视频、音频信息
    def setupMedia(self, item):
        duration = item.video.duration if item.video else item.audio.duration
        self.video_backend = item.video
        self.audio_backend = item.audio
        # 还没有播放过片段,建立子线程并初始化相应对象
        if self.flag == 0:
            self.video = Video(item.video)
            self.durationChanged(duration)
            self.calthread = QThread()
            self.video.moveToThread(self.calthread)
            self.video.t_changed.connect(self.positionChanged)
            self.stop.connect(self.video.stop)
            self.pause.connect(self.video.pause)
            self.resume.connect(self.video.resume)
            self.video.finish.connect(self.processFinish)
            self.calthread.started.connect(self.video.work)
            self.set_video.connect(self.video.setClip)
            self.audioThread = QThread()
            if self.audio_backend:
                self.audio = Audio(clip=item.audio,
                                   fps=item.audio.fps,
                                   buffersize=int(1.0 / self.video.fps *
                                                  item.audio.fps),
                                   nbytes=2)
            else:
                self.audio = Audio(clip=None, fps=0, buffersize=0, nbytes=0)
            self.audio.moveToThread(self.audioThread)
            self.audioThread.started.connect(self.audio.work)
            self.set_audio.connect(self.audio.setClip)
        # 已经播放过,此时视频线程与音频线程均存在,设置相应对象信息即可
        else:
            v_fps = item.video.fps if item.video else 30

            if item.video and item.audio:
                self.durationChanged(duration)
                self.set_video.emit(item.video)
                self.set_audio.emit(item.audio, item.audio.fps,
                                    int(1.0 / v_fps * item.audio.fps), 2)
            elif item.video and not item.audio:
                self.set_video.emit(item.video)
                self.set_audio.emit(None, 44100, 0, 2)
                self.durationChanged(duration)
            else:
                self.set_video.emit(item.video)
                self.durationChanged(item.audio.duration)
                self.set_audio.emit(item.audio, item.audio.fps,
                                    int(1.0 / v_fps * item.audio.fps), 2)

        self.state = State.READY
        self.fitState()

    # 点击播放键时根据状态实施相应行为
    def play(self):
        self.flag += 1
        if self.state in (State.FINISHED, State.READY):
            self.state = State.PLAYING
            self.fitState()
            if self.flag == 1:
                self.calthread.start()
                self.audioThread.start()
            else:
                self.resume.emit()

        elif self.state == State.PLAYING:
            self.state = State.PAUSE
            self.pause.emit()
            self.fitState()
        elif self.state == State.PAUSE:
            self.resume.emit()
            self.state = State.PLAYING
            self.fitState()
Esempio n. 42
0
class InferenceViewer(QtGui.QMainWindow):
    def __init__(self, model_name, model_format, image_dir, model_location,
                 label, hierarchy, image_val, input_dims, output_dims,
                 batch_size, output_dir, add, multiply, verbose, fp16, replace,
                 loop, rali_mode, gui, container_logo, fps_file, cpu_name,
                 gpu_name, parent):
        super(InferenceViewer, self).__init__(parent)
        self.parent = parent

        self.model_name = model_name
        self.model_format = model_format
        self.image_dir = image_dir
        self.model_location = model_location
        self.label = label
        self.hierarchy = hierarchy
        self.image_val = image_val
        self.input_dims = input_dims
        self.output_dims = output_dims
        self.batch_size = batch_size
        self.batch_size_int = (int)(batch_size)
        self.output_dir = output_dir
        self.add = add
        self.multiply = multiply
        self.gui = True if gui == 'yes' else False
        self.fp16 = fp16
        self.replace = replace
        self.verbose = verbose
        self.loop = loop
        self.rali_mode = rali_mode
        inputImageDir = os.path.expanduser(image_dir)
        self.total_images = len(os.listdir(inputImageDir))
        self.origImageQueue = queue.Queue()
        self.augImageQueue = queue.Queue()
        self.fps_file = fps_file
        self.inferenceEngine = None
        self.receiver_thread = None

        self.initEngines()

        if self.gui:
            self.container_index = (int)(container_logo)
            self.cpu_name = cpu_name
            self.gpu_name = gpu_name
            self.pauseState = False
            self.showAug = False
            self.elapsedTime = QTime.currentTime()
            self.lastTime = 0
            self.totalElapsedTime = 0.0
            self.totalAccuracy = 0
            self.progIndex = 0
            self.augIntensity = 0.0
            self.imgCount = 0
            self.frameCount = 9
            self.lastIndex = self.frameCount - 1
            self.x = [0]
            self.y = [0]
            self.augAccuracy = []
            self.totalCurve = None
            self.augCurve = None
            self.graph = None
            self.legend = None
            self.lastAugName = None
            self.pen = pg.mkPen('w', width=4)
            self.AMD_Radeon_pixmap = QPixmap("./data/images/AMD_Radeon.png")
            self.AMD_Radeon_white_pixmap = QPixmap(
                "./data/images/AMD_Radeon-white.png")
            self.MIVisionX_pixmap = QPixmap("./data/images/MIVisionX-logo.png")
            self.MIVisionX_white_pixmap = QPixmap(
                "./data/images/MIVisionX-logo-white.png")
            self.EPYC_pixmap = QPixmap("./data/images/EPYC-blue.png")
            self.EPYC_white_pixmap = QPixmap(
                "./data/images/EPYC-blue-white.png")
            self.docker_pixmap = QPixmap("./data/images/Docker.png")
            self.singularity_pixmap = QPixmap("./data/images/Singularity.png")
            self.rali_pixmap = QPixmap("./data/images/RALI.png")
            self.rali_white_pixmap = QPixmap("./data/images/RALI-white.png")
            self.graph_image_pixmap = QPixmap("./data/images/Graph-image.png")
            self.graph_image_white_pixmap = QPixmap(
                "./data/images/Graph-image-white.png")

            self.initUI()
            self.updateTimer = QTimer()
            self.updateTimer.timeout.connect(self.update)
            self.updateTimer.timeout.connect(self.plotGraph)
            self.updateTimer.timeout.connect(self.setProgressBar)
            self.updateTimer.start(40)

    def initUI(self):
        uic.loadUi("inference_viewer.ui", self)
        self.setStyleSheet("background-color: white")
        self.name_label.setText("Model: %s" % (self.model_name))
        self.cpuName_label.setText(self.cpu_name)
        self.gpuName_label.setText(self.gpu_name)
        self.dataset_label.setText("Augmentation set - %d" % (self.rali_mode))
        self.imagesFrame.setStyleSheet(
            ".QFrame {border-width: 20px; border-image: url(./data/images/filmStrip.png);}"
        )
        self.total_progressBar.setStyleSheet(
            "QProgressBar::chunk { background: lightblue; }")
        self.top1_progressBar.setStyleSheet(
            "QProgressBar::chunk { background: green; }")
        self.top5_progressBar.setStyleSheet(
            "QProgressBar::chunk { background: lightgreen; }")
        self.mis_progressBar.setStyleSheet(
            "QProgressBar::chunk { background: red; }")
        self.total_progressBar.setMaximum(self.total_images *
                                          self.batch_size_int)
        self.graph = pg.PlotWidget(title="Accuracy vs Time")
        self.graph.setLabel('left', 'Accuracy', '%')
        self.graph.setLabel('bottom', 'Time', 's')
        self.graph.setYRange(0, 100, padding=0)
        pg.setConfigOptions(antialias=True)
        self.totalCurve = self.graph.plot(pen=self.pen)
        self.augCurve = self.graph.plot(pen=pg.mkPen('b', width=4))
        self.legend = pg.LegendItem(offset=(370, 1))
        self.legend.setParentItem(self.graph.plotItem)
        self.legend.addItem(self.totalCurve, 'Cumulative')
        self.graph.setBackground(None)
        self.graph.setMaximumWidth(550)
        self.graph.setMaximumHeight(300)
        self.verticalLayout_2.addWidget(self.graph)
        self.level_slider.setMaximum(100)
        self.level_slider.valueChanged.connect(self.setIntensity)
        self.pause_pushButton.setStyleSheet(
            "color: white; background-color: darkBlue")
        self.stop_pushButton.setStyleSheet(
            "color: white; background-color: darkRed")
        self.pause_pushButton.clicked.connect(self.pauseView)
        self.stop_pushButton.clicked.connect(self.terminate)
        self.dark_checkBox.stateChanged.connect(self.setBackground)
        self.verbose_checkBox.stateChanged.connect(self.showVerbose)
        self.rali_checkBox.stateChanged.connect(self.showRALI)
        self.dark_checkBox.setChecked(True)
        self.graph_imageLabel.setPixmap(self.graph_image_pixmap)
        if self.container_index == 1:
            self.container_logo.setPixmap(self.docker_pixmap)
        elif self.container_index == 2:
            self.container_logo.setPixmap(self.singularity_pixmap)
        else:
            self.container_logo.hide()
        for augmentation in range(self.batch_size_int):
            self.augAccuracy.append([0])

        self.showVerbose()
        self.showRALI()

    def initEngines(self):
        self.receiver_thread = QThread()
        # Creating an object for inference.
        self.inferenceEngine = modelInference(
            self.model_name, self.model_format, self.image_dir,
            self.model_location, self.label, self.hierarchy, self.image_val,
            self.input_dims, self.output_dims, self.batch_size,
            self.output_dir, self.add, self.multiply, self.verbose, self.fp16,
            self.replace, self.loop, self.rali_mode, self.origImageQueue,
            self.augImageQueue, self.gui, self.total_images, self.fps_file)

        self.inferenceEngine.moveToThread(self.receiver_thread)
        self.receiver_thread.started.connect(self.inferenceEngine.runInference)
        self.receiver_thread.finished.connect(self.inferenceEngine.deleteLater)
        self.receiver_thread.start()
        self.receiver_thread.terminate()

    def paintEvent(self, event):
        self.showAugImage()
        self.showImage()
        self.displayFPS()
        if self.imgCount == self.total_images:
            if self.loop == 'yes':
                self.resetViewer()
            else:
                self.terminate()

    def resetViewer(self):
        self.imgCount = 0
        del self.x[:]
        self.x.append(0)
        del self.y[:]
        self.y.append(0)
        del self.augAccuracy[:]
        for augmentation in range(self.batch_size_int):
            self.augAccuracy.append([0])

        self.lastTime = 0
        self.elapsedTime = QTime.currentTime()
        self.totalElapsedTime = 0.0
        self.progIndex = 0
        self.showAug = False
        self.lastIndex = self.frameCount - 1
        self.totalCurve.clear()
        self.augCurve.clear()
        self.name_label.setText("Model: %s" % (self.model_name))
        self.legend.removeItem(self.lastAugName)

    def setProgressBar(self):
        if self.showAug:
            self.setAugProgress(self.progIndex)
        else:
            self.setTotalProgress()

    def setTotalProgress(self):
        totalStats = self.inferenceEngine.getTotalStats()
        top1 = totalStats[0]
        top5 = totalStats[1]
        mis = totalStats[2]
        totalCount = top5 + mis
        self.totalAccuracy = (float)(top5) / (totalCount + 1) * 100
        self.total_progressBar.setValue(totalCount)
        self.total_progressBar.setMaximum(self.total_images *
                                          self.batch_size_int)
        self.imgProg_label.setText(
            "Processed: %d of %d" %
            (totalCount, self.total_images * self.batch_size_int))
        self.top1_progressBar.setValue(top1)
        self.top1_progressBar.setMaximum(totalCount)
        self.top5_progressBar.setValue(top5)
        self.top5_progressBar.setMaximum(totalCount)
        self.mis_progressBar.setValue(mis)
        self.mis_progressBar.setMaximum(totalCount)

    def setAugProgress(self, augmentation):
        augStats = self.inferenceEngine.getAugStats(augmentation)
        top1 = augStats[0]
        top5 = augStats[1]
        mis = augStats[2]
        totalCount = top5 + mis
        self.total_progressBar.setValue(totalCount)
        self.total_progressBar.setMaximum(self.total_images)
        self.imgProg_label.setText("Processed: %d of %d" %
                                   (totalCount, self.total_images))
        self.top1_progressBar.setValue(top1)
        self.top1_progressBar.setMaximum(totalCount)
        self.top5_progressBar.setValue(top5)
        self.top5_progressBar.setMaximum(totalCount)
        self.mis_progressBar.setValue(mis)
        self.mis_progressBar.setMaximum(totalCount)

    def plotGraph(self):
        if not self.pauseState:
            curTime = self.elapsedTime.elapsed() / 1000.0
            if (curTime - self.lastTime > 0.01):
                self.x.append(curTime + self.totalElapsedTime)
                self.y.append(self.totalAccuracy)
                self.totalCurve.setData(x=self.x, y=self.y, pen=self.pen)
                for augmentation in range(self.batch_size_int):
                    augStats = self.inferenceEngine.getAugStats(augmentation)
                    top5 = augStats[1]
                    mis = augStats[2]
                    totalCount = top5 + mis
                    totalAccuracy = (float)(top5) / (totalCount + 1) * 100
                    self.augAccuracy[augmentation].append(totalAccuracy)

                if self.showAug:
                    self.augCurve.setData(x=self.x,
                                          y=self.augAccuracy[self.progIndex],
                                          pen=pg.mkPen('b', width=4))

            self.lastTime = curTime

    def showImage(self):
        if not self.origImageQueue.empty():
            origImage = self.origImageQueue.get()
            origWidth = origImage.shape[1]
            origHeight = origImage.shape[0]
            qOrigImage = QtGui.QImage(origImage, origWidth, origHeight,
                                      origWidth * 3,
                                      QtGui.QImage.Format_RGB888)
            qOrigImageResized = qOrigImage.scaled(self.image_label.width(),
                                                  self.image_label.height(),
                                                  QtCore.Qt.IgnoreAspectRatio)
            index = self.imgCount % self.frameCount
            self.origImage_layout.itemAt(index).widget().setPixmap(
                QtGui.QPixmap.fromImage(qOrigImageResized))
            self.origImage_layout.itemAt(index).widget().setStyleSheet(
                "border: 5px solid yellow;")
            self.origImage_layout.itemAt(
                self.lastIndex).widget().setStyleSheet("border: 0")
            self.imgCount += 1
            self.lastIndex = index

    def showAugImage(self):
        if not self.augImageQueue.empty():
            augImage = self.augImageQueue.get()
            augWidth = augImage.shape[1]
            augHeight = augImage.shape[0]
            qAugImage = QtGui.QImage(augImage, augWidth, augHeight,
                                     augWidth * 3, QtGui.QImage.Format_RGB888)
            if self.batch_size_int == 64:
                qAugImageResized = qAugImage.scaled(
                    self.aug_label.width(), self.aug_label.height(),
                    QtCore.Qt.IgnoreAspectRatio)
            else:
                qAugImageResized = qAugImage.scaled(self.aug_label.width(),
                                                    self.aug_label.height(),
                                                    QtCore.Qt.KeepAspectRatio)
            self.aug_label.setPixmap(QtGui.QPixmap.fromImage(qAugImageResized))

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.terminate()

        if event.key() == QtCore.Qt.Key_Space:
            self.pauseView()

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            mousePos = event.pos()
            if self.aug_label.geometry().contains(mousePos):
                index = self.calculateIndex(mousePos.x(), mousePos.y())
                self.progIndex = index
                self.showAug = True
                augName = self.inferenceEngine.getAugName(index)
                self.name_label.setText(augName)
                self.augCurve.setData(x=self.x,
                                      y=self.augAccuracy[self.progIndex],
                                      pen=pg.mkPen('b', width=4))
                self.legend.removeItem(self.lastAugName)
                self.legend.addItem(self.augCurve, augName)
                self.lastAugName = augName
            else:
                self.showAug = False
                self.name_label.setText("Model: %s" % (self.model_name))
                self.augCurve.clear()
                self.legend.removeItem(self.lastAugName)
                self.legend.removeItem('Cumulative')
                self.legend.addItem(self.totalCurve, 'Cumulative')
            if not self.pauseState:
                self.totalCurve.clear()
                self.augCurve.clear()

    def setBackground(self):
        if self.dark_checkBox.isChecked():
            self.setStyleSheet("background-color: #25232F;")
            self.pen = pg.mkPen('w', width=4)
            self.graph.setBackground(None)
            self.origTitle_label.setStyleSheet("color: #C82327;")
            self.controlTitle_label.setStyleSheet("color: #C82327;")
            self.progTitle_label.setStyleSheet("color: #C82327;")
            self.graphTitle_label.setStyleSheet("color: #C82327;")
            self.augTitle_label.setStyleSheet("color: #C82327;")
            self.name_label.setStyleSheet("color: white;")
            self.dataset_label.setStyleSheet("color: white;")
            self.imgProg_label.setStyleSheet("color: white;")
            self.fps_label.setStyleSheet("color: #C82327;")
            self.dark_checkBox.setStyleSheet("color: white;")
            self.verbose_checkBox.setStyleSheet("color: white;")
            self.rali_checkBox.setStyleSheet("color: white;")
            self.level_label.setStyleSheet("color: white;")
            self.low_label.setStyleSheet("color: white;")
            self.high_label.setStyleSheet("color: white;")
            self.cpu_label.setStyleSheet("color: #C82327;")
            self.gpu_label.setStyleSheet("color: #C82327;")
            self.cpuName_label.setStyleSheet("color: white;")
            self.gpuName_label.setStyleSheet("color: white;")
            self.AMD_logo.setPixmap(self.AMD_Radeon_white_pixmap)
            if self.rali_checkBox.isChecked():
                self.MIVisionX_logo.setPixmap(self.rali_white_pixmap)
                self.graph_imageLabel.setPixmap(self.graph_image_white_pixmap)
            else:
                self.MIVisionX_logo.setPixmap(self.MIVisionX_white_pixmap)
            self.EPYC_logo.setPixmap(self.EPYC_white_pixmap)
            self.totalCurve.setData(x=self.x, y=self.y, pen=self.pen)
        else:
            self.setStyleSheet("background-color: white;")
            self.pen = pg.mkPen('k', width=4)
            self.graph.setBackground(None)
            self.origTitle_label.setStyleSheet("color: 0;")
            self.controlTitle_label.setStyleSheet("color: 0;")
            self.progTitle_label.setStyleSheet("color: 0;")
            self.graphTitle_label.setStyleSheet("color: 0;")
            self.augTitle_label.setStyleSheet("color: 0;")
            self.name_label.setStyleSheet("color: 0;")
            self.dataset_label.setStyleSheet("color: 0;")
            self.imgProg_label.setStyleSheet("color: 0;")
            self.fps_label.setStyleSheet("color: 0;")
            self.dark_checkBox.setStyleSheet("color: 0;")
            self.verbose_checkBox.setStyleSheet("color: 0;")
            self.rali_checkBox.setStyleSheet("color: 0;")
            self.level_label.setStyleSheet("color: 0;")
            self.low_label.setStyleSheet("color: 0;")
            self.high_label.setStyleSheet("color: 0;")
            self.cpu_label.setStyleSheet("color: 0;")
            self.gpu_label.setStyleSheet("color: 0;")
            self.cpuName_label.setStyleSheet("color: 0;")
            self.gpuName_label.setStyleSheet("color: 0;")
            self.AMD_logo.setPixmap(self.AMD_Radeon_pixmap)
            if self.rali_checkBox.isChecked():
                self.MIVisionX_logo.setPixmap(self.rali_pixmap)
                self.graph_imageLabel.setPixmap(self.graph_image_pixmap)
            else:
                self.MIVisionX_logo.setPixmap(self.MIVisionX_pixmap)
            self.EPYC_logo.setPixmap(self.EPYC_pixmap)
            self.totalCurve.setData(x=self.x, y=self.y, pen=self.pen)

    def showVerbose(self):
        if self.verbose_checkBox.isChecked():
            self.dataset_label.show()
            self.fps_label.show()
            self.fps_lcdNumber.show()
            self.legend.show()
            self.cpu_label.show()
            self.gpu_label.show()
            self.cpuName_label.show()
            self.gpuName_label.show()
        else:
            self.dataset_label.hide()
            self.fps_label.hide()
            self.fps_lcdNumber.hide()
            self.legend.hide()
            self.cpu_label.hide()
            self.gpu_label.hide()
            self.gpuName_label.hide()
            self.cpuName_label.hide()

    def showRALI(self):
        if self.rali_checkBox.isChecked():
            if self.dark_checkBox.isChecked():
                self.MIVisionX_logo.setPixmap(self.rali_white_pixmap)
                self.graph_imageLabel.setPixmap(self.graph_image_white_pixmap)
            else:
                self.MIVisionX_logo.setPixmap(self.rali_pixmap)
                self.graph_imageLabel.setPixmap(self.graph_image_pixmap)
            self.graph_imageLabel.show()
        else:
            if self.dark_checkBox.isChecked():
                self.MIVisionX_logo.setPixmap(self.MIVisionX_white_pixmap)
            else:
                self.MIVisionX_logo.setPixmap(self.MIVisionX_pixmap)
            self.graph_imageLabel.hide()

    def displayFPS(self):
        self.fps_lcdNumber.display(self.inferenceEngine.getFPS())

    def pauseView(self):
        self.pauseState = not self.pauseState
        if self.pauseState:
            self.totalElapsedTime += self.elapsedTime.elapsed() / 1000.0
            self.pause_pushButton.setText('Resume')
        else:
            self.elapsedTime.restart()
            self.pause_pushButton.setText('Pause')

        self.inferenceEngine.pauseInference()

    def terminate(self):
        self.inferenceEngine.terminate()
        self.receiver_thread.quit()
        for count in range(10):
            QThread.msleep(50)

        self.close()

    def closeEvent(self, event):
        self.terminate()
        exit(0)

    def setIntensity(self):
        augIntensity = (float)(self.level_slider.value()) / 100.0
        self.inferenceEngine.setIntensity(augIntensity)

    def calculateIndex(self, x, y):
        if self.batch_size_int == 64:
            imgWidth = self.aug_label.width() / 16.0
        else:
            imgWidth = self.aug_label.width() / 4.0
        imgHeight = self.aug_label.height() / 4.0
        x -= self.aug_label.x()
        y -= self.aug_label.y()
        column = (int)(x / imgWidth)
        row = (int)(y / imgHeight)
        index = 4 * column + row
        return index
class Thumbnailer(QObject):
    """
    Extracts, caches and retrieves thumbnails for a set of files.

    For each set of files, a process runs to extract the files from
    their source. Each file is then processed, if necessary using
    worker processes fronted by a load balancer.
    """

    frontend_port = pyqtSignal(int)

    # See also the four other signals below

    def __init__(self, parent, no_workers: int, logging_port: int,
                 log_gphoto2: bool) -> None:
        """
        :param parent: Qt parent window
        :param no_workers: how many thumbnail extractor processes to
         use
        :param logging_port: 0MQ port to use for logging control
        :param log_gphoto2: if True, log libgphoto2 logging message
        """
        super().__init__(parent)
        self.context = zmq.Context.instance()
        self.log_gphoto2 = log_gphoto2
        self._frontend_port = None  # type: int
        self.no_workers = no_workers
        self.logging_port = logging_port

        inproc = "inproc://{}"
        self.thumbnailer_controller = self.context.socket(zmq.PAIR)
        self.thumbnailer_controller.bind(inproc.format(
            ThreadNames.thumbnailer))
        self.load_balancer_controller = self.context.socket(zmq.PAIR)
        self.load_balancer_controller.bind(
            inproc.format(ThreadNames.load_balancer))

        self.setupThumbnailManager()

    def generateThumbnails(
            self,
            scan_id: int,
            rpd_files: list,
            name: str,
            proximity_seconds: int,
            cache_dirs: CacheDirs,
            need_photo_cache_dir: bool,
            need_video_cache_dir: bool,
            camera_model: Optional[str] == None,
            camera_port: Optional[str] = None,
            entire_video_required: Optional[bool] = None,
            entire_photo_required: Optional[bool] = None) -> None:
        """
        Initiates thumbnail generation.

        :param scan_id: worker id of the scan
        :param rpd_files: list of rpd_files, all of which should be
         from the same source
        :param name: name of the device
        :param proximity_seconds: the time elapsed between consecutive
         shots that is used to prioritize the order of thumbnail
         generation
        :param cache_dirs: the location where the cache directories
         should be created
        :param need_photo_cache_dir: if True, must use cache dir
         to extract photo thumbnail
        :param need_video_cache_dir: if True, must use cache dir
         to extract video thumbnail
        :param camera_model: If the thumbnails are being downloaded
         from a camera, this is the name of the camera, else None
        :param camera_port: If the thumbnails are being downloaded
         from a camera, this is the port of the camera, else None,
        :param entire_video_required: if the entire video is required
         to extract the thumbnail
        :param entire_photo_required: if the entire photo is required
         to extract the thumbnail
         """
        self.thumbnailer_controller.send_multipart(
            create_inproc_msg(
                b'START_WORKER',
                worker_id=scan_id,
                data=GenerateThumbnailsArguments(
                    scan_id=scan_id,
                    rpd_files=rpd_files,
                    name=name,
                    proximity_seconds=proximity_seconds,
                    cache_dirs=cache_dirs,
                    need_photo_cache_dir=need_photo_cache_dir,
                    need_video_cache_dir=need_video_cache_dir,
                    frontend_port=self._frontend_port,
                    log_gphoto2=self.log_gphoto2,
                    camera=camera_model,
                    port=camera_port,
                    entire_video_required=entire_video_required,
                    entire_photo_required=entire_photo_required)))

    @property
    def thumbnailReceived(self) -> pyqtBoundSignal:
        return self.thumbnail_manager.message

    @property
    def cacheDirs(self) -> pyqtBoundSignal:
        return self.thumbnail_manager.cacheDirs

    # Signal emitted when the worker has been forcefully stopped, rather than
    # merely finished in its work
    @property
    def workerStopped(self) -> pyqtSignal:
        return self.thumbnail_manager.workerStopped

    @property
    def workerFinished(self) -> pyqtSignal:
        return self.thumbnail_manager.workerFinished

    @property
    def cameraRemoved(self) -> pyqtSignal:
        return self.thumbnail_manager.cameraRemoved

    def setupThumbnailManager(self) -> None:
        logging.debug("Starting thumbnail model...")

        self.thumbnail_manager_thread = QThread()
        self.thumbnail_manager = ThumbnailManagerPara(
            logging_port=self.logging_port,
            thread_name=ThreadNames.thumbnailer)
        self.thumbnail_manager.moveToThread(self.thumbnail_manager_thread)
        self.thumbnail_manager_thread.started.connect(
            self.thumbnail_manager.run_sink)
        self.thumbnail_manager.receiverPortSignal.connect(
            self.managerReceiverPort)
        self.thumbnail_manager.sinkStarted.connect(
            self.thumbnailManagerSinkStarted)

        QTimer.singleShot(0, self.thumbnail_manager_thread.start)

    @pyqtSlot(int)
    def managerReceiverPort(self, port: int) -> None:
        self.thumbnail_manager_sink_port = port

    @pyqtSlot()
    def thumbnailManagerSinkStarted(self) -> None:
        logging.debug("...thumbnail model started")

        self.setupLoadBalancer()

    def setupLoadBalancer(self) -> None:
        logging.debug("Starting thumbnail load balancer...")
        self.load_balancer_thread = QThread()
        self.load_balancer = ThumbnailLoadBalancerManager(
            self.context, self.no_workers, self.thumbnail_manager_sink_port,
            self.logging_port)
        self.load_balancer.moveToThread(self.load_balancer_thread)
        self.load_balancer_thread.started.connect(
            self.load_balancer.start_load_balancer)

        self.load_balancer.load_balancer_started.connect(
            self.loadBalancerFrontendPort)
        QTimer.singleShot(0, self.load_balancer_thread.start)

    @pyqtSlot(int)
    def loadBalancerFrontendPort(self, frontend_port: int) -> None:
        logging.debug("...thumbnail load balancer started")
        self._frontend_port = frontend_port
        self.frontend_port.emit(frontend_port)

    def stop(self) -> None:

        self.thumbnailer_controller.send_multipart(create_inproc_msg(b'STOP'))
        self.load_balancer_controller.send_multipart(
            create_inproc_msg(b'STOP'))
        self.thumbnail_manager_thread.quit()
        if not self.thumbnail_manager_thread.wait(1000):
            self.thumbnailer_controller.send_multipart(
                create_inproc_msg(b'TERMINATE'))
        self.load_balancer_thread.quit()
        if not self.load_balancer_thread.wait(1000):
            self.load_balancer_controller.send_multipart(
                create_inproc_msg(b'TERMINATE'))

    def stop_worker(self, scan_id: int) -> None:
        self.thumbnailer_controller.send_multipart(
            create_inproc_msg(b'STOP_WORKER', worker_id=scan_id))
Esempio n. 44
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)
        def on_haveNewVersion(version, changeLog):
            if askUser(f'有新版本:{version}是否更新?\n\n{changeLog.strip()}'):
                openLink(RELEASE_URL)

        self.updateCheckWork = VersionCheckWorker()
        self.updateCheckWork.moveToThread(self.updateCheckThead)
        self.updateCheckWork.haveNewVersion.connect(on_haveNewVersion)
        self.updateCheckWork.finished.connect(self.updateCheckThead.quit)
        self.updateCheckWork.start.connect(self.updateCheckWork.run)
        self.updateCheckWork.start.emit()

    @pyqtSlot(int)
    def on_dictionaryComboBox_currentIndexChanged(self, index):
        """词典候选框改变事件"""
        self.currentDictionaryLabel.setText(
            f'当前选择词典: {self.dictionaryComboBox.currentText()}')
        config = mw.addonManager.getConfig(__name__)
        self.cookieLineEdit.setText(config['credential'][index]['cookie'])

    @pyqtSlot()
    def on_pullRemoteWordsBtn_clicked(self):
        """获取单词按钮点击事件"""
        if not self.deckComboBox.currentText():
            showInfo('\n请选择或输入要同步的牌组')
            return

        self.mainTab.setEnabled(False)
        self.progressBar.setValue(0)
        self.progressBar.setMaximum(0)

        currentConfig = self.getAndSaveCurrentConfig()
        self.selectedDict = dictionaries[currentConfig['selectedDict']]()

        # 登陆线程
        self.loginWorker = LoginStateCheckWorker(
            self.selectedDict.checkCookie,
            json.loads(self.cookieLineEdit.text() or '{}'))
        self.loginWorker.moveToThread(self.workerThread)
        self.loginWorker.start.connect(self.loginWorker.run)
        self.loginWorker.logSuccess.connect(self.onLogSuccess)
        self.loginWorker.logFailed.connect(self.onLoginFailed)
        self.loginWorker.start.emit()

    @pyqtSlot()
    def onLoginFailed(self):
        showCritical('第一次登录或cookie失效!请重新登录')
        self.progressBar.setValue(0)
        self.progressBar.setMaximum(1)
        self.mainTab.setEnabled(True)
        self.cookieLineEdit.clear()
        self.loginDialog = LoginDialog(
            loginUrl=self.selectedDict.loginUrl,
            loginCheckCallbackFn=self.selectedDict.loginCheckCallbackFn,
            parent=self)
        self.loginDialog.loginSucceed.connect(self.onLogSuccess)
        self.loginDialog.show()

    @pyqtSlot(str)
    def onLogSuccess(self, cookie):
        self.cookieLineEdit.setText(cookie)
        self.getAndSaveCurrentConfig()
        self.selectedDict.checkCookie(json.loads(cookie))
        self.selectedDict.getGroups()

        container = QDialog(self)
        group = wordGroup.Ui_Dialog()
        group.setupUi(container)

        for groupName in [
                str(group_name) for group_name, _ in self.selectedDict.groups
        ]:
            item = QListWidgetItem()
            item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                          | Qt.ItemIsEnabled)
            item.setText(groupName)
            item.setCheckState(Qt.Unchecked)
            group.wordGroupListWidget.addItem(item)

        # 恢复上次选择的单词本分组
        if self.selectedGroups:
            for groupName in self.selectedGroups[
                    self.currentConfig['selectedDict']]:
                items = group.wordGroupListWidget.findItems(
                    groupName, Qt.MatchExactly)
                for item in items:
                    item.setCheckState(Qt.Checked)
        else:
            self.selectedGroups = [list()] * len(dictionaries)

        def onAccepted():
            """选择单词本弹窗确定事件"""
            # 清空 listWidget
            self.newWordListWidget.clear()
            self.needDeleteWordListWidget.clear()
            self.mainTab.setEnabled(False)

            selectedGroups = [
                group.wordGroupListWidget.item(index).text()
                for index in range(group.wordGroupListWidget.count())
                if group.wordGroupListWidget.item(index).checkState() ==
                Qt.Checked
            ]
            # 保存分组记录
            self.selectedGroups[
                self.currentConfig['selectedDict']] = selectedGroups
            self.progressBar.setValue(0)
            self.progressBar.setMaximum(1)
            logger.info(f'选中单词本{selectedGroups}')
            self.getRemoteWordList(selectedGroups)

        def onRejected():
            """选择单词本弹窗取消事件"""
            self.progressBar.setValue(0)
            self.progressBar.setMaximum(1)
            self.mainTab.setEnabled(True)

        group.buttonBox.accepted.connect(onAccepted)
        group.buttonBox.rejected.connect(onRejected)
        container.exec()

    def getRemoteWordList(self, selected_groups: [str]):
        """根据选中到分组获取分组下到全部单词,并添加到 newWordListWidget"""
        group_map = dict(self.selectedDict.groups)
        self.localWords = getWordsByDeck(self.deckComboBox.currentText())

        # 启动单词获取线程
        self.pullWorker = RemoteWordFetchingWorker(self.selectedDict, [(
            group_name,
            group_map[group_name],
        ) for group_name in selected_groups])
        self.pullWorker.moveToThread(self.workerThread)
        self.pullWorker.start.connect(self.pullWorker.run)
        self.pullWorker.tick.connect(
            lambda: self.progressBar.setValue(self.progressBar.value() + 1))
        self.pullWorker.setProgress.connect(self.progressBar.setMaximum)
        self.pullWorker.doneThisGroup.connect(self.insertWordToListWidget)
        self.pullWorker.done.connect(self.on_allPullWork_done)
        self.pullWorker.start.emit()

    @pyqtSlot(list)
    def insertWordToListWidget(self, words: list):
        """一个分组获取完毕事件"""
        for word in words:
            wordItem = QListWidgetItem(word, self.newWordListWidget)
            wordItem.setData(Qt.UserRole, None)
        self.newWordListWidget.clearSelection()

    @pyqtSlot()
    def on_allPullWork_done(self):
        """全部分组获取完毕事件"""
        localWordList = set(getWordsByDeck(self.deckComboBox.currentText()))
        remoteWordList = set([
            self.newWordListWidget.item(row).text()
            for row in range(self.newWordListWidget.count())
        ])

        newWords = remoteWordList - localWordList  # 新单词
        needToDeleteWords = localWordList - remoteWordList  # 需要删除的单词
        logger.info(f'本地: {localWordList}')
        logger.info(f'远程: {remoteWordList}')
        logger.info(f'待查: {newWords}')
        logger.info(f'待删: {needToDeleteWords}')
        waitIcon = QIcon(':/icons/wait.png')
        delIcon = QIcon(':/icons/delete.png')
        self.newWordListWidget.clear()
        self.needDeleteWordListWidget.clear()

        for word in needToDeleteWords:
            item = QListWidgetItem(word)
            item.setCheckState(Qt.Checked)
            item.setIcon(delIcon)
            self.needDeleteWordListWidget.addItem(item)

        for word in newWords:
            item = QListWidgetItem(word)
            item.setIcon(waitIcon)
            self.newWordListWidget.addItem(item)
        self.newWordListWidget.clearSelection()

        self.dictionaryComboBox.setEnabled(True)
        self.apiComboBox.setEnabled(True)
        self.deckComboBox.setEnabled(True)
        self.pullRemoteWordsBtn.setEnabled(True)
        self.queryBtn.setEnabled(self.newWordListWidget.count() > 0)
        self.syncBtn.setEnabled(self.newWordListWidget.count() == 0
                                and self.needDeleteWordListWidget.count() > 0)
        if self.needDeleteWordListWidget.count(
        ) == self.newWordListWidget.count() == 0:
            logger.info('无需同步')
            tooltip('无需同步')
        self.mainTab.setEnabled(True)

    @pyqtSlot()
    def on_queryBtn_clicked(self):
        logger.info('点击查询按钮')
        currentConfig = self.getAndSaveCurrentConfig()
        self.queryBtn.setEnabled(False)
        self.pullRemoteWordsBtn.setEnabled(False)
        self.syncBtn.setEnabled(False)

        wordList = []
        selectedWords = self.newWordListWidget.selectedItems()
        if selectedWords:
            # 如果选中单词则只查询选中的单词
            for wordItem in selectedWords:
                wordBundle = dict()
                row = self.newWordListWidget.row(wordItem)
                wordBundle['term'] = wordItem.text()
                for configName in BASIC_OPTION + EXTRA_OPTION:
                    wordBundle[configName] = currentConfig[configName]
                    wordBundle['row'] = row
                wordList.append(wordBundle)
        else:  # 没有选择则查询全部
            for row in range(self.newWordListWidget.count()):
                wordBundle = dict()
                wordItem = self.newWordListWidget.item(row)
                wordBundle['term'] = wordItem.text()
                for configName in BASIC_OPTION + EXTRA_OPTION:
                    wordBundle[configName] = currentConfig[configName]
                    wordBundle['row'] = row
                wordList.append(wordBundle)

        logger.info(f'待查询单词{wordList}')
        # 查询线程
        self.progressBar.setMaximum(len(wordList))
        self.queryWorker = QueryWorker(wordList,
                                       apis[currentConfig['selectedApi']])
        self.queryWorker.moveToThread(self.workerThread)
        self.queryWorker.thisRowDone.connect(self.on_thisRowDone)
        self.queryWorker.thisRowFailed.connect(self.on_thisRowFailed)
        self.queryWorker.tick.connect(
            lambda: self.progressBar.setValue(self.progressBar.value() + 1))
        self.queryWorker.allQueryDone.connect(self.on_allQueryDone)
        self.queryWorker.start.connect(self.queryWorker.run)
        self.queryWorker.start.emit()

    @pyqtSlot(int, dict)
    def on_thisRowDone(self, row, result):
        """该行单词查询完毕"""
        doneIcon = QIcon(':/icons/done.png')
        wordItem = self.newWordListWidget.item(row)
        wordItem.setIcon(doneIcon)
        wordItem.setData(Qt.UserRole, result)

    @pyqtSlot(int)
    def on_thisRowFailed(self, row):
        failedIcon = QIcon(':/icons/failed.png')
        failedWordItem = self.newWordListWidget.item(row)
        failedWordItem.setIcon(failedIcon)

    @pyqtSlot()
    def on_allQueryDone(self):
        failed = []

        for i in range(self.newWordListWidget.count()):
            wordItem = self.newWordListWidget.item(i)
            if not wordItem.data(Qt.UserRole):
                failed.append(wordItem.text())

        if failed:
            logger.warning(f'查询失败或未查询:{failed}')

        self.pullRemoteWordsBtn.setEnabled(True)
        self.queryBtn.setEnabled(True)
        self.syncBtn.setEnabled(True)

    @pyqtSlot()
    def on_syncBtn_clicked(self):

        failedGenerator = (self.newWordListWidget.item(row).data(Qt.UserRole)
                           is None
                           for row in range(self.newWordListWidget.count()))
        if any(failedGenerator):
            if not askUser(
                    '存在未查询或失败的单词,确定要加入单词本吗?\n 你可以选择失败的单词点击 "查询按钮" 来重试。'):
                return

        currentConfig = self.getAndSaveCurrentConfig()
        model = getOrCreateModel(MODEL_NAME)
        getOrCreateModelCardTemplate(model, 'default')
        deck = getOrCreateDeck(self.deckComboBox.currentText())

        logger.info('同步点击')
        audiosDownloadTasks = []
        newWordCount = self.newWordListWidget.count()

        # 判断是否需要下载发音
        if currentConfig['noPron']:
            logger.info('不下载发音')
            whichPron = None
        else:
            whichPron = 'AmEPron' if self.AmEPronRadioButton.isChecked(
            ) else 'BrEPron'
            logger.info(f'下载发音{whichPron}')

        added = 0
        for row in range(newWordCount):
            wordItem = self.newWordListWidget.item(row)
            wordItemData = wordItem.data(Qt.UserRole)
            if wordItemData:
                addNoteToDeck(deck, model, currentConfig, wordItemData)
                added += 1
                # 添加发音任务
                if whichPron and wordItemData.get(whichPron):
                    audiosDownloadTasks.append((
                        f"{whichPron}_{wordItemData['term']}.mp3",
                        wordItemData[whichPron],
                    ))
        mw.reset()

        logger.info(f'发音下载任务:{audiosDownloadTasks}')

        if audiosDownloadTasks:
            self.syncBtn.setEnabled(False)
            self.progressBar.setValue(0)
            self.progressBar.setMaximum(len(audiosDownloadTasks))
            if self.audioDownloadThread is not None:
                self.audioDownloadThread.requestInterruption()
                self.audioDownloadThread.quit()
                self.audioDownloadThread.wait()

            self.audioDownloadThread = QThread(self)
            self.audioDownloadThread.start()
            self.audioDownloadWorker = AudioDownloadWorker(audiosDownloadTasks)
            self.audioDownloadWorker.moveToThread(self.audioDownloadThread)
            self.audioDownloadWorker.tick.connect(
                lambda: self.progressBar.setValue(self.progressBar.value() + 1
                                                  ))
            self.audioDownloadWorker.start.connect(
                self.audioDownloadWorker.run)
            self.audioDownloadWorker.done.connect(lambda: tooltip(f'发音下载完成'))
            self.audioDownloadWorker.done.connect(
                self.audioDownloadThread.quit)
            self.audioDownloadWorker.start.emit()

        self.newWordListWidget.clear()

        needToDeleteWordItems = [
            self.needDeleteWordListWidget.item(row)
            for row in range(self.needDeleteWordListWidget.count()) if
            self.needDeleteWordListWidget.item(row).checkState() == Qt.Checked
        ]
        needToDeleteWords = [i.text() for i in needToDeleteWordItems]

        deleted = 0

        if needToDeleteWords and askUser(
                f'确定要删除这些单词吗:{needToDeleteWords[:3]}...({len(needToDeleteWords)}个)',
                title='Dict2Anki',
                parent=self):
            needToDeleteWordNoteIds = getNotes(needToDeleteWords,
                                               currentConfig['deck'])
            mw.col.remNotes(needToDeleteWordNoteIds)
            deleted += 1
            mw.col.reset()
            mw.reset()
            for item in needToDeleteWordItems:
                self.needDeleteWordListWidget.takeItem(
                    self.needDeleteWordListWidget.row(item))
            logger.info('删除完成')
        logger.info('完成')

        if not audiosDownloadTasks:
            tooltip(f'添加{added}个笔记\n删除{deleted}个笔记')
class Window(QMainWindow, Ui_MainWindow1):
    final_clip = None
    video_backend = None
    cutter_end = 0
    cutter_start = 0
    withaudio = True

    def __init__(self):
        super(Window, self).__init__()

        self.setupUi(self)
        self.mediaPlayer = QMediaPlayer()
        self.pushButton.clicked.connect(self.openFile)

        self.pushButton_3.clicked.connect(self.process)
        self.pushButton_3.setEnabled(False)
        self.pushButton_2.setEnabled(False)

        self.pushButton_2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.pushButton_2.clicked.connect(self.play)

        self.horizontalSlider.setRange(0,0)
        self.horizontalSlider.setStatusTip("slider")
        self.horizontalSlider.sliderMoved.connect(self.setPosition)

        self.label = QLabel()
        self.label.setSizePolicy(QSizePolicy.Preferred,
                                      QSizePolicy.Fixed)

        self.progressBar = QProgressBar()
        self.progressBar.setVisible(False)
        self.progressBar.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Fixed)
        self.statusbar.addPermanentWidget(self.label)
        self.statusbar.addPermanentWidget(self.progressBar)

        self.mediaPlayer.setVideoOutput(self.widget)
        self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
        self.mediaPlayer.positionChanged.connect(self.positionChanged)
        self.mediaPlayer.durationChanged.connect(self.durationChanged)
        self.mediaPlayer.error.connect(self.handleError)


    def changeChannel(self):
        Window.withaudio = not Window.withaudio

        self.pushButton_5.setText("有声") if Window.withaudio else self.pushButton_5.setText("无声")

    def setStart(self):
        Window.cutter_start = self.mediaPlayer.position()/1000


    def process(self):

        Window.cutter_end = self.mediaPlayer.position()/1000
        self.progressBar.setVisible(True)
        self.threadObject = ProcessObject()

        self.subThread = QThread()
        self.threadObject.moveToThread(self.subThread)
        self.subThread.started.connect(self.threadObject.process_work)
        self.threadObject.finish_process.connect(self.clearThread)
        self.threadObject.message.connect(self.thread_message)
        self.threadObject.progress.connect(self.thread_progress)

        self.subThread.start()
        if self.subThread.isRunning():
            self.pushButton_3.setEnabled(False)


    def thread_message(self, value):
        self.statusbar.showMessage(value)

    def thread_progress(self, value):
        self.progressBar.setValue(value)

    def clearThread(self):
        self.progressBar.setVisible(False)
        self.pushButton_3.setEnabled(True)
        self.subThread.quit()


    def openFile(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie",
                QDir.homePath(),"Video(*.mp4;*.wmv;*.rmvb;*.avi);;Audio(*.mp3;*.wav)")

        if fileName != '':

            self.mediaPlayer.setMedia(
                    QMediaContent(QUrl.fromLocalFile(fileName)))
            self.pushButton_2.setEnabled(True)
            Window.video_backend = VideoFileClip(fileName)
            self.pushButton_3.setEnabled(True)
            self.pushButton_5.setEnabled(True)
            self.pushButton_4.setEnabled(True)



    def play(self):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.mediaPlayer.pause()
        else:
            self.mediaPlayer.play()

    def mediaStateChanged(self, state):
        if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
            self.pushButton_2.setIcon(
                    self.style().standardIcon(QStyle.SP_MediaPause))
        else:
            self.pushButton_2.setIcon(
                    self.style().standardIcon(QStyle.SP_MediaPlay))

    def positionChanged(self, position):
        self.horizontalSlider.setValue(position)
        print(self.horizontalSlider.pageStep())
        print(self.horizontalSlider.singleStep())
    def durationChanged(self, duration):

        self.horizontalSlider.setRange(0, duration)


    def setPosition(self, position):

        self.mediaPlayer.setPosition(position)

    def handleError(self):
        self.pushButton.setEnabled(False)
        self.label.setText("Error: %s" % self.mediaPlayer.errorString())
class Cam_ui(QtWidgets.QMainWindow, Ui_MainWindow):  #程序界面主线程
    def __init__(self, parent=None):  #初始化函数(每一次程序运行时,一定会执行的函数)
        super().__init__(parent)

        self.setupUi(self)

        #初始化槽函数
        self.slot_init()

        #对config文件进行定义
        global ip_red, ip_yellow, ip_blue, ip_green, speed  #对全局变量进行写入
        self.config = QSettings(
            "config.ini", QSettings.IniFormat
        )  #建立或读取config.ini文件,当程序目录下没有config文件时,建立文件;当目录有已经有config文件时,读取文件
        ip_red = self.config.value("/ip_red/value")  #从config中建立或读取ip_red的参数
        ip_blue = self.config.value("/ip_blue/value")  #从config中建立或读取ip_blue的参数
        ip_yellow = self.config.value(
            "/ip_yellow/value")  #从config中建立或读取ip_yellow的参数
        ip_green = self.config.value(
            "/ip_green/value")  #从config中建立或读取ip_green的参数
        speed = self.config.value("/speed/value")  #从config中建立或读取speed的参数
        #对speed参数进行判断并显示
        if speed != None:
            self.speed_edit.setText(speed)  #在界面speed_edit中把speed参数值显示出来
            speed = int(speed)  #config中的参数读取后,得到的是str,需要将其转化为int
            speed = 11 - speed  #speed参数的取值范围是[1,10]
        else:
            self.speed_edit.setText("1")  #当第一次运行程序时,默认speed参数的值为1
            speed = 1
        #在各个对应的文本框中,将ip地址显示出来
        self.ip_red_edit.setText(ip_red)
        self.ip_yellow_edit.setText(ip_yellow)
        self.ip_blue_edit.setText(ip_blue)
        self.ip_green_edit.setText(ip_green)

        #定义定位算法线程
        #PyQt官方推荐使用moveToThread函数来实现多线程,而不是改写Threading中的run()函数
        self.loc = Location(self)
        self.thread_loc = QThread()
        self.loc.moveToThread(self.thread_loc)

        #定义颜色分割线程
        self.camera = Camera(self)
        self.thread_camera = QThread()
        self.camera.moveToThread(self.thread_camera)
        self.thread_camera.started.connect(
            self.camera.run)  #设置线程执行时,执行的是camera中的run()函数
        self.thread_camera.start()  #启动线程

        #定义socket通信相关参数
        #定义各个机器人的ip地址和端口
        self.addr_red = (ip_red, 13301)
        self.addr_yellow = (ip_yellow, 13302)
        self.addr_blue = (ip_blue, 13303)
        self.addr_green = (ip_green, 13304)
        #定义各个机器人对应socket
        self.socket_red = socket(AF_INET, SOCK_STREAM)
        self.socket_yellow = socket(AF_INET, SOCK_STREAM)
        self.socket_blue = socket(AF_INET, SOCK_STREAM)
        self.socket_green = socket(AF_INET, SOCK_STREAM)
        #定义默认状态下,socket连接状态为false
        self.connected = False

    def slot_init(self):  #定义槽函数内容(参考Qt的“信号-槽”机制)
        self.butt_ip_edit.clicked.connect(self.ip_info_edit)  #设置ip编辑按钮按下后执行的函数
        self.butt_speed_edit.clicked.connect(
            self.speed_info_edit)  #设置speed编辑按钮按下后执行的函数
        self.butt_start.clicked.connect(
            self.dingwei_thread)  #设置定位线程启动按钮按下后执行的函数
        #设置各个文本框的焦点始终置底
        self.loc.cursor_r.connect(self.dw_red)
        self.loc.cursor_y.connect(self.dw_yellow)
        self.loc.cursor_b.connect(self.dw_blue)
        self.loc.cursor_g.connect(self.dw_green)
        self.butt_connect.clicked.connect(self.socket)  #设置网络通信按钮按下后执行的函数
        self.butt_end.clicked.connect(self.end)  #设置程序结束按钮按下后执行的函数

    def ip_info_edit(self):
        global ip_red, ip_yellow, ip_blue, ip_green
        #对修改后的ip进行操作
        ipx_red = self.ip_red_edit.text()  #从文本框中获取修改后的ip地址信息
        if ipx_red != "":  #判断修改后的信息是否为空
            self.config.setValue("/ip_red/value", ipx_red)  #修改config文件中对应的值
            ip_red = self.config.value("/ip_red/value")  #重新读取并显示
            self.ip_red_edit.setText(ip_red)

        ipx_yellow = self.ip_yellow_edit.text()
        if ipx_yellow != "":
            self.config.setValue("/ip_yellow/value", ipx_yellow)
            ip_yellow = self.config.value("/ip_yellow/value")
            self.ip_yellow_edit.setText(ip_yellow)

        ipx_blue = self.ip_blue_edit.text()
        if ipx_blue != "":
            self.config.setValue("/ip_blue/value", ipx_blue)
            ip_blue = self.config.value("/ip_blue/value")
            self.ip_blue_edit.setText(ip_blue)

        ipx_green = self.ip_green_edit.text()
        if ipx_green != "":
            self.config.setValue("/ip_green/value", ipx_green)
            ip_green = self.config.value("/ip_green/value")
            self.ip_green_edit.setText(ip_green)

    def speed_info_edit(self):
        global speed
        speedx = self.speed_edit.text()
        if speedx != "":
            self.config.setValue("/speed/value", speedx)
            self.speed_edit.setText(speedx)
            speedx = int(speedx)  #令速度参数的取值为1-10,当速度为10时,参数取1;速度为1时,参数取10
            speed = 11 - speedx

    def dw_red(self, red_dis):  #设置文本框中的文本显示始终聚焦在底部(也就是始终显示最新的一条)
        self.Loc_red.append(red_dis)  #append表示插入一段字符,并换行
        self.Loc_red.moveCursor(QTextCursor.End)  #将cusor移动到底部
        if self.connected == True:  #判断Socket是否已经连接
            self.socket_red.send(red_dis.encode())  #通过Socket将字符发送出去,使用默认编码

    def dw_yellow(self, yellow_dis):
        self.Loc_yellow.append(yellow_dis)
        self.Loc_yellow.moveCursor(QTextCursor.End)
        if self.connected == True:
            self.socket_yellow.send(yellow_dis.encode())

    def dw_blue(self, blue_dis):
        self.Loc_blue.append(blue_dis)
        self.Loc_blue.moveCursor(QTextCursor.End)
        if self.connected == True:
            self.socket_blue.send(blue_dis.encode())

    def dw_green(self, green_dis):
        self.Loc_green.append(green_dis)
        self.Loc_green.moveCursor(QTextCursor.End)
        if self.connected == True:
            self.socket_green.send(green_dis.encode())

    def dingwei_thread(self):  #定位算法线程设置
        red_queue.queue.clear()  #将清空queue中的内容清空
        yellow_queue.queue.clear()
        blue_queue.queue.clear()
        green_queue.queue.clear()
        time.sleep(0.5)

        self.thread_loc.started.connect(self.loc.run)  #开启定位算法线程
        self.thread_loc.start()

    def socket(self):  #设置Socket通信的连接
        self.socket_red.connect(self.addr_red)
        self.socket_yellow.connect(self.addr_yellow)
        self.socket_blue.connect(self.addr_blue)
        self.socket_green.connect(self.addr_green)
        self.connected = True

    def end(self):  #程序的结束
        self.socket_red.close()  #关闭Socket
        self.socket_yellow.close()
        self.socket_blue.close()
        self.socket_green.close()
        self.thread_loc.quit()  #关闭线程
        self.thread_camera.quit()
        self.camera.cap.release()  #释放camera
        self.close()  #关闭界面
class LabelPrinter(QObject):

    printerStatusReceived = pyqtSignal(
        str, bool, str, arguments=["comport", "success", "message"])

    def __init__(self, app=None, db=None):
        super().__init__()
        self._app = app
        self._db = db
        self._printer_thread = QThread()
        self._printer_worker = None
        self._queue = Queue()

        self._is_running = False

    @pyqtSlot()
    def stopPrintJobs(self):
        """
        Method to stop the print jobs when the application is trying to be closed
        :return:
        """
        self._is_running = False

    def _print_job(self, equipment=None, rows=None):
        """
        Method to start the printing thread
        :param equipment: Name of the printer equipment, as found in the Serial Port Manager serialPortsModel
        :param rows: List of rows to print
        :return:
        """
        # if self._printer_thread.isRunning():
        #     logging.info(f"The printer thread is already running, waiting for it to conclude")
        self._is_running = True

        # start_time = time.clock()
        # while self._printer_thread.isRunning():
        #     if not self._is_running:
        #         return
        #
        #     current_time = time.clock()
        #     if current_time - start_time > 5:
        #         logging.error(f"Printer thread is taking too long to complete, exiting printing")
        #         return

        try:
            if self._printer_thread.isRunning():
                self._queue.put(rows)

            else:

                model = self._app.serial_port_manager.serialPortsModel
                idx = model.get_item_index(rolename="equipment",
                                           value=equipment)
                if idx != -1:
                    comport = model.get(idx)["comPort"]

                    logging.info(f"Printing to comport={comport}")

                    self._queue.put(rows)
                    kwargs = {
                        "comport": comport,
                        "rows": rows,
                        "queue": self._queue
                    }

                    self._printer_worker = PrinterWorker(kwargs=kwargs)
                    self._printer_worker.moveToThread(self._printer_thread)
                    self._printer_worker.printerStatus.connect(
                        self._printer_status_received)
                    self._printer_thread.started.connect(
                        self._printer_worker.run)
                    self._printer_thread.start()

        except Exception as ex:

            logging.error(f"Error getting the comport: {ex}")

    def _printer_status_received(self, comport, success, message):
        """
        Method to catch the printer results
        :return:
        """
        self.printerStatusReceived.emit(comport, success, message)
        self._printer_thread.quit()

    @pyqtSlot(str, str, str, str, str, name="printADHLabel")
    def print_hookandline_angler_drop_hook(self, equipment: str, angler: str,
                                           drop: str, hook: str, species: str):
        """
        Method for printing an Angler/Drop/Hook tag
        :param angler:
        :param drop:
        :param hook:
        :return:
        """
        # Lines of Data to Print
        site = self._app.state_machine.site.zfill(
            3) if self._app.state_machine.site else ""
        set_id = self._app.state_machine.setId if self._app.state_machine.setId else ""
        # species also used, from the input variable
        barcode_number = angler + drop + hook
        lead_in = """
N
O
q500
S3
D10
ZT\n"""
        lead_out = "\nP" + str(
            1
        ) + "\n\n"  # lead out sends the label count (number of labels to print)

        # suffix = "\"\n"

        # Convert all of the rows to bytes
        lead_in_bytes = bytes(lead_in, "UTF-8")
        site_bytes = bytes("A0,10,0,4,1,1,N,\"Site Name: " + site + "\"\n",
                           "UTF-8")
        set_id_bytes = bytes("A0,50,0,4,1,1,N,\"Set ID: " + set_id + "\"\n",
                             "UTF-8")
        species_bytes = bytes("A0,90,0,4,1,2,N,\"" + species + "\"\n", "UTF-8")
        angler_drop_hook_bytes = bytes(
            "A0,150,0,5,1,2,N,\"" + str(barcode_number) + "\"\n", "UTF-8")
        barcode_bytes = bytes(
            "B0,250,0,1,3,3,100,N,\"" + str(barcode_number) + "\"\n", "UTF-8")
        lead_out_bytes = bytes(lead_out, "UTF-8")

        rows = [
            lead_in_bytes, site_bytes, set_id_bytes, species_bytes,
            angler_drop_hook_bytes, barcode_bytes, lead_out_bytes
        ]

        if equipment is None:
            equipment = "Zebra Printer Aft"

        # logging.info(f"print ADH: {equipment}")
        # for row in rows:
        #     logging.info(f"row={row}")

        self._print_job(equipment=equipment, rows=rows)

    @pyqtSlot(str, str, QVariant, str, name="printHookAndLineTagNumber")
    def print_hookandline_tag_number(self, tag_number, observation,
                                     species_observations, project):
        """
        Great reference + example code for printing using EPL commands:
        https://www.xtuple.org/node/1083

        EPL Reference:
        https://www.zebra.com/content/dam/zebra/manuals/en-us/printer/epl2-pm-en.pdf

        :return:
        """

        if isinstance(species_observations, QJSValue):
            species_observations = species_observations.toVariant()

        # Lines of Data to Print
        date_time = arrow.now().to("US/Pacific").format("YYYYMMDD_HHmmss")
        site = self._app.state_machine.site.zfill(
            3) if self._app.state_machine.site else ""
        set_id = self._app.state_machine.setId if self._app.state_machine.setId else ""
        vessel = self._app.state_machine.vessel if self._app.state_machine.vessel else ""

        try:
            sql = """
                SELECT s.LATITUDE, s.LONGITUDE from SITES s
                    INNER JOIN OPERATIONS o ON o.SITE_ID = s.SITE_ID
                    WHERE o.OPERATION_ID = ?;
            """
            params = [
                self._app.state_machine.siteOpId,
            ]
            results = self._app.rpc.execute_query(sql=sql, params=params)
            latitude = longitude = ""
            if len(results) == 1:
                result = results[0]
                latitude = self.dd_to_formatted_lat_lon(type="latitude",
                                                        value=result[0])
                longitude = self.dd_to_formatted_lat_lon(type="longitude",
                                                         value=result[1])
        except Exception as ex:
            logging.error(f"Error retrieving the latitude and longitude: {ex}")

        depth = " m"
        species = species_observations["species"] if species_observations[
            "species"] else ""
        length = species_observations[
            "length"] + " cm" if species_observations["length"] else ""
        weight = species_observations[
            "weight"] + " kg" if species_observations["weight"] else ""
        sex = species_observations["sex"] if species_observations["sex"] else ""
        pi = ""

        row1 = f"{date_time}    Site: {site}"
        row2 = f"SetID: {set_id}    {vessel}"
        row3 = f"{latitude}    {longitude}"
        # row4 = f"{depth}    {species}"
        row4 = f"{species}"
        row5 = f"{project}   {observation}"
        row6 = f"{length}   {weight}   {sex}"
        # row7 = f"{pi}    Barcode: {tag_number}"
        row7 = f"Barcode: {tag_number}"

        lead_in = """
N
O
q500
S3
D10
ZT\n"""
        lead_out = "\nP" + str(
            1
        ) + "\n\n"  # lead out sends the label count (number of labels to print)

        # Convert all of the rows to bytes
        lead_in_bytes = bytes(lead_in, "UTF-8")
        row1_bytes = bytes("A0,10,0,4,1,1,N,\"" + row1 + "\"\n", "UTF-8")
        row2_bytes = bytes("A0,50,0,4,1,1,N,\"" + row2 + "\"\n", "UTF-8")
        row3_bytes = bytes("A0,90,0,4,1,1,N,\"" + row3 + "\"\n", "UTF-8")
        row4_bytes = bytes("A0,130,0,4,1,1,N,\"" + row4 + "\"\n", "UTF-8")
        row5_bytes = bytes("A0,170,0,4,1,1,N,\"" + row5 + "\"\n", "UTF-8")
        row6_bytes = bytes("A0,210,0,4,1,1,N,\"" + row6 + "\"\n", "UTF-8")
        row7_bytes = bytes("A0,250,0,4,1,1,N,\"" + row7 + "\"\n", "UTF-8")
        barcode_bytes = bytes(
            "B0,290,0,1,3,3,90,N,\"" + str(tag_number) + "\"\n", "UTF-8")
        lead_out_bytes = bytes(lead_out, "UTF-8")

        rows = [
            lead_in_bytes, row1_bytes, row2_bytes, row3_bytes, row4_bytes,
            row5_bytes, row6_bytes, row7_bytes, barcode_bytes, lead_out_bytes
        ]

        for row in rows:
            logging.info(f"label row: {row}")

        self._print_job(equipment="Zebra Printer Cutter", rows=rows)

    def dd_to_formatted_lat_lon(self, type, value):
        """
        Method to convert a latitude in decimal degrees to well-formatted string
        :param type: str - enumerate value:  latitude / longitude
        :param value:
        :return:
        """
        if not isinstance(value, float):
            logging.error(
                "Error formatting latitude to nice format: {0}".format(value))
            return str(value)

        min, deg = math.modf(value)
        min *= 60
        deg = int(deg)

        if type == "latitude":
            uom = "S" if value < 0 else "N"
        else:
            uom = "W" if value < 0 else "E"

        if uom in ["S", "W"] and deg <= 0 and min <= 0:
            deg = -deg
            min = -min

        return f"{deg:d} {min:6.3f}' {uom}"

        return f"{deg:d}\xb0 {min:6.3f}' {uom}"
Esempio n. 48
0
class ManagerUpdates(QMessageBox):
    """
    Self contained manager that checks if updates are available on GitHub
    and displays the ressults in a message box.
    """

    def __init__(self, parent=None, pytesting=False):
        super(ManagerUpdates, self).__init__(parent)
        self._pytesting = pytesting

        self.setWindowTitle('GWHAT updates')
        self.setWindowIcon(icons.get_icon('master'))
        self.setMinimumSize(800, 700)
        self.setWindowFlags(Qt.Window |
                            Qt.CustomizeWindowHint |
                            Qt.WindowCloseButtonHint)
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.setStandardButtons(QMessageBox.Ok)
        self.setDefaultButton(QMessageBox.Ok)

        self.thread_updates = QThread()
        self.worker_updates = WorkerUpdates()
        self.worker_updates.moveToThread(self.thread_updates)
        self.thread_updates.started.connect(self.worker_updates.start)
        self.worker_updates.sig_ready.connect(self._receive_updates_check)

        self.start_updates_check()

    def start_updates_check(self):
        """Check if updates are available."""
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        self.thread_updates.start()

    @QSlot()
    def _receive_updates_check(self):
        self.thread_updates.quit()
        latest_release = self.worker_updates.latest_release
        if self.worker_updates.error is not None:
            icn = QMessageBox.Warning
            msg = self.worker_updates.error
        else:
            icn = QMessageBox.Information
            if self.worker_updates.update_available:
                url_i = ("https://gwhat.readthedocs.io/en/latest/"
                         "getting_started.html")
                msg = ("<b>GWHAT %s is available!</b><br><br>"
                       "This new version can be downloaded from our "
                       "<a href=\"%s\">Releases</a> page.<br><br>"
                       "Instructions to update GWHAT are provided on our "
                       "<a href=\"%s\">Installation</a> instructions."
                       ) % (latest_release, __releases_url__, url_i)
            else:
                url_m = "https://github.com/jnsebgosselin/gwhat/milestones"
                url_t = "https://github.com/jnsebgosselin/gwhat/issues"
                msg = ("<b>%s is up to date.</b><br><br>"
                       "Further information about GWHAT releases are available"
                       " on our <a href=\"%s\">Releases</a> page.<br><br>"
                       "The roadmap of the GWHAT project can be consulted"
                       " on our <a href=\"%s\">Milestones</a> page.<br><br>"
                       "Please help GWHAT by reporting bugs or proposing new"
                       " features on our"
                       " <a href=\"%s\">Issues Tracker</a>."
                       ) % (__namever__, __releases_url__, url_m, url_t)
        self.setText(msg)
        self.setIcon(icn)

        QApplication.restoreOverrideCursor()
        if self._pytesting is False:
            self.exec_()
        else:
            self.show()
Esempio n. 49
0
class TextureMatcher(QWidget):
    def __init__(self, cc: ControlCenter,
                 mat_output_node: GMaterialOutputNode):
        super().__init__(parent=None)
        self.cc = cc
        self._out_node = mat_output_node

        # Define components
        self._menu_bar = QMenuBar()
        self._settings_drawer = QDockWidget(self, Qt.Drawer)
        self._settings_panel = SettingsPanel(self.cc, self)
        self._openGL = OpenGLWidget(400, 400, None,
                                    OpenGLWidget.TEXTURE_RENDER_MODE)
        self._image_plotter = ImagePlotter("Render")
        self._target_plotter = ImagePlotter("Target")
        self._loss_plotter = pg.PlotWidget(title="Loss")
        self._layout = QGridLayout()
        self._shader = self._out_node.get_shader()
        self._program = self._out_node.get_program()
        self._loss_visualizer = LossVisualizer(mat_output_node)

        # Define properties
        self._loss_plot_color = (1., 0.6, 0., 1.0)
        self._loss_plot_style = 'default'

        # Define data
        self._target_image = None
        self._target_matrix = None
        self.thread = None
        self.gd = None
        self.ren_i = 0
        self._params = {}
        self._loss_hist = None
        self._gd_info = dict()
        self._target_filename = None

        self._init_widget()

    def _init_widget(self):
        self.setWindowTitle("DiPTeR - Texture Matcher")

        # Setup menu
        visualize_menu = self._menu_bar.addMenu("visualize")
        visualize_loss_action = visualize_menu.addAction("visualize loss")
        visualize_loss_action.triggered.connect(self._open_loss_viz_window)

        # Setup settings panel
        self._settings_panel.texture_loaded.connect(self._set_image_to_match)
        self._settings_panel.match_start.connect(
            self._run_gradient_descent_torch)
        self._settings_panel.match_stop.connect(self._stop_gradient_descent)
        self._settings_panel.reset_requested.connect(self._reset)
        self._settings_panel.save_data.connect(self._save_data)
        self._settings_panel.restore_best.connect(self._restore_best_params)
        self._settings_panel.setMaximumWidth(250)

        # Setup plots
        self._openGL.init_done.connect(self._set_gl_program)
        self._image_plotter.hide_axes()
        self._image_plotter.set_user_input(False)
        self._target_plotter.hide_axes()
        self._target_plotter.set_user_input(False)

        # Setup openGL rendering window
        opengl_layout = QVBoxLayout()
        opengl_layout.setContentsMargins(20, 0, 20, 10)
        opengl_title = QLabel("OpenGL Render")
        opengl_title_font = QFont()
        opengl_title_font.setPointSize(14)
        opengl_title.setFont(opengl_title_font)
        opengl_layout.addWidget(opengl_title, alignment=Qt.AlignHCenter)
        opengl_layout.addWidget(self._openGL)

        # Setup strech factors
        self._layout.setColumnStretch(0, 2)
        self._layout.setColumnStretch(1, 2)
        self._layout.setColumnStretch(2, 2)
        self._layout.setColumnStretch(3, 1)

        self._layout.setMenuBar(self._menu_bar)
        self._layout.addLayout(opengl_layout, 0, 0)
        self._layout.addWidget(self._image_plotter, 0, 1)
        self._layout.addWidget(self._target_plotter, 0, 2)
        self._layout.addWidget(self._loss_plotter, 1, 0, 1, 3)
        self._layout.addWidget(self._settings_panel, 0, 3, 2, 1)

        self.setLayout(self._layout)

    def _set_image_to_match(self, image: Image):
        # We want to display the same image that we are testing the loss against. This image is columns major (input,y)
        # which is not what matplotlib want's so we have to transpose it back to row major
        self._target_image = image.convert("RGB")
        self._target_filename = image.filename
        self._target_matrix = image_funcs.image_to_tensor(self._target_image)
        self._target_plotter.set_image(self._target_matrix)

    def _set_gl_program(self):
        self._openGL.set_program(self._program)

    def _run_gradient_descent_torch(self):
        self._reset()

        self.gd = GradientDescent(self._target_image, self._out_node,
                                  self._settings_panel.settings())
        self.thread = QThread()
        _logger.debug("Started Gradient Descent Thread...")
        run_in_thread(self.gd,
                      self.thread,
                      iteration_done_callback=self._gd_iter_callback,
                      first_render_done_callback=self._set_parameter_values,
                      gd_finished_callback=self._finish_gradient_descent)

    def _reset(self):
        # Reset procedural model parameters
        for key in self._params:
            self._params[key].restore_value()
            self._set_parameter_values(self._params)

        # Reset plots
        self._loss_plotter.plotItem.clear()
        self._image_plotter.clear()

    def _stop_gradient_descent(self):
        if self.thread.isRunning():
            _logger.info("Stopping Gradient Descent Thread...")
            self._settings_panel.set_gd_finishing()
            self.gd.stop()
            self.thread.quit()
            self._settings_panel.set_gd_finished()

    def _finish_gradient_descent(self, params, loss_hist, info):
        self._params = params
        self._loss_hist = loss_hist
        self._gd_info = info
        min_loss = np.min(loss_hist)
        self._stop_gradient_descent()
        _logger.info(
            "Gradient Descent finished with a minimum loss of {:.4f}.".format(
                min_loss))

    def _gd_iter_callback(self, props):
        loss_hist = props['loss_hist']
        iter = props['iter']
        params = props['params']
        render = props['render']

        self._image_plotter.set_image(render)

        x = np.linspace(0, iter, num=iter + 1, endpoint=True)
        self._loss_plotter.plot(x, loss_hist, symbol='o')

        self._set_parameter_values(params)
        _logger.info("{}. loss: {}, params: {}".format(props['iter'],
                                                       props['loss'], params))

    def _set_parameter_values(self, params: dict):
        for uniform_key, value in params.items():
            try:
                if isinstance(value, Parameter):
                    value = value.get_value()

                self._program[uniform_key] = value
            except IndexError as e:
                _logger.error("Uniform {} does not exist in program!".format(
                    uniform_key))
                raise e

    def _open_loss_viz_window(self):
        self._loss_visualizer.open(self._settings_panel.settings(),
                                   self._target_image, self._out_node)

    def _save_data(self, filename: str):
        mat_name = self.cc.active_material.name
        settings = self._settings_panel.settings().to_dict()
        f = h5py.File(filename, "w")
        dset = f.create_dataset("data", data=self._loss_hist, dtype="f")

        for key, value in settings.items():
            try:
                dset.attrs[key] = value
            except TypeError:
                dset.attrs[key] = str(value)
        dset.attrs["material_name"] = mat_name
        dset.attrs["target_filename"] = self._target_filename
        f.close()
        _logger.info("Saved data to {}".format(filename))

    def _restore_best_params(self):
        for k, v in self._gd_info["min_params"].items():
            for pk, pv in self._params.items():
                if k == pk:
                    pv.set_value(v)

        self._set_parameter_values(self._params)
        _logger.info("Restored best parameter values with loss {}".format(
            self._gd_info["min_loss"]))
class ws_client(QObject):
    connect_signal = pyqtSignal(bool)

    def __init__(self, websocket_ip, port=9090, name='', frame_id="map"):
        super(ws_client, self).__init__()
        """
        Class to manage publishing to ROS thru a rosbridge websocket.
        :param str websocket_ip: IP of the machine with the rosbridge server.
        :param int port: Port of the websocket server, defaults to 9090.
        """

        self.name = websocket_ip if name == '' else name
        self._ip = websocket_ip
        self._port = port

        # List for for each sub topic.
        # Where: key = sub_topic; data = [type_data, pub_topic_name,rospy.Publisher(..)]
        self.sub_list = {}
        self.frame_id = frame_id
        self.abort = False

        self._runFlag = False
        self._connect_flag = False

        self._ws = None
        self._advertise_dict = {}

        self.ws_cb_t = QThread()
        self.moveToThread(self.ws_cb_t)
        self.ws_cb_t.started.connect(self.update)
        self.ws_cb_t.start()

    @pyqtSlot()
    def update(self):
        print("%s:%s\t|\tStart the thread of callback" %
              (self._ip, self._port))
        while True:
            if self.abort:
                break
            self._callback()
            QApplication.processEvents()
        print("%s:%s\t|\tStop the thread of callback" % (self._ip, self._port))

    def setIp(self, ipStr):
        self._ip = ipStr
        print("set ip", self._ip)

    def setPort(self, port):
        try:
            self._port = int(port)
            print("set port", self._port)
        except:
            pass

    def connect(self, ip=None, port=None):
        if self.is_connected():
            print("The client is already connected")
            return
        if ip == None:
            ip = self._ip
        if port == None:
            port = self._port

        print("%s:%s\t|\tConnection to server" % (ip, port))
        try:
            self._ws = websocket.create_connection('ws://' + ip + ':' +
                                                   str(port))
        except:
            print("%s:%s\t|\tError connecting to server !!!" % (ip, port))
            self._connect_flag = False
            self.connect_signal.emit(False)
            return

        print("%s:%s\t|\tThe connection is successful: " % (ip, port))

        # self.param_ws_signal.emit(self._ws)
        self._connect_flag = True
        self.connect_signal.emit(True)
        self._advertise_dict = {}

        # subscribe to
        for key in self.sub_list:
            self._subscribe(key, self.sub_list[key][0])
        self._runFlag = True

    def disconnect(self):
        print("%s:%s\t|\tDisconnect: " % (self._ip, self._port))
        """Cleanup all advertisings"""
        d = self._advertise_dict
        for k in d:
            self._unadvertise(k)
        self._connect_flag = False
        self.connect_signal.emit(False)

        if self._ws is not None:
            self._ws.close()

    def is_connected(self):
        return self._connect_flag

    def _advertise(self, topic_name, topic_type):
        """
        Advertise a topic with it's type in 'package/Message' format.
        :param str topic_name: ROS topic name.
        :param str topic_type: ROS topic type, e.g. std_msgs/String.
        :returns str: ID to de-advertise later on.
        """
        new_uuid = str(uuid4())
        self._advertise_dict[new_uuid] = {
            'topic_name': topic_name,
            'topic_type': topic_type
        }
        advertise_msg = {
            "op": "advertise",
            "id": new_uuid,
            "topic": topic_name,
            "type": topic_type
        }
        # send if connect
        if self.is_connected():
            try:
                self._ws.send(json.dumps(advertise_msg))
            except:
                self._connect_flag = False
                self.connect_signal.emit(False)
                return

        return new_uuid

    def _unadvertise(self, uuid):
        unad_msg = {
            "op": "unadvertise",
            "id": uuid,
            # "topic": topic_name
        }

        # send if connect
        if self.is_connected():
            try:
                self._ws.send(json.dumps(unad_msg))
            except:
                self._connect_flag = False
                self.connect_signal.emit(False)

    def _killThread(self):
        self.abort = True
        self.ws_cb_t.quit()
        self.ws_cb_t.wait()

    def __del__(self):
        self._killThread()

        self.disconnect()

        self._connect_flag = False
        self.connect_signal.emit(False)
        self._runFlag = False
        print("%s:%s\t|\tDelete websocket: " % (self._ip, self._port))

    def _publish(self, topic_name, message):
        """
        Publish onto the already advertised topic the msg in the shape of
        a Python dict.
        :param str topic_name: ROS topic name.
        :param dict msg: Dictionary containing the definition of the message.
        """
        msg = {'op': 'publish', 'topic': topic_name, 'msg': message}
        json_msg = json.dumps(msg)

        # send if connect
        if self.is_connected():
            try:
                self._ws.send(json_msg)
            except:
                self._connect_flag = False
                self.connect_signal.emit(False)
                return

    def publish(self, topic_name, ros_message):
        """
        Publish on a topic given ROS message thru rosbridge.
        :param str topic_name: ROS topic name.
        :param * ros_message: Any ROS message instance, e.g. LaserScan()
            from sensor_msgs/LaserScan.
        """
        # First check if we already advertised the topic

        d = self._advertise_dict
        for k in d:
            if d[k]['topic_name'] == topic_name:
                # Already advertised, do nothing
                break
        else:
            # Not advertised, so we advertise
            topic_type = ros_message._type
            self._advertise(topic_name, topic_type)
        # Converting ROS message to a dictionary thru YAML
        ros_message_as_dict = yaml.load(ros_message.__str__())
        # Publishing
        self._publish(topic_name, ros_message_as_dict)

    def subscribe(self, topic_name, msgs_data, pub_topic_name=''):

        if pub_topic_name == '':
            pub_topic_name = topic_name

        # added to list
        pub = rospy.Publisher(pub_topic_name,
                              msgs_data.__class__,
                              queue_size=10)

        self.sub_list[topic_name] = [msgs_data, pub_topic_name, pub]

    def _subscribe(self, topic_name, msgs_data):
        pub_msg = {
            'op': 'subscribe',
            'topic': topic_name,
            'msgs_data': msgs_data._type
        }

        # send to server
        json_msg = json.dumps(pub_msg)
        self._ws.send(json_msg)
        self.initFlaf = True
        print("%s:%s\t|\tSubscribe to: %s \ttype: %s" %
              (self._ip, self._port, topic_name, msgs_data._type))

    def _callback(self):
        """
        Publish onto the already advertised topic the msg in the shape of
        a Python dict.
        :param str topic_name: ROS topic name.
        :param dict msg: Dictionary containing the definition of the message.
        """
        if not self.is_connected():
            return

        # send if connect
        try:
            json_message = self._ws.recv()
            self._connect_flag = True
            self.connect_signal.emit(True)
            type_msg = json.loads(json_message)['op']
        except:
            self._connect_flag = False
            self.connect_signal.emit(False)
            self._runFlag = False
            print("connected loss")
            return

        if type_msg == 'publish':
            # conver json to ROS msgs
            msgs_conf = self.sub_list[json.loads(json_message)['topic']]
            dictionary = json.loads(json_message)['msg']
            result = message_converter.convert_dictionary_to_ros_message(
                msgs_conf[0]._type, dictionary)
            # print("===============\n"
            #       "Type: '%s' \n"
            #       "===============\n '%s'" % (msgs_conf[0]._type, result))
            # pub to topic

            #fix of unicode
            try:
                result.header.frame_id = str(header.frame_id)
            except:
                pass

            #fix of laserscan
            if msgs_conf[0]._type == 'sensor_msgs/LaserScan':

                for i in range(len(result.ranges)):
                    if result.ranges[i] == None:
                        result.ranges[i] = float('inf')
                    else:
                        result.ranges[i] = float(result.ranges[i])

            msgs_conf[2].publish(result)

        if type_msg == 'service_response':
            print("service_response:", json.loads(json_message)['result'])

        # return result

    def call_service(self, topic_name, msgs_data, pub_topic_name=''):
        self.initFlaf = True

        if pub_topic_name == '':
            pub_topic_name = topic_name

        ros_message_as_dict = yaml.load(msgs_data.__str__())

        pub_msg = {
            'op': 'call_service',
            'service': topic_name,
            "args": ros_message_as_dict
        }

        # send to server
        json_msg = json.dumps(pub_msg)
        # send if connect
        if self.is_connected():
            try:
                self._ws.send(json_msg)
            except:
                self._connect_flag = False
                self.connect_signal.emit(False)
                return

        print("%s | call_service: %s msgs_data: %s" %
              (self.name, topic_name, msgs_data))
Esempio n. 51
0
class FuncBox(QLabel):
    height_ = 25

    def __init__(self, *args):
        super().__init__(*args)
        self.setObjectName("funcBox")
        self.mainWin = self.parent()
        self.setUi()
        self.defaultFlag = True
        # 读取按钮线程

        # 提取按钮
        self.extractBtn_ = self.setExtractBtn()

        self.typeBox_ = TypeBox(self)

        # 读取按钮
        self.readBtn = self.setReadBtn()
        self.readThread = QThread()
        self.readWorker = ReadWorker(mainWin=self.mainWin)
        self.setReadThread()

        self.clearBtn()
        self.searchBtn = self.setSearchBtn()
        self.lstTypeSelectCombo()

        # 提取线程
        self.extractThread = QThread()
        self.worker = ExtractWorker(mainWin=self.mainWin)
        self.extractWork()

    def setUi(self):
        self.resize(self.mainWin.width(), 50)
        self.move(0, self.mainWin.height() - 50)

    def setReadBtn(self):
        # 读取选中列表
        readBtn = QPushButton("读取", self)
        readBtn.setObjectName("readBtn")
        readBtn.resize(80, self.height_)
        readBtn.move(0, self.height() - self.height_ * 2)
        readBtn.setEnabled(False)

        def readBtnFunc():
            self.mainWin.findChild(QLabel, "indexSelect").pb.reset()
            self.readThread.start()
            readBtn.setEnabled(False)

        readBtn.clicked.connect(readBtnFunc)
        return readBtn

    def setReadThread(self):
        def readEnd(bool_):
            self.mainWin.indexList.scrollToItem(self.mainWin.indexList.item(0))
            self.readBtn.setEnabled(True)
            self.readWorker.stop()
            self.readThread.quit()

        self.readWorker.readSignal[bool].connect(readEnd)

        def pbValue(int_):
            self.mainWin.findChild(QLabel, "indexSelect").pb.setValue(int_)

        self.readWorker.readSignal[int].connect(pbValue)
        self.readWorker.moveToThread(self.readThread)
        self.readThread.started.connect(self.readWorker.read)

    def clearBtn(self):
        # 清空列表
        clearBtn = QPushButton("清空", self)
        clearBtn.setObjectName("clearBtn")
        clearBtn.resize(80, self.height_)
        clearBtn.move(80, self.height() - self.height_ * 2)

        def clearFunc():
            self.mainWin.indexCheckList.clear()
            self.mainWin.extractList = []

        clearBtn.clicked.connect(clearFunc)

    def setSearchBtn(self):
        # 搜索框
        searchLine = QLineEdit("输入序号", self)
        searchLine.setObjectName("searchInput")
        searchLine.resize(80, self.height_)
        searchLine.move(0, self.height() - self.height_)
        # search
        searchBtn = QPushButton("搜索", self)
        searchBtn.setObjectName("searchBtn")
        searchBtn.resize(80, self.height_)
        searchBtn.move(80, self.height() - self.height_)
        searchBtn.setEnabled(False)

        def searchBtnFunc():
            searchF(searchLine, self.mainWin)

        searchBtn.clicked.connect(searchBtnFunc)

        return searchBtn

    def lstTypeSelectCombo(self):
        # lstTypeSelect
        lstTypeSInfo = QLabel("模式选择:", self)
        lstTypeSInfo.resize(100, 25)
        lstTypeSInfo.move(160, self.height() - 50)
        lstTypeSelect = QComboBox(self)
        lstTypeSInfo.setAlignment(Qt.AlignLeft | Qt.AlignCenter)
        lstTypeSelect.setObjectName("lstTypeSelect")

        lstTypeSelect.resize(self.width() - lstTypeSInfo.width() - 220, 25)
        lstTypeSelect.move(lstTypeSInfo.width() + 160, self.height() - 50)

        for key in typeDict.keys():
            lstTypeSelect.addItem(typeDict[key][2])

        def actFunc(index):
            self.mainWin.lstType = str(index)
            if self.mainWin.lstType != "1":
                self.readBtn.click()

        lstTypeSelect.activated.connect(actFunc)

        def lstTypeSelectFunc(index):
            indexChangedFunc(index, self)

        lstTypeSelect.currentIndexChanged.connect(lstTypeSelectFunc)

    def setExtractBtn(self):
        # extractBtn
        extractBtn = QPushButton("提取", self)
        extractBtn.setObjectName("extractBtn")
        extractBtn.resize(60, 50)
        extractBtn.move(self.width() - 60, self.height() - 50)
        extractBtn.setEnabled(False)

        def extractFunc():
            self.mainWin.findChild(QLabel, "typeBox").pb.reset()
            mainExtractFunc(extractBtn, self)

        extractBtn.clicked.connect(extractFunc)
        return extractBtn

    def extractWork(self):
        def pbValue(int_):
            self.mainWin.findChild(QLabel, "typeBox").pb.setValue(int_)

        self.worker.extractSignal[int].connect(pbValue)

        def endThread(bool_):
            self.extractBtn_.setEnabled(True)
            self.worker.stop()
            self.extractThread.quit()
            self.mainWin.indexCheckList.clear()
            self.mainWin.extractList = []

        self.worker.extractSignal[bool].connect(endThread)
        self.worker.moveToThread(self.extractThread)
        self.extractThread.started.connect(self.worker.extract)
Esempio n. 52
0
class Main(QtWidgets.QMainWindow, Ui_MainWindow):

    #signals
    #parameters = pyqtSignal(list, list, list, str, str, str)

    #initialize
    def __init__(self, parent=None):
        #QtWidgets.QMainWindow.__init__(self, parent)
        super(Main, self).__init__()

        ##################################################
        # execute the modelbuilder in an own thread -> as seen when starting the thread in the buildModel function,
        # a thread is only started if called from UI

        # create objects
        self.moBuThread = QThread()
        self.moBuObj = modelBuilder(
        )  # create an instance of the class modelBuilder

        # move modelbuilder object to thread
        self.moBuObj.moveToThread(self.moBuThread)

        # connect modelbuilder signals to slots in this class
        self.moBuObj.statusUpdate.connect(
            self.onStatusUpdate
        )  # the function onStatusUpdate is a slot in this class

        # connect modelbuilder signals to slots in this class -> the function onModelCreated contains a slot of the thread object (self.moBuThread.quit)
        self.moBuObj.finished.connect(
            self.onModelCreated
        )  # the function onModelCreated is a slot in this class

        # connect thread started signal to modelbuilder's slot
        # pass parameters as well: if you want to "pass parameters to QThread", then it's quite straightforward.
        # 1. You can access member variable of your subclass from any methods of your subclass.
        # 2. The slot of your worker object, that will be executed in another thread can take as many argument as you want.
        # 3. Use a lambda function: self.moBuThread.started.connect(lambda: self.moBuObj.build(objects, nodesWithoutMbAttribute, couplings, modelname, self.modelfolderpathname, fpesfilepath))
        # Just for information: if build was executed in the main thread: modelCreated = self.moBuObj.build(self, objects, nodesWithoutMbAttribute, couplings, modelname, modelfolderpathname, fpesfilepath)  # execute the build method (in the same thread)
        # Here the first method is used: The build method of the modelbuilder object is called without arguments, when the thread is started the build function waits until it receives data.
        # The data is set directly before the thread is started (see "set data for the modelbuilder object").
        self.moBuThread.started.connect(self.moBuObj.build)

        ###################################################

        self.setupUi(self)

        #ui signals
        self.bselectfpesfile.clicked.connect(self.selectFPES)
        self.bbuildmodel.clicked.connect(self.buildModel)
        self.bdoc.clicked.connect(self.documentation)

    #slot
    #when the modelbuilder is finished, this function is called
    def onModelCreated(self, i):
        #when the thread has finished, quit it
        self.moBuThread.quit()
        modelCreated = i
        if modelCreated == 0:
            if self.calledFromUi:
                QMessageBox.information(
                    None, "Model(s) created",
                    "The model(s) was/were created in the folder \"%s\" (a subdirectory of the folder in which \"%s\" lies). In the <b>config.txt</b> file the model file(s) and the respective modelbase file(s) are listed."
                    % (self.modelfolderpathname, self.selectedfpesfile),
                    QtWidgets.QMessageBox.Ok)
            else:
                print(
                    "OK - The model(s) was/were created in the \nMODELFOLDER: \""
                    + self.modelfolderpathname +
                    "\"\nThis is a subdirectory of the folder in which \"" +
                    self.selectedfpesfile +
                    "\" lies. In the config.txt file the model file(s) and the respective modelbase file(s) are listed."
                )
                print("\n")
        elif modelCreated == 1:
            QMessageBox.warning(
                None, "Model(s) not created",
                "The model(s) could not be created. The simulator or interface is not supported. Please refer to the documentation for simulators and the interface.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The model(s) could not be created. The simulator or interface is not supported. Please refer to the documentation for simulators and the interface."
            )
        elif modelCreated == 2:
            QMessageBox.warning(
                None, "Model(s) not created",
                "The model(s) could not be created. The portnames for the basic models are not okay. Please refer to the documentation.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The model(s) could not be created. The portnames for the basic models are not okay. Please refer to the documentation."
            )
        elif modelCreated == 3:
            QMessageBox.warning(
                None, "Model(s) not created",
                "The model(s) could not be created. The modelbasefile cannot be copied. Please check, that the modelbasefile is lying in the same folder (or a subdirectory, see documentation) as the .jsonsestree file containing the FPES. Furthermore the mb-attribute needs to refer to the modelbasefilename (see documentation). Using FMI there could also be a problem parameterizing the MB.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The model(s) could not be created. The modelbasefile cannot be copied. Please check, that the modelbasefile is lying in the same folder as the .jsonsestree file containing the FPES. Furthermore the mb-attribute needs to refer to the modelbasefilename (see documentation)."
            )
        elif modelCreated == 4:
            QMessageBox.warning(
                None, "Model(s) not created",
                "The model(s) could not be created. A parametervalue to vary could not be interpreted as a Python variable.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The model(s) could not be created. A parametervalue to vary could not be interpreted as a Python variable."
            )
        elif modelCreated == 5:
            QMessageBox.warning(
                None, "Model(s) not imported in the simulator",
                "The model(s) could not be imported in the simulator. The simulator could not be found. Please make sure it is accessible via Shell/Command.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The model(s) could not be imported in the simulator. The simulator could not be found. Please make sure it is accessible via Shell/Command."
            )
        elif modelCreated == 6:
            QMessageBox.warning(
                None, "FMU(s) not created",
                "The FMU(s) could not be created. OpenModelica is necessary for creating FMU(s). Please make sure OpenModelica's omc executable is accessible via Shell/Command.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The FMUs could not be created. OpenModelica is necessary for creating FMUs. Please make sure OpenModelica's omc executable is accessible via Shell/Command."
            )
        elif modelCreated == 7:
            QMessageBox.warning(
                None, "Model(s) not created",
                "The model(s) could not be created. The old model directory could not be removed automatically. For automatic remove no program may access any content of it. Please make sure no model directory is in the same directory as the FPES.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - The model(s) could not be created. An old model directory could not be removed automatically. For automatic remove no program may access any content of it. Please make sure no model directory is in the same directory as the FPES."
            )
        elif modelCreated == 8:
            QMessageBox.warning(
                None, "Model FMU(s) not imported in the simulator",
                "Model FMUs(s) could not be imported in the simulator (for OpenModelica / Dymola).",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - Model FMU(s) could not be imported in the simulator (for OpenModelica / Dymola)."
            )
        elif modelCreated == 9:
            QMessageBox.warning(
                None, "Model(s) not imported in the simulator",
                "One FMU did not pass the compliance check. Check the statusmessage (it is reset when this message is closed)!",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - One FMU did not pass the compliance check. Check the statusmessage!"
            )
        elif modelCreated == 10:
            QMessageBox.warning(
                None, "FMU(s) not created",
                "Please check the FPES and the created model which shall be translated to an FMU.",
                QtWidgets.QMessageBox.Ok)
            print(
                "Not OK - FMU(s) could not be created of the model(s)! Please check the FPES and the created model which shall be translated to an FMU."
            )
        elif modelCreated == 11:
            if self.calledFromUi:
                QMessageBox.information(
                    None, "Created",
                    "Created in the folder \"%s\" (a subdirectory of the folder in which \"%s\" lies)."
                    % (self.modelfolderpathname, self.selectedfpesfile),
                    QtWidgets.QMessageBox.Ok)
            else:
                print("OK - Created in the \nFOLDER: \"" +
                      self.modelfolderpathname +
                      "\"\nThis is a subdirectory of the folder in which \"" +
                      self.selectedfpesfile + "\" lies.")
        #if called from ui, the build model button needs to be activated again and clear the status
        if self.calledFromUi:
            self.bbuildmodel.setEnabled(True)
            self.lstatustext.setText("")

    #slot
    #show the statusmessage from the thread
    def onStatusUpdate(self, message):
        if self.calledFromUi:  #update the UI
            self.lstatustext.setText(message)
        else:
            print(message + "\n")

    def selectFPES(self):
        fname = QFileDialog.getOpenFileName(
            self, "Open an FPES from JSON", '',
            "FPES SES Tree (*.jsonsestree);;All files (*)")
        self.leselectedfpesfile.setText(fname[0])

    def buildModel(self, selectedfpesfile="", calledFromUi=True):
        #make variables class variables
        self.calledFromUi = calledFromUi

        #deactivate the build model button
        if self.calledFromUi:
            self.bbuildmodel.setEnabled(False)

        try:
            # get the fpesfile
            if self.calledFromUi:  #if the modelbuilder was called from the UI
                self.selectedfpesfile = self.leselectedfpesfile.text()
            else:
                self.selectedfpesfile = selectedfpesfile

            # only if a file is selected
            if self.selectedfpesfile != "" and not " " in self.selectedfpesfile:
                # read file
                f = open(self.selectedfpesfile, "r")
                filestr = f.read()
                f.close()
                readJsonObj = readJson  #create an instance of the class readJson
                okay, nodelist, sespes, sesvar = readJsonObj.fromJSON(
                    self, filestr)
                #only if the file is okay and an FPES
                if okay and sespes[0][0] == "fpes" and len(nodelist) > 0:
                    #read the FPES file
                    objects, couplings, nodesWithoutMbAttribute = readJsonObj.readFPES(
                        self, nodelist)
                    if objects:
                        print(
                            "The nodes\n" +
                            ",\n".join(nodesWithoutMbAttribute) +
                            "\nhave no MB-Attribute. Are their attributes needed for the simulation? This is just a hint in case of searching for a mistake.\n"
                        )
                        #get a name for the folder including the path, in which the models are created -> it shall get the name of the FPES .jsonsestree file
                        fpesfilepathname, fpesfileext = os.path.splitext(
                            self.selectedfpesfile)
                        self.modelfolderpathname = fpesfilepathname + "_models"
                        #get a name for the model -> it shall get the name of the FPES .jsonsestree file
                        modelname = os.path.basename(
                            fpesfilepathname) + "_model"
                        #get the path of the selected FPES .jsonsestree file
                        fpesfilepath = os.path.split(
                            self.selectedfpesfile
                        )[0]  #get the path to the modelbase from the fpesfile

                        #set data for the modelbuilder object
                        self.moBuObj.objects = objects
                        self.moBuObj.nodesWithoutMbAttribute = nodesWithoutMbAttribute
                        self.moBuObj.couplings = couplings
                        self.moBuObj.modelname = modelname
                        self.moBuObj.modelfolderpathname = self.modelfolderpathname
                        self.moBuObj.fpesfilepath = fpesfilepath
                        self.moBuObj.sesvar = sesvar

                        #if called from ui: start the modelbuilder thread
                        #if called from shell: start the build function (not in an own thread)
                        if self.calledFromUi:
                            self.moBuThread.start()
                        else:
                            self.moBuObj.build()

                    elif not objects:
                        QMessageBox.warning(
                            None, "Model(s) not created",
                            "The model(s) could not be created. Objects cannot be created from the nodelist.",
                            QtWidgets.QMessageBox.Ok)
                        print(
                            "Not OK - The model(s) could not be created. Objects cannot be created from the nodelist."
                        )
                        # if called from ui, the build model button needs to be activated again and clear the status
                        if self.calledFromUi:
                            self.bbuildmodel.setEnabled(True)
                            self.lstatustext.setText("")
                    else:
                        nWoMbA = ', '.join(nodesWithoutMbAttribute)
                        QMessageBox.warning(
                            None, "Model(s) not created",
                            "The model(s) could not be created. The nodes \"%s\" do not have an mb-attribute."
                            % nWoMbA, QtWidgets.QMessageBox.Ok)
                        print(
                            "Model(s) not created",
                            "The model(s) could not be created. The nodes \"" +
                            nWoMbA + "\" do not have an mb-attribute.")
                        # if called from ui, the build model button needs to be activated again and clear the status
                        if self.calledFromUi:
                            self.bbuildmodel.setEnabled(True)
                            self.lstatustext.setText("")
                else:
                    QMessageBox.warning(
                        None, "Can not read file",
                        "The file \"%s\" seems not to contain an FPES. Please open this file in SESToPy, make sure it represents an FPES and that the Selector in the Information ToolBox is set to flattened PES."
                        % self.selectedfpesfile, QtWidgets.QMessageBox.Ok)
                    print(
                        "Not OK - The file \"" + self.selectedfpesfile +
                        "\" seems not to contain an FPES. Please open this file in SESToPy, make sure it represents an FPES and that the Selector in the Information ToolBox is set to flattened PES."
                    )
                    # if called from ui, the build model button needs to be activated again and clear the status
                    if self.calledFromUi:
                        self.bbuildmodel.setEnabled(True)
                        self.lstatustext.setText("")
            else:
                QMessageBox.information(
                    None,
                    "Selection missing or the path/filename contains whitespaces",
                    "Please select an FPES .jsonsestree file. In the name of the .jsonsestree file and the path to it no whitespaces are allowed!",
                    QtWidgets.QMessageBox.Ok)
                print("Not OK - Please select an FPES .jsonsestree file.")
                # if called from ui, the build model button needs to be activated again and clear the status
                if self.calledFromUi:
                    self.bbuildmodel.setEnabled(True)
                    self.lstatustext.setText("")

        except:
            QMessageBox.critical(
                None, "Can not read file",
                "The file \"%s\" could not be read or there was an unknown error."
                % self.selectedfpesfile, QtWidgets.QMessageBox.Ok)
            print("Not OK - The file \"" + self.selectedfpesfile +
                  "\" could not be read or there was an unknown error.")
            # if called from ui, the build model button needs to be activated again and clear the status
            if self.calledFromUi:
                self.bbuildmodel.setEnabled(True)
                self.lstatustext.setText("")

    """documentation"""

    def documentation(self):
        #QDesktopServices.openUrl(QUrl(self.programPath + "Documentation/Doc_LaTeX/doc.pdf"))
        if not QDesktopServices.openUrl(
                QUrl("file:Documentation/Doc_LaTeX/doc.pdf")):
            QDesktopServices.openUrl(
                QUrl("file:doc.pdf")
            )  #if the doc.pdf is in the main folder (e.g. after building executable)
Esempio n. 53
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. 54
0
class DAQ_PID(QObject):
    """
    """
    log_signal = pyqtSignal(str)
    #look for eventual model files
    command_pid = pyqtSignal(ThreadCommand)
    command_stage = pyqtSignal(ThreadCommand)
    move_done_signal = pyqtSignal(str, float)

    models = []
    try:
        model_mod = importlib.import_module('pymodaq_pid_models')
        for ind_file, entry in enumerate(os.scandir(os.path.join(model_mod.__path__[0], 'models'))):
            if not entry.is_dir() and entry.name != '__init__.py':
                try:
                    file, ext = os.path.splitext(entry.name)
                    importlib.import_module('.'+file, model_mod.__name__+'.models')

                    models.append(file)
                except Exception as e:
                    print(e)
        if 'PIDModelMock' in models:
            mods = models
            mods.pop(models.index('PIDModelMock'))
            models = ['PIDModelMock']
            models.extend(mods)

    except Exception as e:
        print(e)

    if len(models) == 0:
        logger.warning('No valid installed models')


    def __init__(self,area, detector_modules = [], actuator_modules =[]):
        QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates))
        super(DAQ_PID,self).__init__()

        self.settings = Parameter.create(title='PID settings', name='pid_settings', type='group', children=params)
        self.title = 'PyMoDAQ PID'
        self.Initialized_state = False
        self.model_class = None
        self.detector_modules = detector_modules
        self.actuator_modules = actuator_modules
        self.dock_area = area
        self.overshoot = None
        self.check_moving = False
        self.preset_manager = PresetManager()
        self.setupUI()
        self.command_stage.connect(self.move_Abs) #to be compatible with actuator modules within daq scan

        self.enable_controls_pid(False)


        self.enable_controls_pid_run(False)



    def ini_PID(self):

        if self.ini_PID_action.isChecked():
            output_limits =[None,None]
            if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value():
                output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value()
            if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value():
                output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value()


            self.PIDThread = QThread()
            pid_runner = PIDRunner(self.model_class,
                            [mod.move_done_signal for mod in self.actuator_modules],
                            [mod.grab_done_signal for mod in self.detector_modules],
                           [mod.command_stage for mod in self.actuator_modules],
                           [mod.command_detector for mod in self.detector_modules],
                            dict(Kp=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value(),
                            Ki=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value(),
                            Kd=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value(),
                            setpoint=self.settings.child('main_settings', 'pid_controls', 'set_point').value(),
                            sample_time=self.settings.child('main_settings', 'pid_controls', 'sample_time').value()/1000,
                            output_limits=output_limits,
                            auto_mode=False),
                            filter=dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(),
                                         value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value()),
                            det_averaging=[mod.settings.child('main_settings', 'Naverage').value() for mod in self.detector_modules],
                                   )

            self.PIDThread.pid_runner = pid_runner
            pid_runner.pid_output_signal.connect(self.process_output)
            pid_runner.status_sig.connect(self.thread_status)
            self.command_pid.connect(pid_runner.queue_command)

            pid_runner.moveToThread(self.PIDThread)

            self.PIDThread.start()
            self.pid_led.set_as_true()
            self.enable_controls_pid_run(True)



        else:
            if hasattr(self,'PIDThread'):
                if self.PIDThread.isRunning():
                    try:
                        self.PIDThread.quit()
                    except:
                        pass
            self.pid_led.set_as_false()
            self.enable_controls_pid_run(False)

        self.Initialized_state = True

    pyqtSlot(dict)
    def process_output(self, datas):
        self.output_viewer.show_data([[dat] for dat in datas['output']])
        self.input_viewer.show_data([[dat] for dat in datas['input']])
        self.currpoint_sb.setValue(np.mean(datas['input']))

        if self.check_moving:
            if np.abs(np.mean(datas['input'])-self.settings.child('main_settings', 'pid_controls', 'set_point').value()) < \
                    self.settings.child('main_settings', 'epsilon').value():
                self.move_done_signal.emit(self.title, np.mean(datas['input']))
                self.check_moving = False
                print('Move from {:s} is done: {:f}'.format('PID', np.mean(datas['input'])))


    @pyqtSlot(ThreadCommand)
    def move_Abs(self, command=ThreadCommand()):
        """
        """
        if command.command == "move_Abs":
            self.check_moving = True
            self.setpoint_sb.setValue(command.attributes[0])
            QtWidgets.QApplication.processEvents()



    def enable_controls_pid(self,enable = False):
        self.ini_PID_action.setEnabled(enable)
        self.setpoint_sb.setOpts(enabled = enable)

    def enable_controls_pid_run(self,enable = False):
        self.run_action.setEnabled(enable)
        self.pause_action.setEnabled(enable)


    def setupUI(self):

        self.dock_pid = Dock('PID controller', self.dock_area)
        self.dock_area.addDock(self.dock_pid)

        #%% create logger dock
        self.logger_dock=Dock("Logger")
        self.logger_list=QtWidgets.QListWidget()
        self.logger_list.setMinimumWidth(300)
        self.logger_dock.addWidget(self.logger_list)
        self.dock_area.addDock(self.logger_dock,'right')
        self.logger_dock.setVisible(True)





        widget = QtWidgets.QWidget()
        widget_toolbar = QtWidgets.QWidget()
        verlayout = QtWidgets.QVBoxLayout()
        widget.setLayout(verlayout)
        toolbar_layout = QtWidgets.QGridLayout()
        widget_toolbar.setLayout(toolbar_layout)


        iconquit = QtGui.QIcon()
        iconquit.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/close2.png"), QtGui.QIcon.Normal,
                          QtGui.QIcon.Off)
        self.quit_action = QtWidgets.QPushButton(iconquit, "Quit")
        self.quit_action.setToolTip('Quit the application')
        toolbar_layout.addWidget(self.quit_action,0,0,1,2)
        self.quit_action.clicked.connect(self.quit_fun)

        iconini = QtGui.QIcon()
        iconini.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/ini.png"), QtGui.QIcon.Normal,
                          QtGui.QIcon.Off)
        self.ini_model_action = QtWidgets.QPushButton(iconini, "Init Model")
        self.ini_model_action.setToolTip('Initialize the chosen model')
        toolbar_layout.addWidget(self.ini_model_action,2,0)
        self.ini_model_action.clicked.connect(self.ini_model)
        self.model_led = QLED()
        toolbar_layout.addWidget(self.model_led, 2,1)

        self.ini_PID_action = QtWidgets.QPushButton(iconini, "Init PID")
        self.ini_PID_action.setToolTip('Initialize the PID loop')
        toolbar_layout.addWidget(self.ini_PID_action,2,2)
        self.ini_PID_action.setCheckable(True)
        self.ini_PID_action.clicked.connect(self.ini_PID)
        self.pid_led = QLED()
        toolbar_layout.addWidget(self.pid_led, 2,3)

        self.iconrun = QtGui.QIcon()
        self.iconrun.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"), QtGui.QIcon.Normal,
                           QtGui.QIcon.Off)
        self.icon_stop = QtGui.QIcon()
        self.icon_stop.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/stop.png"))
        self.run_action = QtWidgets.QPushButton(self.iconrun, "", None)
        self.run_action.setToolTip('Start PID loop')
        self.run_action.setCheckable(True)
        toolbar_layout.addWidget(self.run_action,0,2)
        self.run_action.clicked.connect(self.run_PID)


        iconpause = QtGui.QIcon()
        iconpause.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/pause.png"), QtGui.QIcon.Normal,
                           QtGui.QIcon.Off)
        self.pause_action = QtWidgets.QPushButton(iconpause, "", None)
        self.pause_action.setToolTip('Pause PID')
        self.pause_action.setCheckable(True)
        toolbar_layout.addWidget(self.pause_action,0,3)
        self.pause_action.setChecked(True)
        self.pause_action.clicked.connect(self.pause_PID)

        lab = QtWidgets.QLabel('Set Point:')
        toolbar_layout.addWidget(lab, 3,0,1,2)

        self.setpoint_sb = custom_tree.SpinBoxCustom()
        self.setpoint_sb.setMinimumHeight(40)
        font = self.setpoint_sb.font()
        font.setPointSizeF(20)
        self.setpoint_sb.setFont(font)
        self.setpoint_sb.setDecimals(6)
        toolbar_layout.addWidget(self.setpoint_sb,3,2,1,2)
        self.setpoint_sb.valueChanged.connect(self.settings.child('main_settings', 'pid_controls', 'set_point').setValue)

        lab1 = QtWidgets.QLabel('Current Point:')
        toolbar_layout.addWidget(lab1, 4,0,1,2)

        self.currpoint_sb = custom_tree.SpinBoxCustom()
        self.currpoint_sb.setMinimumHeight(40)
        self.currpoint_sb.setReadOnly(True)
        self.currpoint_sb.setDecimals(6)
        self.currpoint_sb.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        font = self.currpoint_sb.font()
        font.setPointSizeF(20)
        self.currpoint_sb.setFont(font)
        toolbar_layout.addWidget(self.currpoint_sb,4,2,1,2)


        #create main parameter tree
        self.settings_tree = ParameterTree()
        self.settings_tree.setParameters(self.settings, showTop=False)

        verlayout.addWidget(widget_toolbar)
        verlayout.addWidget(self.settings_tree)


        self.dock_output = Dock('PID output')
        widget_output = QtWidgets.QWidget()
        self.output_viewer = Viewer0D(widget_output)
        self.dock_output.addWidget(widget_output)
        self.dock_area.addDock(self.dock_output, 'right')

        self.dock_input = Dock('PID input')
        widget_input = QtWidgets.QWidget()
        self.input_viewer = Viewer0D(widget_input)
        self.dock_input.addWidget(widget_input)
        self.dock_area.addDock(self.dock_input, 'bottom',self.dock_output)

        if len(self.models) != 0:
            self.get_set_model_params(self.models[0])


        #connecting from tree
        self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)#any changes on the settings will update accordingly the detector
        self.dock_pid.addWidget(widget)

    def get_set_model_params(self, model_file):
        self.settings.child('models', 'model_params').clearChildren()
        model = importlib.import_module('.' + model_file, self.model_mod.__name__+'.models')
        model_class = getattr(model, model_file)
        params = getattr(model_class, 'params')
        self.settings.child('models', 'model_params').addChildren(params)

    def run_PID(self):
        if self.run_action.isChecked():
            self.run_action.setIcon(self.icon_stop)
            self.command_pid.emit(ThreadCommand('start_PID', [self.model_class.curr_input]))
            QtWidgets.QApplication.processEvents()

            QtWidgets.QApplication.processEvents()

            self.command_pid.emit(ThreadCommand('run_PID', [self.model_class.curr_output]))
        else:
            self.run_action.setIcon(self.iconrun)
            self.command_pid.emit(ThreadCommand('stop_PID'))

            QtWidgets.QApplication.processEvents()

    def pause_PID(self):
        self.command_pid.emit(ThreadCommand('pause_PID', [self.pause_action.isChecked()]))

    def update_status(self,txt,log_type=None):
        """
            Show the txt message in the status bar with a delay of wait_time ms.

            =============== =========== =======================
            **Parameters**    **Type**    **Description**
            *txt*             string      The message to show
            *wait_time*       int         the delay of showing
            *log_type*        string      the type of the log
            =============== =========== =======================
        """
        try:
            if log_type is not None:
                self.log_signal.emit(txt)
                logging.info(txt)
        except Exception as e:
            pass

    @pyqtSlot(str)
    def add_log(self,txt):
        """
            Add the QListWisgetItem initialized with txt informations to the User Interface logger_list and to the save_parameters.logger array.

            =============== =========== ======================
            **Parameters**    **Type**   **Description**
            *txt*             string     the log info to add.
            =============== =========== ======================
        """
        try:
            now=datetime.datetime.now()
            new_item=QtWidgets.QListWidgetItem(now.strftime('%Y/%m/%d %H:%M:%S')+": "+txt)
            self.logger_list.addItem(new_item)
        except:
            pass

    def set_file_preset(self,model):
        """
            Set a file managers from the converted xml file given by the filename parameter.


            =============== =========== ===================================================
            **Parameters**    **Type**    **Description**
            *filename*        string      the name of the xml file to be converted/treated
            =============== =========== ===================================================

            Returns
            -------
            (Object list, Object list) tuple
                The updated (Move modules list, Detector modules list).

            See Also
            --------
            custom_tree.XML_file_to_parameter, set_param_from_param, stop_moves, update_status,DAQ_Move_main.daq_move, DAQ_viewer_main.daq_viewer
        """

        filename = os.path.join(get_set_pid_path(), model + '.xml')
        self.preset_file = filename
        self.preset_manager.set_file_preset(filename, show=False)
        self.move_docks = []
        self.det_docks_settings = []
        self.det_docks_viewer = []
        move_forms = []
        actuator_modules = []
        detector_modules = []
        move_types = []


        #################################################################
        ###### sort plugins by IDs and within the same IDs by Master and Slave status
        plugins=[{'type': 'move', 'value': child} for child in self.preset_manager.preset_params.child(('Moves')).children()]+[{'type': 'det', 'value': child} for child in self.preset_manager.preset_params.child(('Detectors')).children()]

        for plug in plugins:
            plug['ID']=plug['value'].child('params','main_settings','controller_ID').value()
            if plug["type"]=='det':
                plug['status']=plug['value'].child('params','detector_settings','controller_status').value()
            else:
                plug['status']=plug['value'].child('params','move_settings', 'multiaxes', 'multi_status').value()

        IDs=list(set([plug['ID'] for plug in plugins]))
        #%%
        plugins_sorted=[]
        for id in IDs:
            plug_Ids=[]
            for plug in plugins:
                if plug['ID']==id:
                    plug_Ids.append(plug)
            plug_Ids.sort(key=lambda status: status['status'])
            plugins_sorted.append(plug_Ids)
        #################################################################
        #######################

        ind_move=-1
        ind_det=-1
        for plug_IDs in plugins_sorted:
            for ind_plugin, plugin in enumerate(plug_IDs):


                plug_name=plugin['value'].child(('name')).value()
                plug_init=plugin['value'].child(('init')).value()
                plug_settings=plugin['value'].child(('params'))

                if plugin['type'] == 'move':
                    ind_move+=1
                    plug_type=plug_settings.child('main_settings','move_type').value()
                    self.move_docks.append(Dock(plug_name, size=(150,250)))
                    if ind_move==0:
                        self.dock_area.addDock(self.move_docks[-1], 'top',self.logger_dock)
                    else:
                        self.dock_area.addDock(self.move_docks[-1], 'above',self.move_docks[-2])
                    move_forms.append(QtWidgets.QWidget())
                    mov_mod_tmp=DAQ_Move(move_forms[-1],plug_name)

                    mov_mod_tmp.ui.Stage_type_combo.setCurrentText(plug_type)
                    mov_mod_tmp.ui.Quit_pb.setEnabled(False)
                    QtWidgets.QApplication.processEvents()

                    set_param_from_param(mov_mod_tmp.settings,plug_settings)
                    QtWidgets.QApplication.processEvents()

                    mov_mod_tmp.bounds_signal[bool].connect(self.stop_moves)
                    self.move_docks[-1].addWidget(move_forms[-1])
                    actuator_modules.append(mov_mod_tmp)

                    try:
                        if ind_plugin==0: #should be a master type plugin
                            if plugin['status']!="Master":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                actuator_modules[-1].ui.IniStage_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_type:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                                master_controller=actuator_modules[-1].controller
                        else:
                            if plugin['status']!="Slave":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                actuator_modules[-1].controller=master_controller
                                actuator_modules[-1].ui.IniStage_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_type:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                    except Exception as e:
                        self.update_status(getLineInfo()+ str(e),'log')


                else:
                    ind_det+=1
                    plug_type=plug_settings.child('main_settings','DAQ_type').value()
                    plug_subtype=plug_settings.child('main_settings','detector_type').value()

                    self.det_docks_settings.append(Dock(plug_name+" settings", size=(150,250)))
                    self.det_docks_viewer.append(Dock(plug_name+" viewer", size=(350,350)))

                    if ind_det==0:
                        self.logger_dock.area.addDock(self.det_docks_settings[-1], 'bottom', self.dock_input) #dock_area of the logger dock
                    else:
                        self.dock_area.addDock(self.det_docks_settings[-1], 'bottom',self.det_docks_settings[-2])
                    self.dock_area.addDock(self.det_docks_viewer[-1],'right',self.det_docks_settings[-1])

                    det_mod_tmp=DAQ_Viewer(self.dock_area,dock_settings=self.det_docks_settings[-1],
                                                        dock_viewer=self.det_docks_viewer[-1],title=plug_name,
                                           DAQ_type=plug_type, parent_scan=self)
                    detector_modules.append(det_mod_tmp)
                    detector_modules[-1].ui.Detector_type_combo.setCurrentText(plug_subtype)
                    detector_modules[-1].ui.Quit_pb.setEnabled(False)
                    set_param_from_param(det_mod_tmp.settings,plug_settings)
                    QtWidgets.QApplication.processEvents()


                    try:
                        if ind_plugin==0: #should be a master type plugin
                            if plugin['status']!="Master":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                detector_modules[-1].ui.IniDet_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_subtype:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                                master_controller=detector_modules[-1].controller
                        else:
                            if plugin['status']!="Slave":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                detector_modules[-1].controller=master_controller
                                detector_modules[-1].ui.IniDet_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_subtype:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                    except Exception as e:
                        self.update_status(getLineInfo()+ str(e),'log')

                    detector_modules[-1].settings.child('main_settings','overshoot').show()
                    detector_modules[-1].overshoot_signal[bool].connect(self.stop_moves)

        QtWidgets.QApplication.processEvents()

        return actuator_modules,detector_modules


    pyqtSlot(bool)
    def stop_moves(self,overshoot):
        """
            Foreach module of the move module object list, stop motion.

            See Also
            --------
            stop_scan,  DAQ_Move_main.daq_move.stop_Motion
        """
        self.overshoot = overshoot
        for mod in self.actuator_modules:
            mod.stop_Motion()

    def set_default_preset(self):
        actuators = self.model_class.actuators
        actuator_names = self.model_class.actuators_name

        detectors_type = self.model_class.detectors_type
        detectors = self.model_class.detectors
        detectors_name = self.model_class.detectors_name

        detector_modules = []
        for ind_det, det in enumerate(detectors):

            detector_modules.append(DAQ_Viewer(area, title=detectors_name[ind_det], DAQ_type=detectors_type[ind_det]))
            #self.detector_modules[-1].ui.IniDet_pb.click()
            QtWidgets.QApplication.processEvents()
            detector_modules[-1].ui.Detector_type_combo.setCurrentText(detectors[ind_det])
            detector_modules[-1].ui.Quit_pb.setEnabled(False)


        self.dock_area.addDock(self.dock_output, 'bottom')
        self.dock_area.moveDock(self.dock_input, 'bottom', self.dock_output)
        self.dock_area.addDock(self.dock_pid, 'left')

        dock_moves = []
        actuator_modules = []
        for ind_act, act in enumerate(actuators):
            form = QtWidgets.QWidget()
            dock_moves.append(Dock(actuator_names[ind_act]))
            area.addDock(dock_moves[-1], 'bottom', self.dock_pid)
            dock_moves[-1].addWidget(form)
            actuator_modules.append(DAQ_Move(form))
            QtWidgets.QApplication.processEvents()
            actuator_modules[-1].ui.Stage_type_combo.setCurrentText(actuators[ind_act])
            actuator_modules[-1].ui.Quit_pb.setEnabled(False)
            #self.actuator_modules[-1].ui.IniStage_pb.click()
            #QThread.msleep(1000)
            QtWidgets.QApplication.processEvents()

        return actuator_modules, detector_modules

    def ini_model(self):
        try:
            model_name = self.settings.child('models', 'model_class').value()
            model = importlib.import_module('.' +model_name, self.model_mod.__name__+'.models')
            self.model_class = getattr(model, model_name)(self)


            #try to get corresponding managers file
            filename = os.path.join(get_set_pid_path(), model_name + '.xml')
            if os.path.isfile(filename):
                self.actuator_modules,  self.detector_modules = self.set_file_preset(model_name)
            else:
                self.actuator_modules, self.detector_modules = self.set_default_preset()

            # # connecting to logger
            # for mov in self.actuator_modules:
            #     mov.log_signal[str].connect(self.add_log)
            # for det in self.detector_modules:
            #     det.log_signal[str].connect(self.add_log)
            # self.log_signal[str].connect(self.add_log)

            self.model_class.ini_model()

            self.enable_controls_pid(True)
            self.model_led.set_as_true()
            self.ini_model_action.setEnabled(False)



        except Exception as e:
            self.update_status(getLineInfo() + str(e), log_type='log')

    def quit_fun(self):
        """
        """
        try:
            try:
                self.PIDThread.exit()
            except Exception as e:
                print(e)

            for module in self.actuator_modules:
                try:
                    module.quit_fun()
                    QtWidgets.QApplication.processEvents()
                    QThread.msleep(1000)
                    QtWidgets.QApplication.processEvents()
                except Exception as e:
                    print(e)

            for module in self.detector_modules:
                try:
                    module.stop_all()
                    QtWidgets.QApplication.processEvents()
                    module.quit_fun()
                    QtWidgets.QApplication.processEvents()
                    QThread.msleep(1000)
                    QtWidgets.QApplication.processEvents()
                except Exception as e:
                    print(e)

            areas=self.dock_area.tempAreas[:]
            for area in areas:
                area.win.close()
                QtWidgets.QApplication.processEvents()
                QThread.msleep(1000)
                QtWidgets.QApplication.processEvents()

            self.dock_area.parent().close()

        except Exception as e:
            print(e)


    def parameter_tree_changed(self,param,changes):
        """
            Foreach value changed, update :
                * Viewer in case of **DAQ_type** parameter name
                * visibility of button in case of **show_averaging** parameter name
                * visibility of naverage in case of **live_averaging** parameter name
                * scale of axis **else** (in 2D pymodaq type)

            Once done emit the update settings signal to link the commit.

            =============== =================================== ================================================================
            **Parameters**    **Type**                           **Description**
            *param*           instance of ppyqtgraph parameter   the parameter to be checked
            *changes*         tuple list                         Contain the (param,changes,info) list listing the changes made
            =============== =================================== ================================================================

            See Also
            --------
            change_viewer, daq_utils.custom_parameter_tree.iter_children
        """

        for param, change, data in changes:
            path = self.settings.childPath(param)
            if change == 'childAdded':
                pass

            elif change == 'value':
                if param.name() == 'model_class':
                    self.get_set_model_params(param.value())

                elif param.name() == 'module_settings':
                    if param.value():
                        self.settings.sigTreeStateChanged.disconnect( self.parameter_tree_changed)
                        param.setValue(False)
                        self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed)
                        self.preset_manager.set_PID_preset(self.settings.child('models','model_class').value())

                elif param.name() == 'refresh_plot_time' or param.name() == 'timeout':
                    self.command_pid.emit(ThreadCommand('update_timer', [param.name(),param.value()]))

                elif param.name() == 'set_point':
                    if self.pid_led.state:
                        self.command_pid.emit(ThreadCommand('update_options', dict(setpoint=param.value())))
                    else:
                        output = self.model_class.convert_output(param.value(),0, stab=False)
                        for ind_act, act in enumerate(self.actuator_modules):
                            act.move_Abs(output[ind_act])



                elif param.name() == 'sample_time':
                    self.command_pid.emit(ThreadCommand('update_options', dict(sample_time=param.value())))

                elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'output_limits'), []):
                    output_limits = [None, None]
                    if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value():
                        output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value()
                    if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value():
                        output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value()

                    self.command_pid.emit(ThreadCommand('update_options', dict(output_limits=output_limits)))

                elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'filter'), []):
                    self.command_pid.emit(ThreadCommand('update_filter',
                                    [dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(),
                                         value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value())]))

                elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'pid_constants'), []):
                    Kp = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value()
                    Ki = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value()
                    Kd = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value()
                    self.command_pid.emit(ThreadCommand('update_options', dict(tunings= (Kp, Ki, Kd))))

                elif param.name() in custom_tree.iter_children(self.settings.child('models', 'model_params'),[]):
                    self.model_class.update_settings(param)

                elif param.name() == 'detector_modules':
                    self.model_class.update_detector_names()

            elif change == 'parent':
                pass

    @pyqtSlot(list)
    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 main.
            |

            Switch the status with :
                * *"Update status"* : Update the status bar with the status attribute txt message

        """
        if status[0]=="Update_Status":
            self.update_status(status[1],log_type=status[2])
Esempio n. 55
0
class Ui_Window(QtWidgets.QDialog, Ui_Dialog):
    def __init__(self, parent=None):
        super(Ui_Window, self).__init__(parent=parent)
        self.setupUi(self)

        font = QtGui.QFont()
        font.setFamily("微软雅黑")
        font.setPointSize(9)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        self.buttonBox.button(QDialogButtonBox.Ok).setFont(font)
        self.buttonBox.button(QDialogButtonBox.Ok).setText("确定")
        self.buttonBox.button(QDialogButtonBox.Cancel).setFont(font)
        self.buttonBox.button(QDialogButtonBox.Cancel).setText("取消")

        vlayout = QtWidgets.QVBoxLayout(self)
        vlayout.addWidget(self.splitter)
        vlayout.setContentsMargins(0, 0, 10, 10)
        self.splitter.setGeometry(0, 0, self.width(), self.height())

        self.splitter.setOrientation(Qt.Horizontal)
        self.splitter.setProperty("Stretch", SplitterState.collapsed)
        self.splitter.setProperty("Dock", Dock.right)
        self.splitter.setProperty("WidgetToHide", self.txt_log)
        self.splitter.setProperty("ExpandParentForm", True)

        self.splitter.setSizes([600, self.splitter.width() - 590])
        self.resize(self.splitter.width(), self.splitter.height())

        self.splitter.splitterMoved.connect(self.splitterMoved)
        self.splitter.handle(1).handleClicked.connect(self.handleClicked)

        self.btn_addRow.clicked.connect(self.btn_addRow_Clicked)
        self.btn_removeRow.clicked.connect(self.btn_removeBtn_clicked)

        self.rbtn_onlySpider.clicked.connect(self.rbtn_toggled)
        self.rbtn_onlyHandle.clicked.connect(self.rbtn_toggled)
        self.rbtn_spiderAndHandle.clicked.connect(self.rbtn_toggled)
        self.tbl_address.verticalHeader().sectionClicked.connect(
            self.table_section_clicked)
        self.btn_obtainMeta.clicked.connect(self.btn_obtainMeta_clicked)
        self.buttonBox.clicked.connect(self.buttonBox_clicked)
        self.btn_saveMetaFile.clicked.connect(self.btn_saveMetaFile_clicked)

        self.btn_tileInfoDialog.clicked.connect(self.open_tileInfoFile)
        self.btn_addressFile.clicked.connect(self.open_addressFile)

        self.txt_originX.editingFinished.connect(self.txt_originX_edited)
        self.txt_originY.editingFinished.connect(self.txt_originY_edited)
        self.txt_xmin.editingFinished.connect(self.txt_xmin_edited)
        self.txt_xmax.editingFinished.connect(self.txt_xmax_edited)
        self.txt_ymin.editingFinished.connect(self.txt_ymin_edited)
        self.txt_ymax.editingFinished.connect(self.txt_ymax_edited)
        self.txt_resolution.editingFinished.connect(self.txt_resolution_edited)
        self.txt_tilesize.editingFinished.connect(self.txt_tilesize_edited)

        self.tbl_address.clicked.connect(self.table_index_clicked)

        self.validateValue()

        self.paras = {}  # 存储参数信息
        self.selIndex = QModelIndex()
        self.table_init()
        log.setLogViewer(parent=self, logViewer=self.txt_log)
        self.txt_log.setReadOnly(True)

        #  最后运算过程放至到另一个线程避免GUI卡住
        self.thread = QThread(self)
        self.crawlTilesThread = crawlTilesWorker()
        self.crawlTilesThread.moveToThread(self.thread)
        self.crawlTilesThread.crawl.connect(self.crawlTilesThread.crawlTiles)
        self.crawlTilesThread.crawlAndMerge.connect(
            self.crawlTilesThread.crawlAndMergeTiles)
        self.crawlTilesThread.finished.connect(self.threadStop)
        self.crawlTilesThread.merge.connect(self.crawlTilesThread.mergeTiles)

    def update_para_value(self, key, editor, bSel=True):
        value = editor.text()
        if bSel and self.selIndex.row() < 0:
            return
        if self.rbtn_onlyHandle.isChecked():
            tileFolder_index = self.tbl_address.model().index(
                self.selIndex.row(), 0)
            tileFolder = self.tbl_address.model().data(tileFolder_index,
                                                       Qt.DisplayRole)
            imageFile_index = self.tbl_address.model().index(
                self.selIndex.row(), 1)
            imageFile = self.tbl_address.model().data(imageFile_index,
                                                      Qt.DisplayRole)
            para_key = tileFolder + "_" + imageFile
            #  以输入文件夹和输出文件作为关键字
            if para_key in self.paras:
                self.paras[para_key][key] = value
            else:
                self.paras[para_key] = {key: value}
        else:
            url_index, level_index, url, level = self.return_url_and_level(
                self.selIndex.row())
            if url in self.paras:
                if level in self.paras[url]['paras']:
                    self.paras[url]['paras'][level][key] = value
        editor.home(False)

    def update_all_paras_value(self, oldValue, newValue, url, level):
        print(oldValue)
        if self.rbtn_onlyHandle.isChecked():
            del self.paras[oldValue]
            self.paras[newValue] = self.update_para_dict()

    @Slot()
    def txt_originX_edited(self):
        self.update_para_value("origin_x", self.txt_originX)

    @Slot()
    def txt_originY_edited(self):
        self.update_para_value("origin_y", self.txt_originY)

    @Slot()
    def txt_xmin_edited(self):
        self.update_para_value("xmin", self.txt_xmin)

    @Slot()
    def txt_xmax_edited(self):
        self.update_para_value("xmax", self.txt_xmax)

    @Slot()
    def txt_ymin_edited(self):
        self.update_para_value("ymin", self.txt_ymin)

    @Slot()
    def txt_ymax_edited(self):
        self.update_para_value("ymax", self.txt_ymax)

    @Slot()
    def txt_resolution_edited(self):
        self.update_para_value("resolution", self.txt_resolution)

    @Slot()
    def txt_tilesize_edited(self):
        self.update_para_value("tilesize", self.txt_tilesize)

    def row_clicked(self, logicRow):
        if self.rbtn_spiderAndHandle.isChecked(
        ) or self.rbtn_onlySpider.isChecked():
            level_index = self.tbl_address.model().index(
                logicRow, self.level_no, QModelIndex())
            level = self.tbl_address.model().data(level_index, Qt.DisplayRole)
            self.update_txt_info(level_index, level)

            self.selIndex = level_index
        elif self.rbtn_onlyHandle.isChecked():
            sel_index = self.tbl_address.model().index(logicRow, 0,
                                                       QModelIndex())
            self.update_txt_info(sel_index)
            self.selIndex = sel_index

    @Slot(int)
    def table_section_clicked(self, section):
        self.row_clicked(section)

    @Slot(QModelIndex)
    def table_index_clicked(self, index):
        self.row_clicked(index.row())

    @Slot()
    def btn_saveMetaFile_clicked(self):
        datas = self.tbl_address.model().datas
        # selModel = self.tbl_address.selectionModel()
        # if selModel is None:
        #     return
        #
        # selRow = -1
        # paras_dict = {}
        # if self.selIndex is not None:
        #     selRow = self.selIndex.row()
        #     paras_dict = self.update_para_dict()

        bHasData = False
        for i in datas:
            for j in i:
                if j != '':
                    bHasData = True
                    break
            else:
                continue
            break

        rows = range(0, len(datas))
        if self.rbtn_spiderAndHandle.isChecked(
        ) or self.rbtn_onlySpider.isChecked():
            if bHasData and bool(self.paras):
                fileName, fileType = QFileDialog.getSaveFileName(
                    self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)")
                # rows = range(0, self.tbl_address.model().rowCount())
                for v in self.paras.values():
                    v['exports'] = []

                for row in rows:
                    url = datas[row][self.url_no]

                    if url in self.paras.keys():
                        level = datas[row][self.level_no]

                        if level not in self.paras[url]['paras']:
                            level = -1

                        # if row == selRow:
                        #     self.paras[url]['paras'][level] = paras_dict

                        if self.rbtn_spiderAndHandle.isChecked():
                            self.paras[url]['exports'].append({
                                'level':
                                level,
                                'tileFolder':
                                datas[row][2],
                                'imageFile':
                                datas[row][3]
                            })
                        elif self.rbtn_onlySpider.isChecked():
                            self.paras[url]['exports'].append({
                                'level':
                                level,
                                'tileFolder':
                                datas[row][2],
                                'imageFile':
                                ''
                            })
        elif self.rbtn_onlyHandle.isChecked():
            if bHasData and bool(self.paras):
                fileName, fileType = QFileDialog.getSaveFileName(
                    self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)")
            for row in rows:
                tileFolder = datas[row][0]
                imageFile = datas[row][1]
                key = tileFolder + "_" + imageFile

                if key in self.paras:
                    self.paras[key]['tileFolder'] = tileFolder
                    self.paras[key]['imageFile'] = imageFile
                else:
                    self.paras[key] = self.update_para_dict()
                    self.paras[key]['tileFolder'] = tileFolder
                    self.paras[key]['imageFile'] = imageFile
        try:
            if fileName != '':
                with open(fileName, 'w', encoding='UTF-8') as f:
                    json.dump(self.paras, f, ensure_ascii=False)
        except:
            log.error("文件存储路径错误,无法保存!", parent=self, dialog=True)

    @Slot(QAbstractButton)
    def buttonBox_clicked(self, button: QAbstractButton):
        if button == self.buttonBox.button(QDialogButtonBox.Ok):
            if not self.check_paras():
                return

            self.thread.start()
            self.run_process()
        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
            self.close()

    def run_process(self):
        rows = range(0, self.tbl_address.model().rowCount(QModelIndex()))
        for row in rows:
            url_index, level_index, url, level = self.return_url_and_level(row)

            if self.rbtn_spiderAndHandle.isChecked():
                paras = self.paras[url]['paras'][level]
                tileFolder_index = self.tbl_address.model().index(
                    row, 2, QModelIndex())
                imgFile_index = self.tbl_address.model().index(
                    row, 3, QModelIndex())
                tileFolder = str(self.tbl_address.model().data(
                    tileFolder_index, Qt.DisplayRole)).strip()
                imgFile = str(self.tbl_address.model().data(
                    imgFile_index, Qt.DisplayRole)).strip()

                if tileFolder == "":
                    tileFolder = defaultTileFolder(url, level)
                    log.warning('第{}行参数缺失非必要参数"瓦片文件夹",将使用默认值"{}".'.format(
                        row + 1, tileFolder))
                else:
                    url_encodeStr = urlEncodeToFileName(url)
                    tileFolder = os.path.join(tileFolder, url_encodeStr,
                                              str(level))
                    if not os.path.exists(tileFolder):
                        os.makedirs(tileFolder)

                if imgFile == "":
                    imgFile = defaultImageFile(url, level)
                    log.warning('第{}行参数缺失非必要参数"输出影像文件",将使用默认值"{}".'.format(
                        row + 1, imgFile))

                self.crawlTilesThread.crawlAndMerge.emit(
                    url, int(level), int(paras['origin_x']),
                    int(paras['origin_y']), float(paras['xmin']),
                    float(paras['xmax']), float(paras['ymin']),
                    float(paras['ymax']), float(paras['resolution']),
                    int(paras['tilesize']), tileFolder, imgFile)
            elif self.rbtn_onlySpider.isChecked():
                paras = self.paras[url]['paras'][level]
                tileFolder_index = self.tbl_address.model().index(
                    row, 2, QModelIndex())
                tileFolder = str(self.tbl_address.model().data(
                    tileFolder_index, Qt.DisplayRole)).strip()
                if tileFolder == "":
                    tileFolder = defaultTileFolder(url, level)
                    log.warning('第{}行参数缺失非必要参数"瓦片文件夹",将使用默认值"{}".'.format(
                        row + 1, tileFolder))
                else:
                    url_encodeStr = urlEncodeToFileName(url)
                    tileFolder = os.path.join(tileFolder, url_encodeStr,
                                              str(level))
                    if not os.path.exists(tileFolder):
                        os.makedirs(tileFolder)

                self.crawlTilesThread.crawl.emit(url, int(level),
                                                 int(paras['origin_x']),
                                                 int(paras['origin_y']),
                                                 float(paras['xmin']),
                                                 float(paras['xmax']),
                                                 float(paras['ymin']),
                                                 float(paras['ymax']),
                                                 float(paras['resolution']),
                                                 int(paras['tilesize']),
                                                 tileFolder)
            elif self.rbtn_onlyHandle.isChecked():
                key = url + "_" + level
                paras = self.paras[key]
                imgFile_index = self.tbl_address.model().index(
                    row, 1, QModelIndex())
                imgFile = str(self.tbl_address.model().data(
                    imgFile_index, Qt.DisplayRole)).strip()
                if imgFile == "":
                    imgFile = defaultImageFile(url, level)
                    log.warning('第{}行参数缺失非必要参数"输出影像文件",将使用默认值"{}".'.format(
                        row + 1, imgFile))

                self.crawlTilesThread.merge.emit(url, int(paras['origin_x']),
                                                 int(paras['origin_y']),
                                                 float(paras['xmin']),
                                                 float(paras['xmax']),
                                                 float(paras['ymin']),
                                                 float(paras['ymax']),
                                                 float(paras['resolution']),
                                                 int(paras['tilesize']),
                                                 imgFile)

    def check_paras(self):
        rows = range(0, self.tbl_address.model().rowCount(QModelIndex()))
        for row in rows:
            url_index, level_index, url, level = self.return_url_and_level(row)

            if self.rbtn_spiderAndHandle.isChecked(
            ) or self.rbtn_onlySpider.isChecked():
                if url == "":
                    log.error('第{}行缺失必要参数"地址",请补全!'.format(row), dialog=True)
                    return False
                if level == "":
                    log.error('第{}行缺失必要参数"等级",请补全!'.format(row), dialog=True)
                    return False

            elif self.rbtn_onlyHandle.isChecked():
                if url == "":
                    log.error('第{}行缺失必要参数"瓦片文件夹",请补全!'.format(row),
                              dialog=True)
                    return False
        return True

    def threadStop(self):
        self.thread.quit()

    def table_init(self):
        self.tbl_address.setStyle(mTableStyle())

        self.tbl_address.horizontalHeader().setStretchLastSection(True)
        self.tbl_address.verticalHeader().setDefaultSectionSize(20)
        self.tbl_address.verticalHeader().setSectionResizeMode(
            QHeaderView.Fixed)  # 行高固定

        color = self.palette().color(QPalette.Button)
        self.tbl_address.horizontalHeader().setStyleSheet(
            "QHeaderView::section {{ background-color: {}}}".format(
                color.name()))
        self.tbl_address.verticalHeader().setStyleSheet(
            "QHeaderView::section {{ background-color: {}}}".format(
                color.name()))
        self.tbl_address.setStyleSheet(
            "QTableCornerButton::section {{ color: {}; border: 1px solid; border-color: {}}}"
            .format(color.name(), color.name()))

        self.tbl_address.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.tbl_address.setEditTriggers(QAbstractItemView.SelectedClicked
                                         | QAbstractItemView.DoubleClicked)
        self.tbl_address.DragDropMode(QAbstractItemView.InternalMove)
        self.tbl_address.setSelectionBehavior(QAbstractItemView.SelectRows
                                              | QAbstractItemView.SelectItems)
        self.tbl_address.setDefaultDropAction(Qt.MoveAction)

        self.tbl_address.horizontalHeader().setSectionsMovable(False)
        self.tbl_address.setDragEnabled(True)
        self.tbl_address.setAcceptDrops(True)

        self.level_no = 1  # 等级字段的序号
        self.url_no = 0  # url地址的序号

        # self.rbtn_spiderAndHandle.setChecked(True)

    def showEvent(self, a0: QtGui.QShowEvent) -> None:
        log.setLogViewer(parent=self, logViewer=self.txt_log)
        self.rbtn_spiderAndHandle.click()

    def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
        self.table_layout()

    def splitterMoved(self):
        self.table_layout()

    def handleClicked(self):
        self.table_layout()

    def table_layout(self):
        if self.rbtn_onlyHandle.isChecked():
            self.tbl_address.setColumnWidth(0, self.tbl_address.width() / 2)
            self.tbl_address.setColumnWidth(1, self.tbl_address.width() / 2)
        elif self.rbtn_onlySpider.isChecked():
            self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4)
            self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2)
            self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.4)
        elif self.rbtn_spiderAndHandle.isChecked():
            self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4)
            self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2)
            self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.2)
            self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2)

    def return_url_and_level(self, logicRow):
        url_index = self.tbl_address.model().index(logicRow, self.url_no)
        level_index = self.tbl_address.model().index(logicRow, self.level_no)
        url = self.tbl_address.model().data(url_index, Qt.DisplayRole)
        level = self.tbl_address.model().data(level_index, Qt.DisplayRole)

        return url_index, level_index, url, level

    @Slot()
    def btn_addRow_Clicked(self):
        selModel = self.tbl_address.selectionModel()
        if selModel is None:
            return
        if len(selModel.selectedIndexes()) == 0:
            self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1, 0)
            next_index = self.model.index(
                self.model.rowCount(QModelIndex()) - 1, 0)
        elif len(selModel.selectedRows()) == 1:
            next_row = selModel.selectedRows()[0].row() + 1
            self.model.addEmptyRow(next_row, 1, 0)
            next_index = self.model.index(next_row, 0)

        selModel.select(
            next_index,
            QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
        self.tbl_address.setFocus()

    def btn_removeBtn_clicked(self):
        index_list = []
        selModel = self.tbl_address.selectionModel()
        if selModel is None:
            return
        if len(selModel.selectedRows()) < 1:
            return
        for model_index in selModel.selectedRows():
            index = QPersistentModelIndex(model_index)
            index_list.append(index)

        oldrow = index.row()
        for index in index_list:
            self.model.removeRows(index.row(), 1, 0)

        if self.model.rowCount(QModelIndex()) == 1:
            next_index = self.model.index(0, 0)
        else:
            next_index = self.model.index(oldrow, 0)
        # next_index = self.model.index(self.model.rowCount(QModelIndex()) - 1, 0)
        selModel.select(
            next_index,
            QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
        self.tbl_address.setFocus()

    def validateValue(self):
        doubleValidator = QDoubleValidator()
        doubleValidator.setNotation(QDoubleValidator.StandardNotation)
        self.txt_originX.setValidator(doubleValidator)
        self.txt_originY.setValidator(doubleValidator)
        self.txt_xmin.setValidator(doubleValidator)
        self.txt_xmax.setValidator(doubleValidator)
        self.txt_ymin.setValidator(doubleValidator)
        self.txt_ymax.setValidator(doubleValidator)
        self.txt_tilesize.setValidator(doubleValidator)
        self.txt_resolution.setValidator(doubleValidator)

        integerValidator = QIntValidator(0, 99)
        self.txt_level.setValidator(integerValidator)

    def rbtn_toggled(self, btn):
        self.model = TableModel()

        self.txt_originX.setText("")
        self.txt_originY.setText("")
        self.txt_xmin.setText("")
        self.txt_xmax.setText("")
        self.txt_ymin.setText("")
        self.txt_ymax.setText("")
        self.txt_tilesize.setText("")
        self.txt_resolution.setText("")
        self.txt_level.setText("")

        if self.rbtn_onlyHandle.isChecked():
            self.table_init_onlyHandle()
        else:
            self.txt_addressFile.setEnabled(True)
            self.btn_addressFile.setEnabled(True)

            self.txt_tileInfoFile.setEnabled(False)
            self.btn_tileInfoDialog.setEnabled(False)
            self.txt_tileInfoFile.clear()

            self.txt_originX.setEnabled(False)
            self.txt_originY.setEnabled(False)
            self.txt_resolution.setEnabled(False)
            self.txt_tilesize.setEnabled(False)

            self.txt_level.setEnabled(False)
            self.btn_obtainMeta.setEnabled(True)

            self.model.setHeaderData(0, Qt.Horizontal, "地址", Qt.DisplayRole)

            if self.rbtn_onlySpider.isChecked():
                self.table_init_onlySpider()
            elif self.rbtn_spiderAndHandle.isChecked():
                self.table_init_spiderAndHandle()

    def table_init_onlyHandle(self):
        self.txt_addressFile.setEnabled(False)
        self.btn_addressFile.setEnabled(False)
        self.txt_addressFile.clear()

        self.txt_tileInfoFile.setEnabled(True)
        self.btn_tileInfoDialog.setEnabled(True)

        self.txt_originX.setEnabled(True)
        self.txt_originY.setEnabled(True)
        self.txt_resolution.setEnabled(True)
        self.txt_tilesize.setEnabled(True)

        self.txt_level.setEnabled(False)
        self.btn_obtainMeta.setEnabled(False)

        self.model.setHeaderData(0, Qt.Horizontal, "输入瓦片文件夹", Qt.DisplayRole)
        # self.model.setHeaderData(1, Qt.Horizontal, "参数文件", Qt.DisplayRole)
        self.model.setHeaderData(1, Qt.Horizontal, "输出影像文件", Qt.DisplayRole)
        delegate = addressTableDelegate(self, [{
            'text': "请选择瓦片文件夹",
            'type': "d"
        }, {
            'text': "请选择或创建影像文件",
            'type': "f"
        }])
        self.tbl_address.setModel(self.model)
        self.tbl_address.setItemDelegate(delegate)
        self.tbl_address.setColumnWidth(0, self.tbl_address.width() / 2)
        self.tbl_address.setColumnWidth(1, self.tbl_address.width() / 2)

        self.paras = {}

    def table_init_onlySpider(self):
        self.model.setHeaderData(1, Qt.Horizontal, "等级", Qt.DisplayRole)
        self.model.setHeaderData(2, Qt.Horizontal, "瓦片文件夹", Qt.DisplayRole)

        delegate = addressTableDelegate(
            self, [None, {
                'type': 'c'
            }, {
                'text': "请选择输出瓦片文件夹",
                'type': "d"
            }])
        self.tbl_address.setModel(self.model)
        self.tbl_address.setItemDelegate(delegate)
        self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4)
        self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2)
        self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.4)

    def table_init_spiderAndHandle(self):
        self.model.setHeaderData(1, Qt.Horizontal, "等级", Qt.DisplayRole)
        self.model.setHeaderData(2, Qt.Horizontal, "瓦片文件夹", Qt.DisplayRole)
        self.model.setHeaderData(3, Qt.Horizontal, "输出影像文件", Qt.DisplayRole)

        delegate = addressTableDelegate(self, [
            None, {
                'type': 'c'
            }, {
                'text': "请选择输出瓦片文件夹",
                'type': "d"
            }, {
                'text': "请选择输出影像文件",
                'type': "f"
            }
        ])
        self.tbl_address.setModel(self.model)
        self.tbl_address.setItemDelegate(delegate)
        self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.4)
        self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.2)
        self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.2)
        self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2)

        self.paras = {}

    @Slot()
    def open_addressFile(self):
        fileName, fileType = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择影像服务参数文件", os.getcwd(), "All Files(*)")
        self.txt_addressFile.setText(fileName)

        if fileName == "":
            return

        try:
            with open(fileName, 'r', encoding='utf-8') as f:
                self.paras = json.load(f)

            self.add_address_rows_from_paras()
        except:
            if self.model.rowCount(QModelIndex()) > 0:
                self.model.removeRows(
                    self.model.rowCount(QModelIndex()) - 1, 1, QModelIndex())
            log.error("读取参数文件失败!", dialog=True)

    def add_address_rows_from_paras(self):
        for kv in self.paras.items():
            url = kv[0]
            v = kv[1]
            imp = v['exports']
            levels = v['levels']

            for i in range(len(imp)):
                row = self.model.rowCount(QModelIndex())
                self.model.addEmptyRow(self.model.rowCount(QModelIndex()), 1,
                                       0)

                url_index = self.tbl_address.model().index(row, self.url_no)
                level_index = self.tbl_address.model().index(
                    row, self.level_no)
                self.tbl_address.model().setData(url_index, url)
                if imp[i]['level'] == -1:
                    self.tbl_address.model().setData(level_index, "")
                else:
                    self.tbl_address.model().setData(level_index,
                                                     imp[i]['level'])
                self.tbl_address.model().setData(
                    self.tbl_address.model().index(row, 2),
                    imp[i]['tileFolder'])

                if self.rbtn_spiderAndHandle.isChecked():
                    self.tbl_address.model().setData(
                        self.tbl_address.model().index(row, 3),
                        imp[i]['imageFile'])

                editor_delegate = self.tbl_address.itemDelegate(level_index)
                if isinstance(editor_delegate, addressTableDelegate):
                    # self.model.setLevelData(level_index, levels)
                    self.model.setLevelData(url, levels)

    @Slot()
    def open_tileInfoFile(self):
        fileName, fileType = QtWidgets.QFileDialog.getOpenFileName(
            self, "选择瓦片参数文件", os.getcwd(), "All Files(*)")
        self.txt_tileInfoFile.setText(fileName)

        if fileName == "":
            return

        try:
            with open(fileName, 'r') as f:
                self.paras = json.load(f)

                for kv in self.paras.items():
                    imp = kv[1]

                    row = self.model.rowCount(QModelIndex())
                    self.model.addEmptyRow(self.model.rowCount(QModelIndex()),
                                           1, 0)

                    self.tbl_address.model().setData(
                        self.tbl_address.model().index(row, 0),
                        imp['tileFolder'])
                    self.tbl_address.model().setData(
                        self.tbl_address.model().index(row, 1),
                        imp['imageFile'])
        except:
            log.error("读取参数文件失败!", dialog=True)

    @Slot()
    def btn_obtainMeta_clicked(self):
        selModel = self.tbl_address.selectionModel()

        if selModel is None:
            return

        indexes = selModel.selectedIndexes()

        if self.rbtn_onlyHandle.isChecked():
            return

        ## 如果有被选中的行,则只获取被选中行的信息
        if len(indexes) > 0:
            rows = sorted(
                set(index.row()
                    for index in self.tbl_address.selectedIndexes()))
        else:
            rows = range(0, self.tbl_address.model().rowCount(QModelIndex()))

        for row in rows:
            url_index, level_index, url, level = self.return_url_and_level(row)
            editor_delegate = self.tbl_address.itemDelegate(level_index)

            url = str(self.tbl_address.model().data(url_index,
                                                    Qt.DisplayRole)).strip()

            if url == "": continue

            # if url not in self.paras.keys():
            getInfo = get_paraInfo(url)
            if getInfo is None:
                log.error(url + "无法获取远程参数信息,请检查地址是否正确以及网络是否连通!")
                continue
            else:
                log.info(url + "参数信息获取成功!")

            self.setParaToMemory(url, getInfo)

            levels = self.paras[url]['levels']

            if isinstance(editor_delegate, addressTableDelegate):
                # self.model.setLevelData(level_index, levels)
                self.model.setLevelData(url, levels)

    def setParaToMemory(self, url, getInfo):
        levels = []
        resolutions = []
        lods = []
        origin_x = origin_y = xmin = xmax = ymin = ymax = resolution = tilesize = level = ""

        if 'tileInfo' in getInfo.keys():
            if 'origin' in getInfo['tileInfo'].keys():
                if 'x' in getInfo['tileInfo']['origin'].keys():
                    origin_x = getInfo['tileInfo']['origin']['x']
                if 'y' in getInfo['tileInfo']['origin'].keys():
                    origin_y = getInfo['tileInfo']['origin']['y']
            if 'lods' in getInfo['tileInfo'].keys():
                lods = getInfo['tileInfo']['lods']

        if 'extent' in getInfo.keys():
            if 'xmin' in getInfo['extent']:
                xmin = getInfo['extent']['xmin']
            if 'xmax' in getInfo['extent']:
                xmax = getInfo['extent']['xmax']
            if 'ymin' in getInfo['extent']:
                ymin = getInfo['extent']['ymin']
            if 'ymax' in getInfo['extent']:
                ymax = getInfo['extent']['ymax']

        if 'tileInfo' in getInfo.keys():
            if 'rows' in getInfo['tileInfo']:
                tilesize = getInfo['tileInfo']['rows']

        paras = {}
        for lod in lods:
            if 'level' in lod.keys():
                level = lod['level']
                levels.append(level)

                if 'resolution' in lod.keys():
                    resolution = lod['resolution']
                    resolutions.append(resolution)

                paras[str(level)] = {
                    'origin_x': origin_x,
                    'origin_y': origin_y,
                    'xmin': xmin,
                    'xmax': xmax,
                    'ymin': ymin,
                    'ymax': ymax,
                    'tilesize': tilesize,
                    'resolution': resolution
                }

        url_encodeStr = urlEncodeToFileName(url)
        self.paras[url] = {
            'code': url_encodeStr,
            'levels': levels,
            'paras': paras
        }

    def update_para_dict(self):
        dict = {
            "origin_x": self.txt_originX.text(),
            "origin_y": self.txt_originY.text(),
            "xmin": self.txt_xmin.text(),
            "xmax": self.txt_xmax.text(),
            "ymin": self.txt_ymin.text(),
            "ymax": self.txt_ymax.text(),
            "tilesize": self.txt_tilesize.text(),
            "resolution": self.txt_resolution.text()
        }
        return dict

    def update_txt_info(self, index: QModelIndex, level=-1):
        # print([index.row(), index.column()])
        key1_index = self.tbl_address.model().index(index.row(), 0)
        key1 = self.tbl_address.model().data(key1_index, Qt.DisplayRole)
        key2_index = self.tbl_address.model().index(index.row(), 1)
        key2 = self.tbl_address.model().data(key2_index, Qt.DisplayRole)

        self.txt_level.setText("")
        self.txt_originX.setText("")
        self.txt_originY.setText("")
        self.txt_xmin.setText("")
        self.txt_xmax.setText("")
        self.txt_ymin.setText("")
        self.txt_ymax.setText("")
        self.txt_tilesize.setText("")
        self.txt_resolution.setText("")

        bUpdate = False
        if self.rbtn_spiderAndHandle.isChecked(
        ) or self.rbtn_onlySpider.isChecked():
            if key1 not in self.paras:
                return

            if level in self.paras[key1]['paras']:
                getInfo = self.paras[key1]['paras'][level]
                self.txt_level.setText(str(level))
                bUpdate = True
        else:
            key = key1 + "_" + key2
            if key in self.paras:
                getInfo = self.paras[key]
                bUpdate = True
            else:
                bUpdate = False

        if bUpdate:
            if 'origin_x' in getInfo:
                self.txt_originX.setText(str(getInfo['origin_x']))
                self.txt_originX.home(False)
            if 'origin_y' in getInfo:
                self.txt_originY.setText(str(getInfo['origin_y']))
                self.txt_originY.home(False)
            if 'xmin' in getInfo:
                self.txt_xmin.setText(str(getInfo['xmin']))
                self.txt_xmin.home(False)
            if 'xmax' in getInfo:
                self.txt_xmax.setText(str(getInfo['xmax']))
                self.txt_xmax.home(False)
            if 'ymin' in getInfo:
                self.txt_ymin.setText(str(getInfo['ymin']))
                self.txt_ymin.home(False)
            if 'ymax' in getInfo:
                self.txt_ymax.setText(str(getInfo['ymax']))
                self.txt_ymax.home(False)
            if 'tilesize' in getInfo:
                self.txt_tilesize.setText(str(getInfo['tilesize']))
                self.txt_tilesize.home(False)
            if 'resolution' in getInfo:
                self.txt_resolution.setText(str(getInfo['resolution']))
                self.txt_resolution.home(False)
Esempio n. 56
0
class BusyLoaderDialog(QDialog):
    """
    This class implements a loader showing a BusyProgressBar indicator.
    """
    def __init__(self, worker, parent=None):
        """
        Initialize the form dialog.
        :type worker: Loader
        :type parent: QWidget
        """
        super().__init__(parent)

        self.worker = worker
        self.workerThread = None

        self.progressBar = QProgressBar(self)
        self.progressBar.setAlignment(Qt.AlignHCenter)
        self.progressBar.setRange(-1, -1)

        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.addWidget(self.progressBar)

        self.setWindowTitle('Loading ...')
        self.setWindowIcon(QIcon(':/images/eddy'))
        self.setFixedSize(300, 100)

    ####################################################################################################################
    #                                                                                                                  #
    #   EVENTS                                                                                                         #
    #                                                                                                                  #
    ####################################################################################################################

    def showEvent(self, event):
        """
        Executed when the dialog is shown
        :type event: QShowEvent
        """
        self.workerThread = QThread()
        self.worker.moveToThread(self.workerThread)

        connect(self.worker.completed, self.completed)
        connect(self.worker.errored, self.errored)
        connect(self.workerThread.started, self.worker.work)

        self.workerThread.start()

    ####################################################################################################################
    #                                                                                                                  #
    #   SLOTS                                                                                                          #
    #                                                                                                                  #
    ####################################################################################################################

    @pyqtSlot(Exception)
    def errored(self, err):
        """
        Executed whenever the translation errors.
        :type err: Exception
        """
        msgbox = QMessageBox(self)
        msgbox.setIconPixmap(QPixmap(':/icons/error'))
        msgbox.setWindowIcon(QIcon(':/images/eddy'))
        msgbox.setWindowTitle('Load failed!')
        msgbox.setStandardButtons(QMessageBox.Close)
        msgbox.setText('Failed to load {}!'.format(os.path.basename(self.worker.filepath)))
        msgbox.setDetailedText(''.join(traceback.format_exception(type(err), err, err.__traceback__)))
        msgbox.exec_()
        self.workerThread.quit()
        self.reject()

    @pyqtSlot()
    def completed(self):
        """
        Executed whenever the translation completes.
        """
        self.workerThread.quit()
        self.worker.moveToThread(QApplication.instance().thread())
        self.accept()