Ejemplo n.º 1
0
class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        layout = QVBoxLayout(self)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 100)
        layout.addWidget(self.progressBar)
        layout.addWidget(QPushButton('开启线程', self, clicked=self.onStart))

        # 当前线程id
        print('main id', QThread.currentThread())

        # 启动线程更新进度条值
        self._thread = QThread(self)
        self._worker = Worker()
        self._worker.moveToThread(self._thread)  # 移动到线程中执行
        self._thread.finished.connect(self._worker.deleteLater)
        self._thread.started.connect(self._worker.run)
        self._worker.valueChanged.connect(self.progressBar.setValue)

    def onStart(self):
        if not self._thread.isRunning():
            print('main id', QThread.currentThread())
            self._thread.start()  # 启动线程

    def closeEvent(self, event):
        if self._thread.isRunning():
            self._thread.requestInterruption()
            self._thread.quit()
            self._thread.wait()
            # 强制
            # self._thread.terminate()
        self._thread.deleteLater()
        super(Window, self).closeEvent(event)
Ejemplo n.º 2
0
class ApiSync(QObject):
    '''
    ApiSync continuously syncs, waiting 15 seconds between task completion.
    '''

    sync_started = pyqtSignal()
    sync_success = pyqtSignal()
    sync_failure = pyqtSignal(Exception)

    TIME_BETWEEN_SYNCS_MS = 1000 * 15  # fifteen seconds between syncs

    def __init__(self, api_client: API, session_maker: scoped_session,
                 gpg: GpgHelper, data_dir: str):
        super().__init__()
        self.api_client = api_client

        self.sync_thread = QThread()
        self.api_sync_bg_task = ApiSyncBackgroundTask(
            api_client, session_maker, gpg, data_dir, self.sync_started,
            self.on_sync_success, self.on_sync_failure)
        self.api_sync_bg_task.moveToThread(self.sync_thread)

        self.sync_thread.started.connect(self.api_sync_bg_task.sync)

    def start(self, api_client: API) -> None:
        '''
        Start metadata syncs.
        '''
        self.api_client = api_client

        if not self.sync_thread.isRunning():
            logger.debug('Starting sync thread')
            self.api_sync_bg_task.api_client = self.api_client
            self.sync_thread.start()

    def stop(self) -> None:
        '''
        Stop metadata syncs.
        '''
        self.api_client = None

        if self.sync_thread.isRunning():
            logger.debug('Stopping sync thread')
            self.sync_thread.quit()

    def on_sync_success(self) -> None:
        '''
        Start another sync on success.
        '''
        self.sync_success.emit()
        QTimer.singleShot(self.TIME_BETWEEN_SYNCS_MS,
                          self.api_sync_bg_task.sync)

    def on_sync_failure(self, result: Exception) -> None:
        '''
        Only start another sync on failure if the reason is a timeout request.
        '''
        self.sync_failure.emit(result)
        QTimer.singleShot(self.TIME_BETWEEN_SYNCS_MS,
                          self.api_sync_bg_task.sync)
Ejemplo n.º 3
0
class DownloadDialog(QDialog, Ui_DownloadDialog):
    def __init__(self, url, filePath, threadno, totalSize, parent=None):
        super().__init__(parent)
        self.setupUi(self)
        self.setWindowTitle(os.path.split(filePath)[1])
        self.url = url
        self.filePath = filePath
        self.threadno = threadno
        self.totalSize = totalSize
        self.downloadTask = None
        self.workerThread = QThread(self)
        self.workerThread.started.connect(self.notifyStarted)
        self.workerThread.finished.connect(self.notifyFinished)
        self.mStopPushButton.clicked.connect(self.stopProcess)
        self.mCancelPushButton.clicked.connect(self.cancelProcess)
        self.toggleDownloadTask()

    @pyqtSlot(int)
    def setProgress(self, v):
        self.mProgressBar.setValue(v)
        if v == self.totalSize:
            self.workerThread.quit()
            self.workerThread.wait()

    def toggleDownloadTask(self):
        self.mProgressBar.setMaximum(self.totalSize)
        self.downloadTask = DownloadTask(self.url, self.filePath, self.totalSize, self.threadno)
        self.downloadTask.progressed.connect(self.setProgress)
        self.workerThread.started.connect(self.downloadTask.startDownload)
        self.workerThread.finished.connect(self.downloadTask.deleteLater)
        self.downloadTask.moveToThread(self.workerThread)
        self.workerThread.start()

    @pyqtSlot()
    def stopProcess(self):
        logger.info("stop task of downloading {} finished.".format(self.url))
        if self.workerThread.isRunning():
            self.workerThread.terminate()
            self.workerThread.wait()
            self.mStopPushButton.setText("Start")
        else:
            self.mStopPushButton.setText("Stop")

    @pyqtSlot()
    def cancelProcess(self):
        logger.info("cancel task of downloading {} finished.".format(self.url))
        if self.workerThread.isRunning():
            self.workerThread.terminate()
            self.workerThread.wait()

    @pyqtSlot()
    def notifyStarted(self):
        pass

    @pyqtSlot()
    def notifyFinished(self):
        logger.info("finished download {}".format(self.url))
Ejemplo n.º 4
0
class AppDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(AppDialog, self).__init__(parent)
        self.setWindowTitle("PyQt5 -- QThread app")

        ui_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                               'qthread.ui')
        self.ui_widget = uic.loadUi(ui_file, self)
        self.ui_widget.buttonBox.accepted.connect(self.on_accept)
        self.ui_widget.buttonBox.rejected.connect(self.on_reject)

        self.ui_widget.pushButton_start.clicked.connect(self.on_click_start)
        self.ui_widget.pushButton_stop.clicked.connect(self.on_click_stop)

        self.ui_widget.progressBar.setValue(0)
        print("")

        self.thread_qt = QThread()

    def on_update_from_helper(self, tick):
        self.ui_widget.progressBar.setValue(tick)

    def on_result_from_helper(self, tick):
        self.ui_widget.progressBar.setValue(tick)
        print("Thread is completed")

    @pyqtSlot()
    def on_click_start(self):
        if self.thread_qt.isRunning():
            print("Thread is already running")
        else:
            print("Kicked off thread")
            self.helper_impl = helperThreadImpl(min=0, max=100)
            self.thread_qt.started.connect(self.helper_impl.start)
            self.helper_impl.sig_update.connect(self.on_update_from_helper)
            self.helper_impl.sig_result.connect(self.on_result_from_helper)
            self.helper_impl.moveToThread(self.thread_qt)
            self.thread_qt.start()

    @pyqtSlot()
    def on_click_stop(self):
        if self.thread_qt.isRunning():
            print("Ask thread to temrinate...")
            self.thread_qt.terminate()

            # It takes time, as per docs it depends on underlying OS
            self.thread_qt.wait()
            print("Thread is terminated")
        else:
            print("Thread is not active")

    def on_accept(self):
        print("on_accept")

    def on_reject(self):
        print("on_reject")
Ejemplo n.º 5
0
class Example(QObject):
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)

        self.gui = Window()

        self.worker = Worker()  # 백그라운드에서 돌아갈 인스턴스 소환
        self.worker_thread = QThread()  # 따로 돌아갈 thread를 하나 생성
        self.worker.moveToThread(self.worker_thread)  # worker를 만들어둔 쓰레드에 넣어줍니다
        self.worker_thread.start()  # 쓰레드를 실행합니다.

        self._connectSignals()  # 시그널을 연결하기 위한 함수를 호출
        self.gui.show()

    # 시그널을 연결하기 위한 func.
    def _connectSignals(self):
        # gui의 버튼을 클릭 시 연결설정
        self.gui.button_start.clicked.connect(self.worker.startWork)
        # worker에서 발생한 signal(sig_numbers) 의 연결 설정
        self.worker.sig_numbers.connect(self.gui.updateStatus)
        # cancel 버튼 연결 설정
        self.gui.button_cancel.clicked.connect(self.forceWorkerReset)

    # 쓰레드의 loop를 중단하고 다시 처음으로 대기 시키는 func.
    def forceWorkerReset(self):
        if self.worker_thread.isRunning():  # 쓰레드가 돌아가고 있다면
            self.worker_thread.terminate()  # 현재 돌아가는 thread를 중지시킨다
            self.worker_thread.wait()  # 새롭게 thread를 대기한 후
            self.worker_thread.start()  # 다시 처음부터 시작
Ejemplo n.º 6
0
class MyQProcess(QWidget):
    def __init__(self):
        super(QWidget, self).__init__()
        layout = QVBoxLayout()
        self.edit = QTextEdit()
        self.thread = QThread()

        self.setupConnections()

        self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
        layout.addWidget(self.edit)
        self.setLayout(layout)
        self.show()

    def setupConnections(self):
        self.worker = Worker()
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.sendOutput.connect(self.showOutput)

        self.worker.moveToThread(self.thread)
        self.thread.start()

    def __del__(self):
        if self.thread.isRunning():
            self.thread.quit()
            # Do some extra checking if thread has finished or not here if you want to

    #Define Slot Here
    @pyqtSlot(str)
    def showOutput(self, output):
        #self.edit.clear()
        self.edit.append(output)
class MainWin(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self,parent)
        self.window = uic.loadUi(main_path,self) # 메인창 만들기, 불러오기 self는 왜잇냐
        self.window.show()
        # 쓰레드 생성 및 연결
        self.worker = Worker() # back 에서 구동될 쓰레드
        self.threadObj = QThread() # 쓰레드 객체 생성
        self.worker.moveToThread(self.threadObj) # 가동할 쓰레드 - 쓰레드 객체를 연결
        self.connectionSignal()  # signal 연결 작업
        self.threadObj.start()  # 쓰레드 가동
    

    #쓰레드 중단 버튼을 누르면 호출된다. ?
    def connectionSignal(self):
        # 쓰레드 가동 버튼을 누르면 쓰레드가 시작.
        self.window.pushButton.clicked.connect(self.worker.start)
        # 쓰레드가 돌면서 이벤트를 발생하는데 받을 콜백함수 연결
        self.worker.signal_obj.connect(self.window.recvCnt)# 서로 다른 클래스 끼리 연결
        # 이벤트 이름이 없다.
    #쓰레드가 보내는 숫자값 받는 콜백함수

    #여기는 두개가 있다. signal이 (시작, 종료)
    @pyqtSlot(int)
    def recvCnt(self, cnt):
        self.window.progressBar.setValue(cnt)  # 받아서 화면을 바꿔준다.

    @pyqtSlot()
    def onThreadStop(self):# 이미 연결시켜놓음(디자이너에서)
        if self.threadObj.isRunning():
            self.threadObj.terminate()
Ejemplo n.º 8
0
class MFWorker(QObject):
    def __init__(self, func, args=None):
        super().__init__(None)
        self.func = func
        self.args = args
        self.thread = QThread()
        self.moveToThread(self.thread)
        self.thread.started.connect(self.run)
        pass

    def isRunning(self):
        return self.thread.isRunning()

    def start(self):
        self.thread.start()
        pass

    def terminate(self):
        self.thread.exit(0)
        pass

    def run(self):
        if self.args:
            self.func(*self.args)
        else:
            self.func()
        self.thread.exit(0)
        pass

    pass
Ejemplo n.º 9
0
Archivo: d.py Proyecto: huxx-j/untitled
class Example(QObject):
    def __init__(self, parent=None):
        super(self.__class__, self).__init__(parent)

        self.gui = Window()
        self.worker = Worker()  # 백그라운드에서 돌아갈 인스턴스 소환
        self.worker_thread = QThread()  # 따로 돌아갈 thread를 하나 생성
        self.worker.moveToThread(self.worker_thread)  # worker를 만들어둔 쓰레드에 넣어줍니다
        self.worker_thread.start()  # 쓰레드를 실행합니다.

        self._connectSignals(
        )  # 시그널을 연결하기 위한 함수를 호출[출처] [Python3] PyQt5 | QThread-pyqtSignal/Slot Example|작성자 우리동네약사

        self.gui.show()

    def _connectSignals(self):
        # // gui 의 버튼을 클릭시 연결설정
        self.gui.button_start.clicked.connect(self.worker.startWork)
        # // worker에서 발생한 signal(sig_numbers)의 연결 설정
        self.worker.sig_numbers.connect(self.gui.updateStatus)
        # // cancel 버튼 연결 설정
        self.gui.button_cancel.clicked.connect(self.forceWorkerReset)

    # // 쓰레드의 loop를 중단하고 다시 처음으로 대기 시키는 func.
    def forceWorkerReset(self):
        if self.worker_thread.isRunning():
            self.worker_thread.terminate()
            self.worker_thread.wait()
            self.worker_thread.start()
Ejemplo n.º 10
0
class Window(QWidget):
    def __init__(self):

        super().__init__()
        self.initUi()
        self.show()

    def initUi(self):

        self.button_start = QPushButton('Start long running task')
        self.button_start.clicked.connect(self.run_task)

        self.button_stop = QPushButton('Stop long running task')
        self.button_stop.clicked.connect(self.end_task)

        self.label = QLabel()

        self.layout = QGridLayout()
        self.layout.addWidget(self.button_start, 0, 0)
        self.layout.addWidget(self.button_stop, 1, 0)
        self.layout.addWidget(self.label, 2, 0)
        self.setLayout(self.layout)

    def run_task(self):

        self.thread = QThread()

        self.worker = Worker(self.thread)
        self.worker.moveToThread(self.thread)
        self.worker.incremented.connect(self.set_label_value)

        self.thread.started.connect(self.worker.do_work)
        self.thread.finished.connect(self.thread.deleteLater)

        if not self.thread.isRunning():
            self.thread.start()

    def end_task(self):

        self.worker.stop()
        if self.thread.isRunning():
            self.thread.quit()
            self.label.setText('')

    def set_label_value(self, value):
        self.label.setText(value)
Ejemplo n.º 11
0
 def mp_thread_callback(self, thread: QThread, ret):
     if isinstance(ret, Exception):
         print(ret)
         self.show_critical_error('Error contacting central market')
     thread.wait(2000)
     if thread.isRunning():
         thread.terminate()
     self.mp_threads.remove(thread)
Ejemplo n.º 12
0
class VersionCheck(QObject):
    """
    If executed in PyInstaller bundle, check for updates on remote server.
    Download updated executable and inform user that an update is available.
    """
    def __init__(self, app, version):
        super().__init__()
        self.app = app
        self.version = version

        # Prepare thread
        self.thread = QThread()
        self.first_run = True
        self.obj = VersionCheckWorker(self.version)

    def create_thread(self, skip_wait=False):
        # Skip updater from debug Env
        if run_in_ide and self.first_run:
            LOGGER.debug('Not running initial version check from debug.')
            self.first_run = False
            return

        if self.thread.isRunning():
            return

        # Skip wait if version check called from menu
        if skip_wait:
            self.obj.set_skip_wait()

        # hide updater menu
        self.app.ui.actionVersion_info.setVisible(False)
        self.app.ui.menuUpdate.setEnabled(False)

        # Move the Worker object to the Thread object
        self.obj.moveToThread(self.thread)

        self.obj.version_info.connect(self.new_version)
        self.obj.finished.connect(self.thread.quit)

        self.thread.started.connect(self.obj.version_check)

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

    def new_version(self, version_info):
        if version_info > self.version:
            version_text = 'Aktualisierung auf ' + version_info + ' steht bereit.'
            self.app.ui.actionVersion_info.setText(version_text)
            # Display update menu
            self.app.ui.menuUpdate.setEnabled(True)
            self.app.ui.menuUpdate.setIcon(
                self.app.ui.icon[Itemstyle.MAIN['update']])
            self.app.ui.actionVersion_info.setVisible(True)
        else:
            self.app.ui.menuUpdate.setTitle('Version aktuell')
Ejemplo n.º 13
0
class MainWindow(QWidget):
    def __init__(self, device, parent=None):
        super().__init__()
        vbox = QVBoxLayout()
        self.measurement_time = pg.SpinBox(value=0.01,
                                           suffix='s',
                                           bounds=(0.001, None))
        self.coincidence_window = pg.SpinBox(value=0.01,
                                             suffix='s',
                                             bounds=(0, None))
        self.long_button = QPushButton("Measure")
        self.long_button.setCheckable(True)
        self.measurement_thread = QThread()
        self.measurement_thread.finished.connect(self.run_measurement)
        self.long_button.clicked.connect(self.run_measurement)
        self.graph = pg.PlotWidget()
        self.setLayout(vbox)
        self.channel_1_data = np.zeros((300))
        self.channel_2_data = np.zeros((300))
        self.coincidences_data = np.zeros((300))
        self.channel_1 = self.graph.plot([], pen=pg.mkPen('b'))
        self.channel_2 = self.graph.plot([], pen=pg.mkPen('r'))
        self.coincidences = self.graph.plot([], pen=pg.mkPen('w'))
        self.device = device
        vbox.addWidget(self.measurement_time)
        vbox.addWidget(self.coincidence_window)
        vbox.addWidget(self.long_button)
        vbox.addWidget(self.graph)
        vbox.addWidget(SLMControllerWidget())

    @pyqtSlot(list, list)
    def update_data(self, l1, l2):
        self.channel_1_data = np.roll(self.channel_1_data, -1)
        self.channel_2_data = np.roll(self.channel_2_data, -1)
        self.coincidences_data = np.roll(self.coincidences_data, -1)
        self.channel_1_data[-1] = l1[0]
        self.channel_2_data[-1] = l1[1]
        self.coincidences_data[-1] = l2[0]
        self.channel_1.setData(self.channel_1_data)
        self.channel_2.setData(self.channel_2_data)
        self.coincidences.setData(self.coincidences_data)

    def run_measurement(self):
        if self.long_button.isChecked(
        ) and not self.measurement_thread.isRunning():
            self.measurement = MeasurementThread(
                self.device, int(self.measurement_time.value() * 1000),
                int(self.coincidence_window.value()))
            self.measurement.measurement_done.connect(self.update_data)
            self.measurement.moveToThread(self.measurement_thread)
            self.measurement_thread.started.connect(
                self.measurement.run_measurement)
            self.measurement.measurement_done.connect(
                self.measurement_thread.quit)
            self.measurement_thread.start()
Ejemplo n.º 14
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()
Ejemplo n.º 15
0
class SerialPortSimulator(QObject):

    dataReceived = pyqtSignal(QVariant, arguments=["data"])

    def __init__(self, app=None, db=None):

        super().__init__()
        self._app = app
        self._db = db

        self._thread = QThread()
        self._worker = SimulatorWorker()
        self._worker.moveToThread(self._thread)
        self._worker.dataCreated.connect(self.data_created)
        self._thread.started.connect(self._worker.run)

    @pyqtSlot()
    def start(self):

        if not self._thread.isRunning():
            self._thread.start()

    @pyqtSlot()
    def stop(self):

        if self._thread.isRunning():
            self._worker.stop()
            self._thread.quit()

    def data_created(self, data):
        """
        Method to catch the data from the SimulatorWorker
        :param data:
        :return:
        """
        # logging.info(f"data caught: {data}")
        self.dataReceived.emit(data)
Ejemplo n.º 16
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()
Ejemplo n.º 17
0
class CameraControler(QObject):
    def __init__(self, camera, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.thread = QThread()
        self.camera = camera
        self.camera.moveToThread(self.thread)
        self.thread.started.connect(self.camera.work)
        self.thread.finished.connect(self.camera.deleteLater)

    def __del__(self):
        self.thread.quit()
        self.thread.wait()

    def start(self):
        if not self.thread.isRunning():
            self.thread.start()
Ejemplo n.º 18
0
    def cmdFSUpdateMP_callback(self, thread: QThread, ret):
        model = self.enh_model
        frmMain = self.frmMain

        idx_NAME = self.HEADERS.index(HEADER_NAME)

        if isinstance(ret, Exception):
            print(ret)
            frmMain.sig_show_message.emit(frmMain.CRITICAL,
                                          'Error contacting central market')
        else:
            settings = model.settings
            item_store: ItemStore = settings[settings.P_ITEM_STORE]
            with QBlockSig(self):
                for i in range(self.rowCount()):
                    this_gear: Gear = self.cellWidget(i, idx_NAME).gear
                    self.fs_gear_set_costs(this_gear, item_store, i)
            frmMain.sig_show_message.emit(frmMain.REGULAR,
                                          'Fail stacking prices updated')
        thread.wait(2000)
        if thread.isRunning():
            thread.terminate()
        self.mp_threads.remove(thread)
Ejemplo n.º 19
0
class ThreadingAdapter(QObject):
    finished = pyqtSignal()
    objThread = None
    source = None

    def __init__(self):
        super().__init__()
        self.objThread = QThread()
        self.moveToThread(self.objThread)
        self.finished.connect(self.objThread.quit)
        self.objThread.started.connect(self.run)

    @pyqtSlot()
    def run(self):
        pass

    def start(self):
        self.objThread.start()

    def isRunning(self):
        return self.objThread.isRunning()

    def wait(self):
        self.objThread.wait()
class LabelPrinter(QObject):
    """
    Class for the LabelPrinter used to handle printing labels.
    """
    printerStatusReceived = pyqtSignal(
        str, bool, str, arguments=["comport", "success", "message"])
    tagIdChanged = pyqtSignal(str, arguments=["tag_id"])

    def __init__(self, app=None, db=None):
        super().__init__()

        self._logger = logging.getLogger(__name__)
        self._app = app
        self._db = db

        self._printer_thread = QThread()
        self._printer_worker = None

    @pyqtSlot(str, int, str, str)
    def print_job(self, comport, pi_id, action, specimen_number):
        """
        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 self._printer_thread.isRunning():
            return

        if specimen_number is None:
            return

        # Line 1 - Header
        header = "NOAA/NWFSC/FRAM - WCGBT Survey"

        # Line 2a - Principal Investigator
        try:
            investigator = PrincipalInvestigatorLu.select() \
                .where(PrincipalInvestigatorLu.principal_investigator == pi_id).get().last_name
        except DoesNotExist as ex:
            investigator = "PI Not Available"

        # Line 2b - Action Type
        # action - that was passed in

        # Line 3 - Haul #
        haul_id = self._app.state_machine.haul["haul_number"]
        if "t" in haul_id and len(haul_id) > 3:
            haul_id = "t" + haul_id[-4:]

        # Line 4 - Species Scientific Name
        species = self._app.state_machine.species["scientific_name"]

        # Line 5 - Length / Weight / Sex
        try:
            length = weight = sex = stomach = tissue = ovary = testes = ""
            parent_specimen = self._app.state_machine.specimen[
                "parentSpecimenId"]
            specimens = (Specimen.select(Specimen, TypesLu).join(
                TypesLu,
                on=(Specimen.action_type == TypesLu.type_id
                    ).alias('types')).where(
                        Specimen.parent_specimen == parent_specimen))
            for specimen in specimens:
                if "length" in specimen.types.type.lower():
                    length = specimen.numeric_value
                elif "weight" in specimen.types.type.lower():
                    weight = round(specimen.numeric_value, 2)
                elif "sex" in specimen.types.type.lower():
                    sex = specimen.alpha_value
                elif "stomach" in specimen.types.type.lower():
                    stomach = specimen.alpha_value
                elif "tissue" in specimen.types.type.lower():
                    tissue = specimen.alpha_value
                elif "ovary" in specimen.types.type.lower():
                    ovary = specimen.alpha_value
                elif "testes" in specimen.types.type.lower():
                    testes = specimen.alpha_value
        except DoesNotExist as ex:
            pass
        except Exception as ex:
            logging.info('Error creating the measurement line: ' + str(ex))
        measurement = "Len: " + str(length) + "cm, Wt: " + str(
            weight) + "kg, Sex: " + str(sex)

        # Line 3B - Shortened Specimen Number - on the same line as the haul ID
        short_specimen_number = ""
        try:
            if investigator == "FRAM":
                print_iteration = ""
                specimen_number_split = specimen_number.split("-")
                if len(specimen_number_split) == 5:
                    vessel_id = specimen_number_split[1]
                    spec_num = specimen_number_split[4]
                    if not specimen_number[-1:].isdigit():
                        print_iteration = specimen_number[-1]
                    if stomach != "" or tissue != "":
                        short_specimen_number = vessel_id + spec_num + print_iteration
                    elif ovary != "" or testes != "":
                        short_specimen_number = vessel_id + haul_id + spec_num + print_iteration
                short_specimen_number = ", Short #: " + short_specimen_number
        except Exception as ex:
            logging.error(
                f"Error creating the shortened specimen number: {ex}")

        # Line 7 - Latitude / Longitude / Depth             # location = "47 15.54N, 126 27.55W"
        try:
            haul = Hauls.select().where(
                Hauls.haul == self._app.state_machine.haul["haul_id"]).get()
            haul_start = haul.start_datetime
            latitude = haul.latitude_min
            longitude = haul.longitude_min
            depth = haul.depth_min
            depth_uom = haul.depth_uom

            if isinstance(depth, float) and depth_uom == "ftm":
                depth = 1.8288 * depth
            if latitude and longitude and depth:
                location = f"{latitude:.6f}, {longitude:.6f}, {depth:.1f}m"
            else:
                location = f"{latitude:.6f}, {longitude:.6f}, Unknown"
        except Exception as ex:
            location = "Unknown, Unknown, Unknown"

        # Line 6 - Date/Time
        try:
            date = datetime.now().strftime(
                "%Y%m%d %H%M%S")  # date = "08/16/2015"
            date_year = datetime.now().year
            haul_dt = arrow.get(haul_start)
            haul_year = haul_dt.datetime.year
            logging.info(
                f"date_year={date_year}, haul_year={haul_year},     date={date} > haul_dt = {haul_dt}"
            )
            if date_year != haul_year:
                haul_dt = haul_dt.format('YYYYMMDD HH:mm:ss')
                if haul_year > date_year:
                    date = haul_dt
            logging.info(f"new date: {date}")

        except Exception as ex:
            logging.info(f"error getting the proper date/time: {ex}")

        # Line 8 - Specimen number
        # If no character at the end, add an A, other increase the character by 1 and update the list item
        if specimen_number[-1:].isdigit():
            specimen_number += "A"
        else:
            char = chr(ord(specimen_number[-1:]) + 1)
            specimen_number = str(specimen_number[:-1]) + char

        # TODO Todd Hay Update_list_item - should I call back to special_actions.upsert here?
        # self.update_list_item(action_type, specimen_number)
        self.tagIdChanged.emit(specimen_number)

        # Line 9 - barcode_number
        # barcode_number = re.sub(r'[^\d.]+', '', specimen_number)
        # barcode_number = re.sub(r'[\W]+', '', specimen_number)      # strips the hyphens
        # barcode_number = specimen_number

        # Strip all hypens and alpha characters
        barcode_number = re.sub(r'[^\d]', '', specimen_number)

        # TODO Todd Hay - I might need to strip the - from the barcode to have it get properly encoded
        # Per p. 54 - printer specification (epl2 programming manual), hyphens may be used but they'll be ignored
        # barcode_number = int(specimen_number.strip('-'))

        # logging.info('specimen number: ' + str(specimen_number))
        # logging.info('barcode number: ' + str(barcode_number))

        suffix = "\"\n"
        lead_in = """
N
O
q500
S3
D10
ZT\n"""
        lead_in_bytes = bytes(lead_in, "UTF-8")

        count = 1
        lead_out = "\nP" + str(
            count) + "\n\n"  # lead out sends the label count (number to print)
        lead_out_bytes = bytes(lead_out, "UTF-8")

        header_bytes = bytes("A0,10,0,4,1,1,N,\"" + header + suffix, "UTF-8")
        investigator_bytes = bytes(
            "A0,50,0,4,1,1,N,\"" + "PI: " + investigator + ", " + str(action) +
            suffix, "UTF-8")
        haul_id_bytes = bytes(
            "A0,90,0,4,1,1,N,\"" + "Haul ID: " + str(haul_id) +
            str(short_specimen_number) + suffix, "UTF-8")
        species_bytes = bytes(
            "A0,130,0,4,1,1,N,\"" + "Species: " + species + suffix, "UTF-8")
        measurement_bytes = bytes("A0,170,0,4,1,1,N,\"" + measurement + suffix,
                                  "UTF-8")
        date_bytes = bytes(
            "A0,210,0,4,1,1,N,\"" + "Date: " + str(date) + suffix, "UTF-8")
        location_bytes = bytes("A0,250,0,4,1,1,N,\"" + str(location) + suffix,
                               "UTF-8")
        specimen_number_bytes = bytes(
            "A0,290,0,4,1,1,N,\"" + "Spec #: " + str(specimen_number) + suffix,
            "UTF-8")
        barcode_bytes = bytes(
            "B0,330,0,1,3,3,72,N,\"" + str(barcode_number) + suffix, "UTF-8")

        rows = [
            lead_in_bytes, header_bytes, investigator_bytes, haul_id_bytes,
            species_bytes, measurement_bytes, date_bytes, location_bytes,
            specimen_number_bytes, barcode_bytes, lead_out_bytes
        ]

        if comport is None:
            comport = "COM9"

        kwargs = {"comport": comport, "rows": rows}

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

    @pyqtSlot(str)
    def print_test_job(self, comport):
        """
        Method for printing out a test label to ensure that the printer is operational
        :return:
        """
        if not isinstance(comport, str) or comport == "":
            return

        header = "TEST TEST TEST Print"  # Line 1 - Header
        investigator = "FRAM"  # Line 2a - PI
        action = "Action"  # Line 2b - Action
        haul_id = "210099888777"  # Line 3 - Haul #
        species = "Abyssal Crangon"  # Line 4 - Species Scientific Name
        measurement = "Len: 100cm, Wt: 60kg, Sex: M"  # Line 5 - Measurement
        date = datetime.now().strftime("%Y%m%d %H%M%S")  # Line 6 - Date/Time
        location = "Unknown, Unknown"  # Line 7 - Latitude / Longitude
        specimen_number = "2100-001-001-001-001"  # Line 8 - Specimen number
        barcode_number = re.sub(r'[^\d]', '',
                                specimen_number)  # Line 9 - barcode_number

        suffix = "\"\n"
        lead_in = """
    N
    O
    q500
    S3
    D10
    ZT\n"""
        lead_in_bytes = bytes(lead_in, "UTF-8")

        count = 1
        lead_out = "\nP" + str(
            count) + "\n\n"  # lead out sends the label count (number to print)
        lead_out_bytes = bytes(lead_out, "UTF-8")

        header_bytes = bytes("A0,10,0,4,1,1,N,\"" + header + suffix, "UTF-8")
        investigator_bytes = bytes(
            "A0,50,0,4,1,1,N,\"" + "PI: " + investigator + ", " + str(action) +
            suffix, "UTF-8")
        haul_id_bytes = bytes(
            "A0,90,0,4,1,1,N,\"" + "Haul ID: " + str(haul_id) + suffix,
            "UTF-8")
        species_bytes = bytes(
            "A0,130,0,4,1,1,N,\"" + "Species: " + species + suffix, "UTF-8")
        measurement_bytes = bytes("A0,170,0,4,1,1,N,\"" + measurement + suffix,
                                  "UTF-8")
        date_bytes = bytes(
            "A0,210,0,4,1,1,N,\"" + "Date: " + str(date) + suffix, "UTF-8")
        location_bytes = bytes(
            "A0,250,0,4,1,1,N,\"" + "Loc: " + str(location) + suffix, "UTF-8")
        specimen_number_bytes = bytes(
            "A0,290,0,4,1,1,N,\"" + "Spec #: " + str(specimen_number) + suffix,
            "UTF-8")
        barcode_bytes = bytes(
            "B0,330,0,1,3,3,72,N,\"" + str(barcode_number) + suffix, "UTF-8")

        rows = [
            lead_in_bytes, header_bytes, investigator_bytes, haul_id_bytes,
            species_bytes, measurement_bytes, date_bytes, location_bytes,
            specimen_number_bytes, barcode_bytes, lead_out_bytes
        ]

        kwargs = {"comport": comport, "rows": rows}

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

    def _printer_status_received(self, comport, success, message):
        """
        Method to catch the printer results
        :return:
        """
        self.printerStatusReceived.emit(comport, success, message)
        # logging.info('message received: ' + str(message))
        self._printer_thread.quit()

    @pyqtSlot(str, result=str)
    def get_tag_id(self, specimen_type):
        """
        Method to get a new tag ID
        :return:
        """
        mapping = {
            "ovaryNumber": {
                "type": "000",
                "action": "Ovary ID",
                "id": "ovarySpecimenId"
            },
            "stomachNumber": {
                "type": "001",
                "action": "Stomach ID",
                "id": "stomachSpecimenId"
            },
            "tissueNumber": {
                "type": "002",
                "action": "Tissue ID",
                "id": "tissueSpecimenId"
            },
            "finclipNumber": {
                "type": "003",
                "action": "Finclip ID",
                "id": "finclipSpecimenId"
            }
        }
        if specimen_type not in mapping:
            return

        tag_id = None
        try:
            for setting in Settings.select():
                if setting.parameter == "Survey Year":
                    year = setting.value
                elif setting.parameter == "Vessel ID":
                    vessel_id = setting.value

            haul_number = str(self._app.state_machine.haul["haul_number"])
            if len(haul_number) > 3:
                haul_number = haul_number[-3:]

            try:
                pi_action_code_id = \
                    PiActionCodesLu.select(PiActionCodesLu) \
                        .join(PrincipalInvestigatorLu,
                              on=(PiActionCodesLu.principal_investigator == PrincipalInvestigatorLu.principal_investigator)) \
                        .join(TypesLu, on=(PiActionCodesLu.action_type == TypesLu.type_id).alias('types')) \
                        .where(PrincipalInvestigatorLu.full_name == "FRAM Standard Survey",
                               TypesLu.category == "Action",
                               TypesLu.type == mapping[specimen_type]["action"]).get().pi_action_code
            except DoesNotExist as ex:
                pi_action_code_id = 1

            specimen_type_id = str(pi_action_code_id).zfill(3)

            # Query for specimen number - get the latest one for the given specimen type (i.e. ovary, stomach, tissue, finclip)
            spec_num_length = 21
            specimens = (Specimen.select(Specimen, TypesLu).join(
                TypesLu,
                on=(Specimen.action_type == TypesLu.type_id
                    ).alias('types')).where(
                        TypesLu.type == mapping[specimen_type]["action"],
                        TypesLu.category == "Action",
                        fn.length(
                            Specimen.alpha_value) == spec_num_length).order_by(
                                Specimen.alpha_value.desc()))

            # Get the newest specimen.  Note that one may not exist as it hasn't been created yet
            try:
                last_specimen_num = specimens.get().alpha_value
            except DoesNotExist as dne:
                last_specimen_num = None

            # Compare to the existing specimen type for the selected model item
            index = self._app.state_machine.specimen["row"]
            item = self._model.get(index)
            specimen_value = item[specimen_type]
            specimen_id = item[mapping[specimen_type]["id"]]
            """
            Use Cases
            1. No existing SPECIMEN record exists for this specimen_type - insert a new one by one-upping the
                last number for this specimen_type
            2. An existing SPECIMEN exists for this specimen_type - so a number should already be added, don't
               override then, correct?  We should only give the next number up ever after having queried the
               specimen table for the last number for this specimen_type - which is what we have in
               last_specimen_num
            """

            if specimen_id is None or specimen_id == "" or \
                            specimen_value is None or specimen_value == "" or len(specimen_value) != spec_num_length:
                # No specimen record exists for this specimen_type, so we're creating a new specimen_value
                # So one up the highest number and put an "a" at the end of it
                if last_specimen_num:
                    specimen_num = str(
                        int(re.sub(r'[^\d.]+', '', last_specimen_num)[-3:]) +
                        1).zfill(3)
                else:
                    specimen_num = "001"
                print_version = "a"
            else:
                # Specimen record exists, then nothing to do here.  Clicking the print button will up the last
                # alpha character
                return specimen_value

            sep = "-"
            tag_id = year + sep + vessel_id + sep + haul_number + sep + specimen_type_id + \
                     sep + specimen_num  # + print_version

        except Exception as ex:
            logging.info('get_tag_id error: ' + str(ex))

        # logging.info('tag_id: ' + str(tag_id))

        return tag_id
Ejemplo n.º 21
0
class Settings(QObject):
    """
    Handles Trawl Backdeck settings and related database interactions
    """
    # onPrinterChanged = pyqtSignal()
    # printerChanged = pyqtSignal()
    # pingStatusReceived = pyqtSignal(str, bool, arguments=['message', 'success'])
    loggedInStatusChanged = pyqtSignal()
    passwordFailed = pyqtSignal()
    yearChanged = pyqtSignal()
    vesselChanged = pyqtSignal()
    haulChanged = pyqtSignal()
    wheelhouseDbChanged = pyqtSignal()
    sensorsDbChanged = pyqtSignal()
    backdeckDbChanged = pyqtSignal()
    modeChanged = pyqtSignal()
    statusBarMessageChanged = pyqtSignal()
    isLoadingChanged = pyqtSignal()
    scanFilesChanged = pyqtSignal()

    def __init__(self, app=None):
        super().__init__()

        self._logger = logging.getLogger(__name__)
        self._app = app

        self._host = 'nwcdbp24.nwfsc.noaa.gov'
        self._port = 5433
        self._database_name = 'fram'
        self._database = None
        self._connection = None
        self.mode = "Prod"

        self._backdeck_database = None

        self._database_proxy = database_proxy

        self._wheelhouse_proxy = wheelhouse_db_proxy
        self._sensors_proxy = sensors_db_proxy
        self._backdeck_proxy = backdeck_db_proxy

        self._year = None
        self._vessel = None
        self._haul = None

        self._logged_in_status = False

        self._get_credentials()

        self._load_management_files_thread = QThread()
        self._load_management_files_worker = None

        self._status_bar_msg = ""

        self._scan_files = False
        self._is_loading = False

    @pyqtProperty(bool, notify=scanFilesChanged)
    def scanFiles(self):
        """
        Method to return the self._scan_files variable
        :return:
        """
        return self._scan_files

    @scanFiles.setter
    def scanFiles(self, value):
        """
        Method to set the self._scan_files variable
        :param value:
        :return:
        """
        if not isinstance(value, bool):
            logging.error(
                f"trying to set the self._scan_files variable, but it is not a bool: {value}"
            )
            return

        self._scan_files = value
        self.scanFilesChanged.emit()

        logging.info(f"scanFiles = {self._scan_files}")

    @pyqtProperty(bool, notify=isLoadingChanged)
    def isLoading(self):
        """
        Method to keep track of whether data is being load or not.  If it is, disable changing of various items such
        as vessel, year, etc.
        :return:
        """
        return self._is_loading

    @isLoading.setter
    def isLoading(self, value):
        """
        Method to set the _is_loading variable
        :param value:
        :return:
        """
        if not isinstance(value, bool):
            return

        self._is_loading = value
        self.isLoadingChanged.emit()

    def _get_credentials(self):
        """
        Testing method to auto-fill credentials
        :return:
        """
        self._username = ""
        self._password = ""

        path = os.path.expanduser("~/Desktop/credential.txt")
        if os.path.exists(path):
            file = open(path)
            self._username = file.readline().strip("\r\n")
            self._password = file.readline().strip("\r\n")
            file.close()

    @pyqtProperty(str, notify=passwordFailed)
    def password(self):
        """
        Return a password
        :return:
        """
        return self._password

    @pyqtProperty(str, notify=passwordFailed)
    def username(self):
        """
        Return a username
        :return:
        """
        return self._username

    @pyqtSlot(str, str)
    def login(self, user, password):
        """
        Method to login to the Postgresql database
        :param user: str - the user
        :param password: str - the password
        :return:
        """
        try:
            self._username = user

            self._database = PostgresqlDatabase(self._database_name,
                                                user=self._username,
                                                password=password,
                                                host=self._host,
                                                port=self._port)
            # autorollback=True)
            self._connection = self._database.connect()
            self._database_proxy.initialize(self._database)

            logging.info(f"db closed? = {self._database.is_closed()}")

            self.loadFileManagementModels()

            self.loggedInStatus = True

        except Exception as ex:
            if self._connection:
                self._database.close()
                self._connection = None
            logging.info(f'Error logging in: {ex}')
            self.loggedInStatus = False

            if "password authentication failed" in str(ex) or \
                    ("FATAL:  role" in str(ex) and "does not exist" in str(ex)):
                self.passwordFailed.emit()

    @pyqtSlot()
    def logout(self):
        """
        Method to logout of the Postgresql database
        :return:
        """
        try:
            result = self._database.close()
            # TODO Todd Hay - result returns None, as opposed to returning False per the docs, why?
            logging.info(
                f"connection was closed: {result} >> db = {self._database}")
            self._connection = None
            self.loggedInStatus = False

            logging.info(f"db closed? = {self._database.is_closed()}"
                         )  #  returns false, why?

        except Exception as ex:
            pass

    @pyqtProperty(bool, notify=loggedInStatusChanged)
    def loggedInStatus(self):
        """
        Method that returns the loggedInStatus of the user.
        This keeps track of whether the user has established
        a valid connection the Postgresql database
        :return:
        """
        return self._logged_in_status

    @loggedInStatus.setter
    def loggedInStatus(self, value):
        """
        Setting the self._logged_in_status value
        :param value:
        :return:
        """

        if not isinstance(value, bool):
            return

        self._logged_in_status = value
        self.loggedInStatusChanged.emit()

    @pyqtProperty(str, notify=yearChanged)
    def year(self):
        """
        Method to return the self._year variable
        :return:
        """
        return self._year

    @year.setter
    def year(self, value):
        """
        Method to set the year variable.  This is used for keeping track of what year was
        chosen for uploading/QA/QCing the survey data
        :param value: str
        :return:
        """
        if not isinstance(value, str):
            return

        self._year = value
        self.yearChanged.emit()

    @pyqtProperty(str, notify=vesselChanged)
    def vessel(self):
        """
        Method to return the self._vessel variable
        :return:
        """
        return self._vessel

    @vessel.setter
    def vessel(self, value):
        """
        Method to set the vessel variable
        :param value: str
        :return:
        """
        if not isinstance(value, str):
            return

        self._vessel = value
        self.vesselChanged.emit()

    @pyqtProperty(QVariant, notify=haulChanged)
    def haul(self):
        """
        Method to return the currently selected haul
        """
        return self._haul

    @haul.setter
    def haul(self, value):
        """
        Method to set the self._haul value
        :param value:
        :return:
        """
        self._haul = value
        self.haulChanged.emit()

    @pyqtProperty(str, notify=statusBarMessageChanged)
    def statusBarMessage(self):
        """
        Message for returning the self._status_bar_msg
        :return:
        """
        return self._status_bar_msg

    @statusBarMessage.setter
    def statusBarMessage(self, value):
        """
        Method use to set the self._status_bar_msg.  This object is used to control the message of the overall window
        status bar
        :param value:
        :return:
        """
        if not isinstance(value, str):
            return

        self._status_bar_msg = value
        self.statusBarMessageChanged.emit()

    @pyqtProperty(str, notify=modeChanged)
    def mode(self):
        """
        Method to return the mode of the application, which is either set to test or real
        :return:
        """
        return self._mode

    @mode.setter
    def mode(self, value):
        """
        Method to set the mode of the operation
        :param value:
        :return:
        """
        if value not in ["Dev", "Stage", "Prod"]:
            return

        # Change the FRAM_CENTRAL database connection settings
        if value == "Dev":
            self._database_name = "framdev"
            self._port = 5432
        elif value == "Stage":
            self._database_name = "framstg"
            self._port = 5433
        elif value == "Prod":
            self._database_name = "fram"
            self._port = 5433

        self._mode = value

        logging.info(
            f"mode = {self._mode}, db = {self._database_name}, port = {self._port}"
        )

        self.logout()

        self.modeChanged.emit()

    @pyqtProperty(QVariant, notify=wheelhouseDbChanged)
    def wheelhouseProxy(self):
        """
        Method to return the self._wheelhouse_model
        :return:
        """
        return self._wheelhouse_proxy

    def set_wheelhouse_proxy(self, db_file):
        """
        Method to to set the value of self._wheelhouse_proxy.  This is used for attaching to a
        wheelhouse database for interrogating it to populate fram_central tables such as operations, operations_files_mtx,
        etc.
        :return:
        """
        if db_file is None or db_file == "":
            self._wheelhouse_proxy.initialize("")
            self.wheelhouseDbChanged.emit()
            return

        if not isinstance(db_file, str):
            return

        database = SqliteDatabase(db_file, **{})
        self._wheelhouse_proxy.initialize(database)
        self.wheelhouseDbChanged.emit()

    @pyqtProperty(QVariant, notify=sensorsDbChanged)
    def sensorsProxy(self):
        """
        Method to return the self._sensors_proxy.
        :return:
        """
        return self._sensors_proxy

    def set_sensors_proxy(self, db_file):
        """
        Method to set the value of self._sensors_proxy
        :param db:
        :return:
        """
        if db_file is None or db_file == "":
            self._sensors_proxy.initialize("")
            self.sensorsDbChanged.emit()
            return

        if not isinstance(db_file, str):
            return

        try:
            # database = SqliteDatabase(db_file, **{})
            database = APSWDatabase(db_file, **{})
            # database = APSWDatabase(db_file, timeout=5000)   # Sets the setbusytimeout keyword
            self._sensors_proxy.initialize(database)
            self.sensorsDbChanged.emit()
        except Exception as ex:
            logging.info('Error setting the sensor db proxy: {0} > {1}'.format(
                db_file, ex))
            self._sensors_proxy.initialize("")
            self.sensorsDbChanged.emit()

    @pyqtProperty(QVariant, notify=backdeckDbChanged)
    def backdeckProxy(self):
        """
        Method to return self._backdeck_proxy
        :return:
        """
        return self._backdeck_proxy

    def set_backdeck_proxy(self, db_file):
        """
        Methohd to set the value of self._backdeck_proxy
        :param db_file:
        :return:
        """
        if db_file is None or db_file == "":
            self._backdeck_proxy.initialize("")
            self.backdeckDbChanged.emit()
            return

        if not isinstance(db_file, str):
            return

        self._backdeck_database = SqliteDatabase(db_file, **{})
        self._backdeck_proxy.initialize(self._backdeck_database)

        # database = SqliteDatabase(db_file, **{})
        # self._backdeck_proxy.initialize(database)
        self.backdeckDbChanged.emit()

    @pyqtSlot()
    def loadFileManagementModels(self):
        """
        Method called once a user has successfully logged in that populates the three TableViews on the
        FileManagementScreen and the single TableView on the DataCompletenessScreen
        :return:
        """

        # kwargs = {"app": self._app, "year": self._year, "vessel": self._vessel}
        # self._load_management_files_worker = LoadFilesWorker(kwargs=kwargs)
        # self._load_management_files_worker.moveToThread(self._load_management_files_thread)
        # self._load_management_files_worker.loadStatus.connect(self._load_status_received)
        # self._load_management_files_thread.started.connect(self._load_management_files_worker.run)
        # self._load_management_files_thread.start()
        #
        # return

        if not self._scan_files:
            return

        logging.info(
            f"start populating the file management screen, interacts a lot with LOOKUPS"
        )
        self._app.file_management.wheelhouseModel.populate_model()
        logging.info(f"finished wheelhouse")
        self._app.file_management.backdeckModel.populate_model()
        logging.info(f"finished backdeck")
        self._app.file_management.sensorsModel.populate_model()
        logging.info(f"finished sensors")

        self._app.data_completeness.dataCheckModel.populate_model()
        logging.info(f"finished dataCheck")

    def _load_status_received(self, status, msg):
        """
        Method called from the LoadFilesWorkers thread with information about the status of loading the files
        to populate the tables on the FileManagementScreen
        :return:
        """
        logging.info('status, msg: {0}, {1}'.format(status, msg))
        if status:
            logging.info('populating...')
            self._app.file_management.wheelhouseModel.populate_model()
            self._app.file_management.backdeckModel.populate_model()
            self._app.file_management.sensorsModel.populate_model()

        if self._load_management_files_thread.isRunning():
            self._load_management_files_thread.quit()
Ejemplo n.º 22
0
class VNCClient(QObject):
    started = pyqtSignal()
    finished = pyqtSignal()
    imageSizeChanged = pyqtSignal(QSize)
    imageChanged = pyqtSignal(int, int, int, int)
    passwordRequested = pyqtSignal(bool)
    textCut = pyqtSignal(unicode)

    def __init__(self, host, port, settings, parent=None):
        super(VNCClient, self).__init__(parent)
        self.thread = QThread()
        self.moveToThread(self.thread)
        self.host = host
        self.port = port
        self.settings = settings
        self.username = None
        self.password = None
        self.rfb_client = None
        self.socket_notifier = None
        self.thread.started.connect(self._SH_ThreadStarted)
        self.thread.finished.connect(self._SH_ThreadFinished)

    def _get_settings(self):
        return self.__dict__['settings']

    def _set_settings(self, settings):
        old_settings = self.__dict__.get('settings', None)
        if settings == old_settings:
            return
        self.__dict__['settings'] = settings
        if self.thread.isRunning():
            QApplication.postEvent(self, RFBConfigureClientEvent())

    settings = property(_get_settings, _set_settings)
    del _get_settings, _set_settings

    @property
    def image(self):
        return self.rfb_client.image if self.rfb_client is not None else None

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

    def stop(self):
        self.thread.quit()

    def key_event(self, key, down):
        if self.thread.isRunning():
            QApplication.postEvent(self, RFBKeyEvent(key, down))

    def mouse_event(self, x, y, button_mask):
        if self.thread.isRunning():
            QApplication.postEvent(self, RFBMouseEvent(x, y, button_mask))

    def cut_text_event(self, text):
        if text and self.thread.isRunning():
            QApplication.postEvent(self, RFBCutTextEvent(text))

    def _SH_ThreadStarted(self):
        self.started.emit()
        notification_center = NotificationCenter()
        notification_center.post_notification('VNCClientWillStart', sender=self)
        self.rfb_client = RFBClient(parent=self)
        try:
            self.rfb_client.connect()
        except RFBClientError:
            self.thread.quit()
        else:
            self.socket_notifier = QSocketNotifier(self.rfb_client.socket, QSocketNotifier.Read, self)
            self.socket_notifier.activated.connect(self._SH_SocketNotifierActivated)
            notification_center.post_notification('VNCClientDidStart', sender=self)

    def _SH_ThreadFinished(self):
        self.finished.emit()
        notification_center = NotificationCenter()
        notification_center.post_notification('VNCClientWillEnd', sender=self)
        if self.socket_notifier is not None:
            self.socket_notifier.activated.disconnect(self._SH_SocketNotifierActivated)
            self.socket_notifier = None
        self.rfb_client = None
        notification_center.post_notification('VNCClientDidEnd', sender=self)

    def _SH_SocketNotifierActivated(self, sock):
        self.socket_notifier.setEnabled(False)
        try:
            self.rfb_client.handle_server_message()
        except RFBClientError:
            self.thread.quit()
        else:
            self.socket_notifier.setEnabled(True)

    def _SH_ConfigureRFBClient(self):
        if self.rfb_client is not None:
            self.rfb_client.configure()

    def customEvent(self, event):
        handler = getattr(self, '_EH_%s' % event.name, Null)
        handler(event)

    def _EH_RFBConfigureClientEvent(self, event):
        if self.rfb_client is not None:
            self.rfb_client.configure()

    def _EH_RFBKeyEvent(self, event):
        if self.rfb_client is not None:
            self.rfb_client.send_key_event(event.key, event.down)

    def _EH_RFBMouseEvent(self, event):
        if self.rfb_client is not None:
            self.rfb_client.send_pointer_event(event.x, event.y, event.button_mask)

    def _EH_RFBCutTextEvent(self, event):
        if self.rfb_client is not None:
            self.rfb_client.send_client_cut_text(event.text)
Ejemplo n.º 23
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)
Ejemplo n.º 24
0
class Tachy2Gis:
    """QGIS Plugin Implementation."""

    # Custom methods go here:

    def vertexReceived(self, line):
        newVtx = T2G_Vertex.fromGSI(line)
        self.mapTool.addVertex(vtx=newVtx)

    ## Clears the map canvas and in turn the vertexList
    def clearCanvas(self):
        self.mapTool.clear()

    ## Opens the field dialog in preparation of dumping new vertices to the target layer
    def dump(self):

        # the input table of the dialog is updated
        targetLayer = self.dlg.sourceLayerComboBox.currentLayer()
        # if the target layer holds point geometries, only the currently selected vertex is dumped and
        # removed from the list
        project = QgsProject.instance()
        QgsExpressionContextUtils.setProjectVariable(project,
                                                     'maxWerteAktualisieren',
                                                     'False')
        #QgsMessageLog.logMessage('Test', 'T2G Archäologie', Qgis.Info)
        if targetLayer.geometryType() == QgsWkbTypes.PointGeometry:  # Timmel
            for i in range(0, len(self.vertexList)):
                #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'True')
                self.dlg.vertexTableView.selectRow(i)
                self.vertexList.dumpToFile(targetLayer,
                                           self.fieldDialog.fieldData)
                #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'False')
        # self.mapTool.deleteVertex()
        # otherwise the list is cleared
        #self.mapTool.clear()
        else:
            #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'True')
            self.vertexList.dumpToFile(targetLayer, self.fieldDialog.fieldData)
            #QgsExpressionContextUtils.setProjectVariable(project, 'SignalGeometrieNeu', 'False')
            # otherwise the list is cleared
            #self.mapTool.clear()

        #QgsExpressionContextUtils.setProjectVariable(QgsProject.instance(), 'SignalGeometrieNeu', 'False')
        targetLayer.commitChanges()
        # Attributdialog mit Filter zum schreiben öffnen
        # letzte id ermitteln
        idfeld = targetLayer.dataProvider().fieldNameIndex('id')

        if targetLayer.maximumValue(idfeld) == None:
            idmaxi = 0
        else:
            idmaxi = targetLayer.maximumValue(idfeld)

        if targetLayer.geometryType() == QgsWkbTypes.PointGeometry:
            query = "id >= " + str(int(idmaxi) - len(self.vertexList) + 1)
        else:
            query = "id = " + str(int(idmaxi))

        self.messpunktAdd()
        targetLayer.startEditing()
        targetLayer.fields().at(
            0).constraints().constraintDescription(), 'desc'
        #self.iface.openFeatureForm(targetLayer, 3, True)
        box = self.iface.showAttributeTable(targetLayer, query)
        box.exec_()
        self.mapTool.clear()
        targetLayer.commitChanges()
        QgsExpressionContextUtils.setProjectVariable(project,
                                                     'maxWerteAktualisieren',
                                                     'True')
        self.iface.mapCanvas().refreshAllLayers()
        targetLayer.removeSelection()

    def messpunktAdd(self):
        #T2G_Vertex.messliste.clear()
        #T2G_Vertex.messliste.append({'pointId': 1, 'targetX': 1, 'targetY': 1, 'targetZ': 1})
        #T2G_Vertex.messliste.append({'pointId': 2, 'targetX': 1, 'targetY': 1, 'targetZ': 1})
        #T2G_Vertex.messliste.append({'pointId': 3, 'targetX': 1, 'targetY': 1, 'targetZ': 1})
        layer = QgsProject.instance().mapLayersByName('Messpunkte')[0]
        layer.startEditing()
        for item in T2G_Vertex.messliste:
            ptnr = str(item['pointId']).lstrip("0")
            x = str(item['targetX'])
            y = str(item['targetY'])
            z = str(item['targetZ'])
            # Timmel Spiegelhöhe
            try:
                project = QgsProject.instance()
                reflH = QgsExpressionContextUtils.projectScope(
                    project).variable('reflH')
                reflH = float(reflH)
                z = round(float(z) - reflH, 3)
            except:
                pass

            pt = QgsPoint(float(x), float(y), float(z))
            attL = {'ptnr': ptnr, 'x': x, 'y': y, 'z': z, 'reflH': reflH}
            self.addPoint3D(layer, pt, attL)
            QgsMessageLog.logMessage(
                str(ptnr) + '|' + str(x) + '|' + str(y) + '|' + str(z),
                'Messpunkte', Qgis.Info)
        layer.commitChanges()
        layer.removeSelection()

    def addPoint3D(self, layer, point, attListe):
        #layer.startEditing()
        feature = QgsFeature()
        fields = layer.fields()
        feature.setFields(fields)
        feature.setGeometry(QgsGeometry(point))
        # Attribute
        layer.dataProvider().addFeatures([feature])
        layer.updateExtents()
        features = [feature for feature in layer.getFeatures()]
        lastfeature = features[-1]

        for item in attListe:
            #QgsMessageLog.logMessage(str(item), 'T2G', Qgis.Info)
            fIndex = layer.dataProvider().fieldNameIndex(item)
            layer.changeAttributeValue(lastfeature.id(), fIndex,
                                       attListe[item])
        T2G_VertexList.addStaticAttribut(self, layer, lastfeature)
        #layer.commitChanges()

    def snap(self):
        if self.dlg.checkBox.isChecked():
            T2G_VertexList.snap = 1
        else:
            T2G_VertexList.snap = 0

    ## Restores the map tool to the one that was active before T2G was started
    #  The pan tool is the default tool used by QGIS
    def restoreTool(self):
        if self.previousTool is None:
            self.previousTool = QgsMapToolPan(self.iface.mapCanvas())
        self.iface.mapCanvas().setMapTool(self.previousTool)
        self.iface.actionSelectRectangle().trigger()

    def setActiveLayer(self):  #Timmel
        if Qt is None:
            return
        activeLayer = self.dlg.sourceLayerComboBox.currentLayer()
        if activeLayer is None or activeLayer.type(
        ) == QgsMapLayer.RasterLayer:
            return
        self.iface.setActiveLayer(activeLayer)
        self.vertexList.updateAnchors(activeLayer)

    def targetChanged(self):
        targetLayer = self.fieldDialog.targetLayerComboBox.setLayer  # Timmel
        self.mapTool.setGeometryType(targetLayer)

    def toggleEdit(self):
        iface.actionToggleEditing().trigger()

    def connectSerial(self):
        port = self.dlg.portComboBox.currentText()
        self.tachyReader.setPort(port)

    def setLog(self):
        logFileName = QFileDialog.getSaveFileName()[0]
        self.dlg.logFileEdit.setText(logFileName)
        self.tachyReader.setLogfile(logFileName)

    def dumpEnabled(self):
        verticesAvailable = (len(self.vertexList) > 0
                             )  # tim > durch >= ersetzt
        # Selecting a target layer while there are no vertices in the vertex list may cause segfaults. To avoid this,
        # the 'Dump' button is disabled as long there are none:
        self.dlg.dumpButton.setEnabled(verticesAvailable)

    # Interface code goes here:
    def setupControls(self):
        """This method connects all controls in the UI to their callbacks.
        It is called in ad_action"""

        portNames = [
            port.portName() for port in QSerialPortInfo.availablePorts()
        ]
        self.dlg.portComboBox.addItems(portNames)
        self.dlg.portComboBox.currentIndexChanged.connect(self.connectSerial)

        self.dlg.logFileButton.clicked.connect(self.setLog)

        self.dlg.deleteAllButton.clicked.connect(self.clearCanvas)
        self.dlg.finished.connect(self.mapTool.clear)
        self.dlg.dumpButton.clicked.connect(self.dump)
        self.dlg.deleteVertexButton.clicked.connect(self.mapTool.deleteVertex)

        self.dlg.vertexTableView.setModel(self.vertexList)
        self.dlg.vertexTableView.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.dlg.vertexTableView.setSelectionModel(
            QItemSelectionModel(self.vertexList))
        self.dlg.vertexTableView.selectionModel().selectionChanged.connect(
            self.mapTool.selectVertex)

        self.dlg.finished.connect(self.restoreTool)
        self.dlg.accepted.connect(self.restoreTool)
        self.dlg.rejected.connect(self.restoreTool)

        self.dlg.sourceLayerComboBox.layerChanged.connect(self.setActiveLayer)
        self.dlg.sourceLayerComboBox.layerChanged.connect(self.mapTool.clear)

        self.fieldDialog.targetLayerComboBox.layerChanged.connect(
            self.targetChanged)
        self.vertexList.layoutChanged.connect(self.dumpEnabled)

        self.dlg.checkBox.stateChanged.connect(self.snap)

    ## 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.
    def __init__(self, iface):
        # 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',
                                   'Tachy2Gis_{}.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('&Tachy2GIS')
        self.toolbar = self.iface.addToolBar('Tachy2Gis')
        self.toolbar.setObjectName('Tachy2Gis')

        ## From here: Own additions
        self.vertexList = T2G_VertexList()

        self.mapTool = T2G_VertexePickerTool(self)
        self.previousTool = None
        self.fieldDialog = FieldDialog(self.iface.activeLayer())
        self.tachyReader = TachyReader(QSerialPort.Baud9600)
        self.pollingThread = QThread()
        self.tachyReader.moveToThread(self.pollingThread)
        self.pollingThread.start()
        self.tachyReader.lineReceived.connect(self.vertexReceived)
        self.tachyReader.beginListening()

    # 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('Tachy2Gis', 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
        """

        # Create the dialog (after translation) and keep reference
        self.dlg = Tachy2GisDialog(
            iface.mainWindow())  # Dialog im Vordergrund halten.
        self.setupControls()

        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.iface.addToolBarIcon(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 = ':/plugins/Tachy2Gis/icon.png'
        self.add_action(icon_path,
                        text=self.tr('Tachy2GIS'),
                        callback=self.run,
                        parent=self.iface.mainWindow())

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(self.tr('&Tachy2GIS'), action)
            self.iface.removeToolBarIcon(action)
        # remove the toolbar
        del self.toolbar
        if self.pollingThread.isRunning():
            self.tachyReader.shutDown()
            self.pollingThread.terminate()
            self.pollingThread.wait()

    def run(self):
        """Run method that performs all the real work"""
        # Store the active map tool and switch to the T2G_VertexPickerTool
        self.previousTool = self.iface.mapCanvas().mapTool()
        self.iface.mapCanvas().setMapTool(self.mapTool)
        self.mapTool.alive = True
        #self.setActiveLayer()
        self.dlg.sourceLayerComboBox.setLayer(self.iface.activeLayer())
        self.dlg.show()

        outLayerList = []
        self.layerLine = QgsProject.instance().mapLayersByName('E_Line')[0]
        self.layerPoly = QgsProject.instance().mapLayersByName('E_Polygon')[0]
        self.layerPoint = QgsProject.instance().mapLayersByName('E_Point')[0]
        for lay in QgsProject.instance().mapLayers().values():
            if lay == self.layerLine or lay == self.layerPoly or lay == self.layerPoint:
                QgsMessageLog.logMessage('gleich', 'T2G Archäologie',
                                         Qgis.Info)
                pass
            else:
                QgsMessageLog.logMessage(lay.name(), 'T2G Archäologie',
                                         Qgis.Info)
                outLayerList.append(lay)
        self.dlg.sourceLayerComboBox.setExceptedLayerList(outLayerList)
Ejemplo n.º 25
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)
Ejemplo n.º 26
0
class GalleryDialog(QWidget):
	"""
	A window for adding/modifying gallery.
	Pass a list of QModelIndexes to edit their data
	or pass a path to preset path
	"""

	SERIES = pyqtSignal(list)
	SERIES_EDIT = pyqtSignal(list, int)
	#gallery_list = [] # might want to extend this to allow mass gallery adding

	def __init__(self, parent=None, arg=None):
		super().__init__(parent, Qt.Dialog)
		self.setAttribute(Qt.WA_DeleteOnClose)
		self.parent_widget = parent
		log_d('Triggered Gallery Edit/Add Dialog')
		m_l = QVBoxLayout()
		self.main_layout = QVBoxLayout()
		dummy = QWidget(self)
		scroll_area = QScrollArea(self)
		scroll_area.setWidgetResizable(True)
		scroll_area.setFrameStyle(scroll_area.StyledPanel)
		dummy.setLayout(self.main_layout)
		scroll_area.setWidget(dummy)
		m_l.addWidget(scroll_area, 3)

		final_buttons = QHBoxLayout()
		final_buttons.setAlignment(Qt.AlignRight)
		m_l.addLayout(final_buttons)
		self.done = QPushButton("Done")
		self.done.setDefault(True)
		cancel = QPushButton("Cancel")
		final_buttons.addWidget(cancel)
		final_buttons.addWidget(self.done)

		def new_gallery():
			self.setWindowTitle('Add a new gallery')
			self.newUI()
			self.commonUI()
			self.done.clicked.connect(self.accept)
			cancel.clicked.connect(self.reject)

		if arg:
			if isinstance(arg, list):
				self.setWindowTitle('Edit gallery')
				self.position = arg[0].row()
				for index in arg:
					gallery = index.data(Qt.UserRole+1)
					self.commonUI()
					self.setGallery(gallery)
				self.done.clicked.connect(self.accept_edit)
				cancel.clicked.connect(self.reject_edit)
			elif isinstance(arg, str):
				new_gallery()
				self.choose_dir(arg)
		else:
			new_gallery()

		log_d('GalleryDialog: Create UI: successful')
		#TODO: Implement a way to mass add galleries
		#IDEA: Extend dialog in a ScrollArea with more forms...

		self.setLayout(m_l)
		self.resize(500,560)
		frect = self.frameGeometry()
		frect.moveCenter(QDesktopWidget().availableGeometry().center())
		self.move(frect.topLeft())
		#self.setAttribute(Qt.WA_DeleteOnClose)
		self._fetch_inst = fetch.Fetch()
		self._fetch_thread = QThread(self)
		self._fetch_thread.setObjectName("GalleryDialog metadata thread")
		self._fetch_inst.moveToThread(self._fetch_thread)
		self._fetch_thread.started.connect(self._fetch_inst.auto_web_metadata)

	def commonUI(self):
		f_web = QGroupBox("Metadata from the Web")
		f_web.setCheckable(False)
		self.main_layout.addWidget(f_web)
		web_main_layout = QVBoxLayout()
		web_info = misc.ClickedLabel("Which gallery URLs are supported? (hover)", parent=self)
		web_info.setToolTip(app_constants.SUPPORTED_METADATA_URLS)
		web_info.setToolTipDuration(999999999)
		web_main_layout.addWidget(web_info)
		web_layout = QHBoxLayout()
		web_main_layout.addLayout(web_layout)
		f_web.setLayout(web_main_layout)

		f_gallery = QGroupBox("Gallery Info")
		f_gallery.setCheckable(False)
		self.main_layout.addWidget(f_gallery)
		gallery_layout = QFormLayout()
		f_gallery.setLayout(gallery_layout)

		def basic_web(name):
			return QLabel(name), QLineEdit(), QPushButton("Get metadata"), QProgressBar()

		url_lbl, self.url_edit, url_btn, url_prog = basic_web("URL:")
		url_btn.clicked.connect(lambda: self.web_metadata(self.url_edit.text(), url_btn,
											url_prog))
		url_prog.setTextVisible(False)
		url_prog.setMinimum(0)
		url_prog.setMaximum(0)
		web_layout.addWidget(url_lbl, 0, Qt.AlignLeft)
		web_layout.addWidget(self.url_edit, 0)
		web_layout.addWidget(url_btn, 0, Qt.AlignRight)
		web_layout.addWidget(url_prog, 0, Qt.AlignRight)
		self.url_edit.setPlaceholderText("Insert supported gallery URLs or just press the button!")
		url_prog.hide()

		self.title_edit = QLineEdit()
		self.author_edit = QLineEdit()
		author_completer = misc.GCompleter(self, False, True, False)
		author_completer.setCaseSensitivity(Qt.CaseInsensitive)
		self.author_edit.setCompleter(author_completer)
		self.descr_edit = QTextEdit()
		self.descr_edit.setAcceptRichText(True)
		self.lang_box = QComboBox()
		self.lang_box.addItems(app_constants.G_LANGUAGES)
		self.lang_box.addItems(app_constants.G_CUSTOM_LANGUAGES)
		self._find_combobox_match(self.lang_box, app_constants.G_DEF_LANGUAGE, 0)
		tags_l = QVBoxLayout()
		tag_info = misc.ClickedLabel("How do i write namespace & tags? (hover)", parent=self)
		tag_info.setToolTip("Ways to write tags:\n\nNormal tags:\ntag1, tag2, tag3\n\n"+
					  "Namespaced tags:\nns1:tag1, ns1:tag2\n\nNamespaced tags with one or more"+
					  " tags under same namespace:\nns1:[tag1, tag2, tag3], ns2:[tag1, tag2]\n\n"+
					  "Those three ways of writing namespace & tags can be combined freely.\n"+
					  "Tags are seperated by a comma, NOT whitespace.\nNamespaces will be capitalized while tags"+
					  " will be lowercased.")
		tag_info.setToolTipDuration(99999999)
		tags_l.addWidget(tag_info)
		self.tags_edit = misc.CompleterTextEdit()
		self.tags_edit.setCompleter(misc.GCompleter(self, False, False))
		tags_l.addWidget(self.tags_edit, 3)
		self.tags_edit.setPlaceholderText("Press Tab to autocomplete (Ctrl + E to show popup)")
		self.type_box = QComboBox()
		self.type_box.addItems(app_constants.G_TYPES)
		self._find_combobox_match(self.type_box, app_constants.G_DEF_TYPE, 0)
		#self.type_box.currentIndexChanged[int].connect(self.doujin_show)
		#self.doujin_parent = QLineEdit()
		#self.doujin_parent.setVisible(False)
		self.status_box = QComboBox()
		self.status_box.addItems(app_constants.G_STATUS)
		self._find_combobox_match(self.status_box, app_constants.G_DEF_STATUS, 0)
		self.pub_edit = QDateEdit()
		self.pub_edit.setCalendarPopup(True)
		self.pub_edit.setDate(QDate.currentDate())
		self.path_lbl = misc.ClickedLabel("")
		self.path_lbl.setWordWrap(True)
		self.path_lbl.clicked.connect(lambda a: utils.open_path(a, a) if a else None)

		link_layout = QHBoxLayout()
		self.link_lbl = QLabel("")
		self.link_lbl.setWordWrap(True)
		self.link_edit = QLineEdit()
		link_layout.addWidget(self.link_edit)
		link_layout.addWidget(self.link_lbl)
		self.link_edit.hide()
		self.link_btn = QPushButton("Modify")
		self.link_btn.setFixedWidth(50)
		self.link_btn2 = QPushButton("Set")
		self.link_btn2.setFixedWidth(40)
		self.link_btn.clicked.connect(self.link_modify)
		self.link_btn2.clicked.connect(self.link_set)
		link_layout.addWidget(self.link_btn)
		link_layout.addWidget(self.link_btn2)
		self.link_btn2.hide()

		gallery_layout.addRow("Title:", self.title_edit)
		gallery_layout.addRow("Author:", self.author_edit)
		gallery_layout.addRow("Description:", self.descr_edit)
		gallery_layout.addRow("Language:", self.lang_box)
		gallery_layout.addRow("Tags:", tags_l)
		gallery_layout.addRow("Type:", self.type_box)
		gallery_layout.addRow("Status:", self.status_box)
		gallery_layout.addRow("Publication Date:", self.pub_edit)
		gallery_layout.addRow("Path:", self.path_lbl)
		gallery_layout.addRow("Link:", link_layout)

		self.title_edit.setFocus()

	def resizeEvent(self, event):
		self.tags_edit.setFixedHeight(event.size().height()//8)
		self.descr_edit.setFixedHeight(event.size().height()//12.5)
		return super().resizeEvent(event)

	def _find_combobox_match(self, combobox, key, default):
		f_index = combobox.findText(key, Qt.MatchFixedString)
		if f_index != -1:
			combobox.setCurrentIndex(f_index)
		else:
			combobox.setCurrentIndex(default)

	def setGallery(self, gallery):
		"To be used for when editing a gallery"
		self.gallery = gallery

		self.url_edit.setText(gallery.link)

		self.title_edit.setText(gallery.title)
		self.author_edit.setText(gallery.artist)
		self.descr_edit.setText(gallery.info)

		self.tags_edit.setText(utils.tag_to_string(gallery.tags))


		self._find_combobox_match(self.lang_box, gallery.language, 2)
		self._find_combobox_match(self.type_box, gallery.type, 0)
		self._find_combobox_match(self.status_box, gallery.status, 0)

		gallery_pub_date = "{}".format(gallery.pub_date).split(' ')
		try:
			self.gallery_time = datetime.strptime(gallery_pub_date[1], '%H:%M:%S').time()
		except IndexError:
			pass
		qdate_pub_date = QDate.fromString(gallery_pub_date[0], "yyyy-MM-dd")
		self.pub_edit.setDate(qdate_pub_date)

		self.link_lbl.setText(gallery.link)
		self.path_lbl.setText(gallery.path)

	def newUI(self):

		f_local = QGroupBox("Directory/Archive")
		f_local.setCheckable(False)
		self.main_layout.addWidget(f_local)
		local_layout = QHBoxLayout()
		f_local.setLayout(local_layout)

		choose_folder = QPushButton("From Directory")
		choose_folder.clicked.connect(lambda: self.choose_dir('f'))
		local_layout.addWidget(choose_folder)

		choose_archive = QPushButton("From Archive")
		choose_archive.clicked.connect(lambda: self.choose_dir('a'))
		local_layout.addWidget(choose_archive)

		self.file_exists_lbl = QLabel()
		local_layout.addWidget(self.file_exists_lbl)
		self.file_exists_lbl.hide()

	def choose_dir(self, mode):
		"""
		Pass which mode to open the folder explorer in:
		'f': directory
		'a': files
		Or pass a predefined path
		"""
		self.done.show()
		self.file_exists_lbl.hide()
		if mode == 'a':
			name = QFileDialog.getOpenFileName(self, 'Choose archive',
											  filter=utils.FILE_FILTER)
			name = name[0]
		elif mode == 'f':
			name = QFileDialog.getExistingDirectory(self, 'Choose folder')
		elif mode:
			if os.path.exists(mode):
				name = mode
			else:
				return None
		if not name:
			return
		head, tail = os.path.split(name)
		name = os.path.join(head, tail)
		parsed = utils.title_parser(tail)
		self.title_edit.setText(parsed['title'])
		self.author_edit.setText(parsed['artist'])
		self.path_lbl.setText(name)
		if not parsed['language']:
			parsed['language'] = app_constants.G_DEF_LANGUAGE
		l_i = self.lang_box.findText(parsed['language'])
		if l_i != -1:
			self.lang_box.setCurrentIndex(l_i)
		if gallerydb.GalleryDB.check_exists(name):
			self.file_exists_lbl.setText('<font color="red">Gallery already exists.</font>')
			self.file_exists_lbl.show()
		# check galleries
		gs = 1
		if name.endswith(utils.ARCHIVE_FILES):
			gs = len(utils.check_archive(name))
		elif os.path.isdir(name):
			g_dirs, g_archs = utils.recursive_gallery_check(name)
			gs = len(g_dirs) + len(g_archs)
		if gs == 0:
			self.file_exists_lbl.setText('<font color="red">Invalid gallery source.</font>')
			self.file_exists_lbl.show()
			self.done.hide()
		if app_constants.SUBFOLDER_AS_GALLERY:
			if gs > 1:
				self.file_exists_lbl.setText('<font color="red">More than one galleries detected in source! Use other methods to add.</font>')
				self.file_exists_lbl.show()
				self.done.hide()

	def check(self):
		if len(self.title_edit.text()) is 0:
			self.title_edit.setFocus()
			self.title_edit.setStyleSheet("border-style:outset;border-width:2px;border-color:red;")
			return False
		elif len(self.author_edit.text()) is 0:
			self.author_edit.setText("Unknown")

		if len(self.path_lbl.text()) == 0 or self.path_lbl.text() == 'No path specified':
			self.path_lbl.setStyleSheet("color:red")
			self.path_lbl.setText('No path specified')
			return False

		return True

	def set_chapters(self, gallery_object, add_to_model=True):
		path = gallery_object.path
		chap_container = gallerydb.ChaptersContainer(gallery_object)
		metafile = utils.GMetafile()
		try:
			log_d('Listing dir...')
			con = scandir.scandir(path) # list all folders in gallery dir
			log_i('Gallery source is a directory')
			log_d('Sorting')
			chapters = sorted([sub.path for sub in con if sub.is_dir() or sub.name.endswith(utils.ARCHIVE_FILES)]) #subfolders
			# if gallery has chapters divided into sub folders
			if len(chapters) != 0:
				log_d('Chapters divided in folders..')
				for ch in chapters:
					chap = chap_container.create_chapter()
					chap.title = utils.title_parser(ch)['title']
					chap.path = os.path.join(path, ch)
					metafile.update(utils.GMetafile(chap.path))
					chap.pages = len(list(scandir.scandir(chap.path)))

			else: #else assume that all images are in gallery folder
				chap = chap_container.create_chapter()
				chap.title = utils.title_parser(os.path.split(path)[1])['title']
				chap.path = path
				metafile.update(utils.GMetafile(path))
				chap.pages = len(list(scandir.scandir(path)))

		except NotADirectoryError:
			if path.endswith(utils.ARCHIVE_FILES):
				gallery_object.is_archive = 1
				log_i("Gallery source is an archive")
				archive_g = sorted(utils.check_archive(path))
				for g in archive_g:
					chap = chap_container.create_chapter()
					chap.path = g
					chap.in_archive = 1
					metafile.update(utils.GMetafile(g, path))
					arch = utils.ArchiveFile(path)
					chap.pages = len(arch.dir_contents(g))
					arch.close()

		metafile.apply_gallery(gallery_object)
		if add_to_model:
			self.SERIES.emit([gallery_object])
			log_d('Sent gallery to model')
		

	def reject(self):
		if self.check():
			msgbox = QMessageBox()
			msgbox.setText("<font color='red'><b>Noo oniichan! You were about to add a new gallery.</b></font>")
			msgbox.setInformativeText("Do you really want to discard?")
			msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
			msgbox.setDefaultButton(QMessageBox.No)
			if msgbox.exec() == QMessageBox.Yes:
				self.close()
		else:
			self.close()

	def web_metadata(self, url, btn_widget, pgr_widget):
		self.link_lbl.setText(url)
		btn_widget.hide()
		pgr_widget.show()

		def status(stat):
			def do_hide():
				try:
					pgr_widget.hide()
					btn_widget.show()
				except RuntimeError:
					pass

			if stat:
				do_hide()
			else:
				danger = """QProgressBar::chunk {
					background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );
					border-bottom-right-radius: 5px;
					border-bottom-left-radius: 5px;
					border: .px solid black;}"""
				pgr_widget.setStyleSheet(danger)
				QTimer.singleShot(3000, do_hide)

		def gallery_picker(gallery, title_url_list, q):
			self.parent_widget._web_metadata_picker(gallery, title_url_list, q, self)

		try:
			dummy_gallery = self.make_gallery(self.gallery)
		except AttributeError:
			dummy_gallery = self.make_gallery(gallerydb.Gallery(), False)
		if not dummy_gallery:
			status(False)
			return None

		dummy_gallery._g_dialog_url = url
		self._fetch_inst.galleries = [dummy_gallery]
		self._disconnect()
		self._fetch_inst.GALLERY_PICKER.connect(gallery_picker)
		self._fetch_inst.GALLERY_EMITTER.connect(self.set_web_metadata)
		self._fetch_inst.FINISHED.connect(status)
		self._fetch_thread.start()
			
	def set_web_metadata(self, metadata):
		assert isinstance(metadata, gallerydb.Gallery)
		self.link_lbl.setText(metadata.link)
		self.title_edit.setText(metadata.title)
		self.author_edit.setText(metadata.artist)
		tags = ""
		lang = ['English', 'Japanese']
		self._find_combobox_match(self.lang_box, metadata.language, 2)
		self.tags_edit.setText(utils.tag_to_string(metadata.tags))
		pub_string = "{}".format(metadata.pub_date)
		pub_date = QDate.fromString(pub_string.split()[0], "yyyy-MM-dd")
		self.pub_edit.setDate(pub_date)
		self._find_combobox_match(self.type_box, metadata.type, 0)

	def make_gallery(self, new_gallery, add_to_model=True, new=False):
		if self.check():
			new_gallery.title = self.title_edit.text()
			log_d('Adding gallery title')
			new_gallery.artist = self.author_edit.text()
			log_d('Adding gallery artist')
			log_d('Adding gallery path')
			if new and app_constants.MOVE_IMPORTED_GALLERIES:
				app_constants.OVERRIDE_MONITOR = True
				new_gallery.path = utils.move_files(self.path_lbl.text())
			else:
				new_gallery.path = self.path_lbl.text()
			new_gallery.info = self.descr_edit.toPlainText()
			log_d('Adding gallery descr')
			new_gallery.type = self.type_box.currentText()
			log_d('Adding gallery type')
			new_gallery.language = self.lang_box.currentText()
			log_d('Adding gallery lang')
			new_gallery.status = self.status_box.currentText()
			log_d('Adding gallery status')
			new_gallery.tags = utils.tag_to_dict(self.tags_edit.toPlainText())
			log_d('Adding gallery: tagging to dict')
			qpub_d = self.pub_edit.date().toString("ddMMyyyy")
			dpub_d = datetime.strptime(qpub_d, "%d%m%Y").date()
			try:
				d_t = self.gallery_time
			except AttributeError:
				d_t = datetime.now().time().replace(microsecond=0)
			dpub_d = datetime.combine(dpub_d, d_t)
			new_gallery.pub_date = dpub_d
			log_d('Adding gallery pub date')
			new_gallery.link = self.link_lbl.text()
			log_d('Adding gallery link')
			if not new_gallery.chapters:
				log_d('Starting chapters')
				thread = threading.Thread(target=self.set_chapters, args=(new_gallery,add_to_model), daemon=True)
				thread.start()
				thread.join()
				log_d('Finished chapters')
			return new_gallery


	def link_set(self):
		t = self.link_edit.text()
		self.link_edit.hide()
		self.link_lbl.show()
		self.link_lbl.setText(t)
		self.link_btn2.hide()
		self.link_btn.show() 

	def link_modify(self):
		t = self.link_lbl.text()
		self.link_lbl.hide()
		self.link_edit.show()
		self.link_edit.setText(t)
		self.link_btn.hide()
		self.link_btn2.show()

	def _disconnect(self):
		try:
			self._fetch_inst.GALLERY_PICKER.disconnect()
			self._fetch_inst.GALLERY_EMITTER.disconnect()
			self._fetch_inst.FINISHED.disconnect()
		except TypeError:
			pass

	def delayed_close(self):
		if self._fetch_thread.isRunning():
			self._fetch_thread.finished.connect(self.close)
			self.hide()
		else:
			self.close()

	def accept(self):
		new_gallery = self.make_gallery(gallerydb.Gallery(), new=True)

		if new_gallery:
			self.delayed_close()

	def accept_edit(self):
		new_gallery = self.make_gallery(self.gallery)
		#for ser in self.gallery:
		if new_gallery:
			self.SERIES_EDIT.emit([new_gallery], self.position)
			self.delayed_close()

	def reject_edit(self):
		self.delayed_close()
Ejemplo n.º 27
0
class Application():
    def __init__(self, get_resources):
        self.get_resources = get_resources  # Get resources
        config.initiate()  # Initiate configurations
        self.start_timer_thread()  # Start timer on separate thread
        self.start_application()  # Start application

    def start_timer_thread(self):
        # create Worker and Thread
        self.worker = Worker()  # no parent!
        self.thread = QThread()  # no parent!

        # Move the Worker object to the Thread object
        self.worker.moveToThread(self.thread)

        # Connect Thread started signal to Worker operational slot method
        self.thread.started.connect(self.worker.start)

        # Connect Worker`s Signals to update progressbar.
        self.worker.fire_alarm.connect(self.fire_alarm)
        self.thread.start()

    def start_application(self):
        app = QApplication(sys.argv)

        self.trayIcon = QSystemTrayIcon(
            QIcon("src/main/resources/base/alarm-clock.svg"), app)
        self.menu = QMenu()

        self.configureAction = self.menu.addAction('Configure')
        self.configureAction.triggered.connect(self.configure)

        self.notifyAction = self.menu.addAction('Notification')
        self.notifyAction.triggered.connect(self.restart_timer)

        self.quitAction = self.menu.addAction('Quit')
        self.quitAction.triggered.connect(self.exit)

        self.trayIcon.setContextMenu(self.menu)
        self.trayIcon.show()
        sys.exit(app.exec_())

    def restart_timer(self):
        for timer in self.worker.timers:
            # Stop all timers
            timer.stop()

        self.thread.disconnect()
        self.thread.quit(
        )  # This is very important(Quit thread b4 restarting it)
        self.thread.started.connect(self.worker.start)
        while self.thread.isRunning():
            pass
        else:
            self.thread.start()

    def fire_alarm(self, time, message):
        alarm_data = {'time': time, 'message': message}
        self.notification = Notification(self.get_resources, alarm_data)
        self.notification.show()

    def configure(self):
        self.preference = Preferences()
        self.preference.show()

    def exit(self):
        sys.exit()
Ejemplo n.º 28
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.scrollbar.valueChanged.connect(self.maybe_enable_buttons)

        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 maybe_enable_buttons(self, scrollbar_value):
        if scrollbar_value == self.scrollbar.maximum():
            self.copy_button.setEnabled(True)
            self.export_button.setEnabled(True)
        else:
            self.copy_button.setEnabled(False)
            self.export_button.setEnabled(False)

    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.maybe_enable_buttons(self.scrollbar.value())
        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()

    def resizeEvent(self, _):
        self.maybe_enable_buttons(self.scrollbar.value())
Ejemplo n.º 29
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        loadUi('open.ui', self)
        self.setWindowTitle('OpenParty Server')
        self.totalFrames = 0
        self.fps = 0
        self.openButton.clicked.connect(self.open_file)

        self.threadVideoGen = QThread()
        self.threadVideoPlay = QThread()
        self.threadAudio = QThread()
        self.threadChat = QThread()

        self.volumeSlider.setMinimum(0)
        self.volumeSlider.setMaximum(100)
        self.volumeSlider.setValue(50)
        self.volumeSlider.sliderReleased.connect(self.set_volume)

        self.process_name = psutil.Process(os.getpid()).name()
        self.volume_set = False

        self.session_active = True
        while not self.session_active:
            pass

        self.is_fullscreen = False

    # full screen UI on double click
    def mouseDoubleClickEvent(self, e):  # double click
        try:
            if not self.is_fullscreen:
                self.showFullScreen()
                self.is_fullscreen = True
            elif self.is_fullscreen:
                self.showNormal()
                self.is_fullscreen = False
        except Exception as e:
            logging.error('double click err: {}'.format(e))

    # initialize threads for each component
    def start_video_gen(self):
        self.threadVideoGen = VideoGen(self.cap, self.q, self.totalFrames)
        self.threadVideoGen.start()

    def start_audio(self):
        self.threadAudio = LocalAudio(self.playButton, self.stopButton,
                                      self.progressBar,
                                      self.audioProgressLabel, self.fps)
        self.threadAudio.start()

    def start_video_play(self):
        self.threadVideoPlay = PlayVideo(self.cap, self.q, self.progresslabel,
                                         self.progressBar, self.frame,
                                         self.totalFrames, self.fps,
                                         self.playButton, self.stopButton,
                                         self.fpsLabel, self.threadVideoGen,
                                         self.threadAudio)
        self.threadVideoPlay.start()

    def start_tcp_chat(self):
        if not self.threadChat.isRunning():
            print('starting chat thread...')
            self.threadChat = TcpChat(self.threadVideoPlay, self.threadAudio,
                                      self.chatBox, self.messageBox)
            self.threadChat.start()
        else:
            self.threadChat.update_threads(self.threadVideoPlay,
                                           self.threadAudio)

    # after opening file start threads for each component
    def open_file(self):
        # self.frame.mouseDoubleClickEvent = mouseDoubleClickEvent
        try:
            self.videoFileName = QFileDialog.getOpenFileName(
                self, 'Select Video File')
            self.file_name = list(self.videoFileName)[0]

            # self.file_name = "C:\\repos\\OpenParty\\app\\server\\vids\\Nigt of the living dead.mp4"
            if not self.file_name == "":
                if self.threadVideoPlay.isRunning():
                    self.threadVideoPlay.stopSignal.emit()
                if self.threadAudio.isRunning():
                    self.threadAudio.stopSignal.emit()

                if self.threadAudio.isRunning():
                    self.threadAudio.destroy()
                if self.threadVideoPlay.isRunning():
                    self.threadVideoPlay.destroy()
                if self.threadVideoGen.isRunning():
                    self.threadVideoGen.destroy()

                self.threadVideoGen = QThread()
                self.threadVideoPlay = QThread()
                self.threadAudio = QThread()

                self.cap = cv2.VideoCapture(self.file_name)
                # if not self.cap:
                #     raise Exception('file not valid')
                self.fps = self.cap.get(cv2.CAP_PROP_FPS)
                self.q = queue.Queue(maxsize=1000)
                self.totalFrames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))

                print('Opening file {} with fps {}'.format(
                    list(self.file_name)[0], self.fps))

                # extract and convert audio from the video file into a temp.wav to be sent
                # set the bitrate, number of channels, sample size and overwrite old file with same name
                command = "ffmpeg.exe -i \"{}\" -ab 160k -ac 2 -ar 44100 -vn {} -y".format(
                    self.file_name, 'temp.wav')
                os.system(command)

                self.start_video_gen()
                self.start_audio()
                self.start_video_play()
                self.start_tcp_chat()

                for session in AudioUtilities.GetAllSessions():
                    if session.Process and session.Process.name(
                    ) == self.process_name:
                        self.volume = session.SimpleAudioVolume
                        self.volume.SetMasterVolume(0.5, None)
                        self.volumeSlider.setValue(50)
                        self.volume_set = True
        except Exception as e:
            logging.error(e)

    def set_volume(self):
        if self.volume_set:
            value = self.volumeSlider.value()
            self.volume.SetMasterVolume(value / 100, None)
        else:
            self.volumeSlider.setValue(50)

    # when exiting the UI make sure the threads are closed
    def closeEvent(self, event):
        try:
            print('Session ended')
            self.threadChat.terminate()
            if self.threadVideoPlay.isRunning():
                self.threadVideoPlay.destroy()
            if self.threadAudio.isRunning():
                self.threadAudio.destroy()
            if self.threadVideoGen.isRunning():
                self.threadVideoGen.destroy()
        finally:
            os._exit(1)
Ejemplo n.º 30
0
class QtCodeGen(QtWidgets.QDialog):
    # Folgende Widgets stehen zur Verfügung:
    def __init__(self, kontaktdaten: dict, ROOT_PATH: str, parent = None):
        super().__init__(parent)
        uic.loadUi(os.path.join(PATH, "ui_qtcodegen.ui"), self)
        self.setupUi(self, ROOT_PATH)
        
        self.parent = parent
        self._hardClose = False
        
        # Attribute erstellen
        self.kontaktdaten = kontaktdaten
        self.ROOT_PATH = ROOT_PATH

        # std.out & error auf das Textfeld umleiten
        sys.stdout = EigenerStream(text_schreiben=self.update_ausgabe)
        sys.stderr = EigenerStream(text_schreiben=self.update_ausgabe)

        # Entsprechend Konfigurieren
        self.setup_thread()
        self.thread.start()
        
        # show gui
        self.show()
  
    def __del__(self):
        print("QtCodeGen destruct")
        
    def setupUi(self, QtCodeGen, ROOT_PATH):
        self.setObjectName("QtCodeGen")
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.setModal(False)
        self.resize(700, 300)
        self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint);
        self.setWindowIcon(QIcon(os.path.join(ROOT_PATH, "images/spritze.ico")))

    def setup_thread(self):
        """
        Thread + Worker erstellen und Konfigurieren
        """
        self.thread = QThread(parent=self)
        self.thread.setTerminationEnabled(True)
        self.worker = Worker(self.kontaktdaten, self.ROOT_PATH)
        
        # Worker und Thread verbinden
        self.worker.moveToThread(self.thread)
        
        # Signale setzen
        self.thread.started.connect(self.worker.code_gen)
        self.worker.signalShowInput.connect(self.showInputDlg)
        self.worker.signalShowDlg.connect(self.showDlg)


    def update_ausgabe(self, text):
        """
        Fügt den übergeben Text dem textAusgabe hinzu

        Args:
            text (str): Text welcher hinzukommen soll
        """

        # Austausch der Farbcodes / Ascii Zeichen aus der Shell
        listeCodes = ['\033[95m', '\033[91m', '\033[33m', '\x1b[0m', '\033[94m', '\033[32m', '\033[0m']
        for farbcode in listeCodes:
            if farbcode in text:
                if farbcode == '\033[95m' or farbcode == '\033[91m':
                    text = f"<div style='color:red'>{text}</div>"
                elif farbcode == '\033[33m':
                    text = f"<div style='color:orange'>{text}</div>"
                elif farbcode == '\x1b[0m':
                    text = f"<div>{text}</div>"
                elif farbcode == '\033[94m':
                    text = f"<div style='color:blue'>{text}</div>"
                elif farbcode == '\033[32m':
                    text = f"<div style='color:green'>{text}</div>"
                text = text.replace(farbcode, '')

        cursor = self.textAusgabe.textCursor()
        cursor.movePosition(QtGui.QTextCursor.End)
        cursor.insertHtml(str(text))
        cursor.insertText(str("\n"))
        self.textAusgabe.setTextCursor(cursor)
        self.textAusgabe.ensureCursorVisible()

    def showInputDlg(self, dlgType):
        
        if dlgType == "GEBURTSDATUM":
           while True:
                try:
                    text, ok = QtWidgets.QInputDialog.getText(self, 'Geburtsdatum', 'Bitte trage nachfolgend dein Geburtsdatum im Format DD.MM.YYYY ein.\n'
                        'Beispiel: 02.03.1982\n')
                    if ok:
                        geburtsdatum = str(text)
                        validate_datum(geburtsdatum)
                        self.worker.signalUpdateData.emit("GEBURTSDATUM",geburtsdatum)
                        break
                    else:
                        self.hardClose()
                        break
                except ValidationError as exc:
                    QtWidgets.QMessageBox.critical(self, "Geburtsdatum ungültiges Format", "Das Datum entspricht nicht dem richtigen Format (DD.MM.YYYY).")
 
        elif dlgType == "SMSCODE_OK":
            ret = QtWidgets.QMessageBox.information(self, "Erfolgreich", "Code erfolgreich generiert. Du kannst jetzt mit der Terminsuche fortfahren.",QMessageBox.StandardButton.Ok)
            if ret == QMessageBox.StandardButton.Ok:
                self.worker.signalUpdateData.emit("SMSCODE_OK","")
                self.hardClose()
            
    def showDlg(self, strMode, strTxt):
        if strMode == "MISSING_KONTAKT":
            ret = QtWidgets.QMessageBox.critical(self, "Kontaktdaten ungültig",
                "Die Kontakdaten sind nicht korrekt!.\n\nBitte Datei neu erstellen!", QMessageBox.StandardButton.Ok)
            if ret == QMessageBox.StandardButton.Ok:
                self.hardClose()
        elif strMode == "CRITICAL_CLOSE":
            ret = QtWidgets.QMessageBox.critical(self, "Error", strTxt, QMessageBox.StandardButton.Ok)
            if ret == QMessageBox.StandardButton.Ok:
                self.hardClose()

                
    # force to close the dialog without confirmation
    def hardClose(self):
        self._hardClose = True
        self.close()
         
    def closeEvent(self, event):
        """
        Wird aufgerufen, wenn die Anwendung geschlossen wird
        """

        if self.thread.isRunning():
            if self._hardClose is False:
                res = QtWidgets.QMessageBox.warning(self, "Suche beenden", "Suche wirklich beenden?\n",
                                                    (QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel))

                if res != QMessageBox.StandardButton.Ok:
                    event.ignore()
                    return
                
            
        #exit

        #stop worker
        self.worker.stop()
        self.worker.deleteLater()

        #stop thread
        self.thread.quit()
        self.thread.wait(3000)
        self.thread.terminate()

        # Streams wieder korrigieren, damit kein Fehler kommt
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__

        self.deleteLater()
        event.accept()

    @staticmethod
    def start_code_gen(kontaktdaten: dict,  ROOT_PATH: str):
        app = QtWidgets.QApplication(list())
        window = QtCodeGen(kontaktdaten, ROOT_PATH)
        app.exec_()
Ejemplo n.º 31
0
class ChartWindow(QDialog):
    """ChartWindow(QDialog)

    This class offers an window to draw candlesticks and analysis results.
    """

    def __init__(self, df=None, debug=False, *args):
        """__init__(self, *args) -> None

        initialize this class

        Parameters
        ----------
        df    : pandas.DataFrame
            OHLC dataset
        debug : bool
            if True, then work in the debug mode
        """
        super().__init__(*args)

        self.initInnerParameters(df, debug)
        self.initAnalysisThread()
        self.initGui()
    
    @footprint
    def initInnerParameters(self, df, debug):
        """initInnerParameters(self, df, debug) -> None

        initialize the inner parameters

        Parameters
        ----------
        df    : pandas.DataFrame
            OHLC dataset
        debug : bool
            if True, then work in the debug mode
        """

        # for GUI
        self._window_width = 900 # [pixel]
        self._window_height = 720 # [pixel]
        self._spacing = 5 # [pixel]
        self._groupbox_title_font_size = 14
        self._label_font_size = 14
        self._window_color = "gray"
        self._txt_bg_color = "#D0D3D4"
        self._chk_box_bg_color = "#FFFFFF"

        # for settings
        self._N_ema_min = 1
        self._N_ema_max = 30
        self._btc_volime = 1.
        self._N_ema1 = 20
        self._N_ema2 = 21
        self._delta = 10. # for judgement of extreme maxima / minima
        self._datetimeFmt_BITFLYER = "%Y-%m-%dT%H:%M:%S.%f"
        self._datetimeFmt_BITFLYER_2 = "%Y-%m-%dT%H:%M:%S"
        self._N_dec = 5

        # set DataAdapter
        self._adapter = DataAdapter(df=df, 
            N_ema_max=self._N_ema_max, N_ema_min=self._N_ema_min,
            N_dec=self._N_dec, N_ema1=self._N_ema1, N_ema2=self._N_ema2,
            delta=self._delta, size=self._btc_volime,
        )

        # for inner data
        self.initInnerData()

        # for DEBUG
        self.DEBUG = debug
    
    def initInnerData(self):
        """initInnerData(self) -> None

        initialize the inner data
        """
        pass
    
    def initAnalysisThread(self):
        self._thread_analysis = QThread()
        self._worker_analysis = AnalysisWorker()
        self._worker_analysis.do_something.connect(self.updateAdapterByWorker)
        self._worker_analysis.moveToThread(self._thread_analysis)

        # start
        self._thread_analysis.started.connect(self._worker_analysis.process)

        # finished
        self._worker_analysis.finished.connect(self._thread_analysis.quit)
        # self._thread_analysis.finished.connect(self.checkIsTimerStopped)
    
    def initGui(self):
        """initGui(self) -> None

        initialize the GUI
        """
        self.initMainGrid()

        if self.DEBUG:
            self.setWindowTitle("CandleStick(Emulate)")
        else:
            self.setWindowTitle("CandleStick")
        
        # analysis graphs
        self.analysis_graphs = AnalysisGraphs()
        self.analysis_graphs.setMaximumWidth(self._window_width // 3)

        # chart graphs
        self.chart_graphs = ChartGraphs()

        # Settings
        group_setting, grid_setting = make_groupbox_and_grid(
            self, 40, (self._window_height) // 3,
            "Settings", self._groupbox_title_font_size, self._spacing
        )

        label_ema1 = make_label(
            group_setting, "N1", self._label_font_size, True, Qt.AlignLeft
        )
        grid_setting.addWidget(label_ema1, 0, 0)

        self.le_ema1 = QLineEdit(group_setting)
        self.le_ema1.setText(str(self._N_ema1))
        # font = self.le_ema1.font()
        # font.setPointSize(self._button_font_size)
        # self.le_ema1.setFont(font)
        self.le_ema1.setMaximumWidth(40)
        self.le_ema1.setMaximumHeight(16)
        # self.le_ema1.resize(20, 16)
        self.le_ema1.setStyleSheet("background-color:{};".format(self._txt_bg_color))
        self.le_ema1.setValidator(QIntValidator())
        grid_setting.addWidget(self.le_ema1, 0, 1)

        label_ema2 = make_label(
            group_setting, "N2", self._label_font_size, True, Qt.AlignLeft
        )
        grid_setting.addWidget(label_ema2, 1, 0)

        self.le_ema2 = QLineEdit(group_setting)
        self.le_ema2.setText(str(self._N_ema2))
        # font = self.le_ema2.font()
        # font.setPointSize(self._button_font_size)
        # self.le_ema2.setFont(font)
        self.le_ema2.setMaximumWidth(40)
        self.le_ema2.setMaximumHeight(16)
        # self.le_ema2.resize(20, 16)
        self.le_ema2.setStyleSheet("background-color:{};".format(self._txt_bg_color))
        self.le_ema2.setValidator(QIntValidator())
        grid_setting.addWidget(self.le_ema2, 1, 1)

        label_delta = make_label(
            group_setting, "delta", self._label_font_size, True, Qt.AlignLeft
        )
        grid_setting.addWidget(label_delta, 2, 0)

        self.le_delta = QLineEdit(group_setting)
        self.le_delta.setText(str(self._delta))
        # font = self.le_delta.font()
        # font.setPointSize(self._button_font_size)
        # self.le_delta.setFont(font)
        self.le_delta.setMaximumWidth(40)
        self.le_delta.setMaximumHeight(16)
        self.le_delta.setStyleSheet("background-color:{};".format(self._txt_bg_color))
        self.le_delta.setValidator(QDoubleValidator())
        grid_setting.addWidget(self.le_delta, 2, 1)

        # Results
        group_results, grid_results = make_groupbox_and_grid(
            self, 40, (self._window_height) // 3,
            "Results", self._groupbox_title_font_size, self._spacing
        )
        label_benefit = make_label(
            group_results, "Benefit", self._label_font_size, True, Qt.AlignLeft
        )
        self.label_benefit_value = make_label(
            group_results, "0", self._label_font_size, True, Qt.AlignLeft
        )
        label_days = make_label(
            group_results, "Days", self._label_font_size, True, Qt.AlignLeft
        )
        self.label_days_value = make_label(
            group_results, "0", self._label_font_size, True, Qt.AlignLeft
        )
        label_perday = make_label(
            group_results, "Per day", self._label_font_size, True, Qt.AlignLeft
        )
        self.label_perday_value = make_label(
            group_results, "0", self._label_font_size, True, Qt.AlignLeft
        )

        grid_results.addWidget(label_benefit, 0, 0)
        grid_results.addWidget(self.label_benefit_value, 1, 0)
        grid_results.addWidget(label_days, 2, 0)
        grid_results.addWidget(self.label_days_value, 3, 0)
        grid_results.addWidget(label_perday, 4, 0)
        grid_results.addWidget(self.label_perday_value, 5, 0)

        # Items in debug mode
        if not self.DEBUG:
            self.grid.addWidget(self.analysis_graphs, 0, 0, 2, 2)
            self.grid.addWidget(self.chart_graphs, 0, 2, 2, 3)
            # self.grid.addWidget(self.glw, 0, 0, 3, 5)
            self.grid.addWidget(group_setting, 0, 5, 1, 1)
            self.grid.addWidget(group_results, 1, 5, 1, 1)
        else:
            group_debug, grid_debug = make_groupbox_and_grid(
                self, 60, self._window_height // 3,
                "DEBUG", self._groupbox_title_font_size, self._spacing
            )

            ## start position
            self.le_start = QLineEdit(group_debug)
            self.le_start.setText("0")
            # font = self.le_start.font()
            # font.setPointSize(self._button_font_size)
            # self.le_start.setFont(font)
            self.le_start.resize(40, 16)
            self.le_start.setStyleSheet("background-color:{};".format(self._txt_bg_color))
            self.le_start.setValidator(QIntValidator())

            ## end position
            self.le_end = QLineEdit(group_debug)
            self.le_end.setText("100")
            self.le_end.resize(40, 16)
            self.le_end.setStyleSheet("background-color:{};".format(self._txt_bg_color))
            self.le_end.setValidator(QIntValidator())

            ## checkbox to use the average values of each OHLC as order ltps
            self.chk_use_average = QCheckBox(group_debug)
            pal = QPalette()
            pal.setColor(QPalette.Foreground, QColor(self._chk_box_bg_color))
            # pal.setColor(QPalette.Active, QColor("white"))
            self.chk_use_average.setPalette(pal)
            # self.chk_use_average.setStyleSheet("background-color:{};".format(self._chk_bg_color))
            self.chk_use_average.setChecked(False)
            self.chk_use_average.resize(16, 16)
            # self.chk_use_average.stateChanged.connect(self.setTxtBTCJPYEditState)

            ## update button
            self.button1 = make_pushbutton(
                self, 40, 16, "Update", 14, 
                method=self.update, color=None, isBold=False
            )

            self.button3 = make_pushbutton(
                self, 40, 16, "Analyze", 14, 
                method=self.analyze, color=None, isBold=False
            )

            self.button4 = make_pushbutton(
                self, 40, 16, "View", 14, 
                method=self.drawAnalysisResults, color=None, isBold=False
            )

            ## add
            grid_debug.addWidget(self.le_start, 0, 0)
            grid_debug.addWidget(self.le_end, 1, 0)
            grid_debug.addWidget(self.chk_use_average, 2, 0)
            grid_debug.addWidget(self.button1, 3, 0)
            grid_debug.addWidget(self.button3, 4, 0)
            grid_debug.addWidget(self.button4, 5, 0)

            self.grid.addWidget(self.analysis_graphs, 0, 0, 3, 2)
            self.grid.addWidget(self.chart_graphs, 0, 2, 3, 3)
            # self.grid.addWidget(self.glw, 0, 0, 3, 5)
            self.grid.addWidget(group_setting, 0, 5, 1, 1)
            self.grid.addWidget(group_results, 1, 5, 1, 1)
            self.grid.addWidget(group_debug, 2, 5, 1, 1)
    
    def initMainGrid(self):
        """ initMainWidget(self) -> None

        initialize the main widget and its grid.
        """
        self.resize(self._window_width, self._window_height)
        self.setStyleSheet("background-color:{};".format(self._window_color))
        self.grid = QGridLayout(self)
        self.grid.setSpacing(5)
    
    @pyqtSlot()
    def update(self):
        """update(self) -> None

        update the inner adapter and graphs
        """
        try:
            self.updateInnerAdapter()
            self.updatePlots()
        except Exception as ex:
            _, _2, exc_tb = sys.exc_info()
            # fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
            print("line {}: {}".format(exc_tb.tb_lineno, ex))
    
    def updateInnerAdapter(self):
        """updateInnerAdapter(self) -> None
        
        update the adapter
        """
        if self._adapter.N_ema1 != int(self.le_ema1.text()) or self._N_ema2 != int(self.le_ema2.text()):
            self._adapter.N_ema1 = int(self.le_ema1.text())
            self._adapter.N_ema2 = int(self.le_ema2.text())
        if self._adapter.delta != float(self.le_delta.text()):
            self._adapter.delta = float(self.le_delta.text())
        
        # if need_update:
        self._adapter.initOHLCVData()
        self.updateResults()
    
    def updateResults(self):
        """updateResults(self) -> None

        update the results of trades
        """
        self.label_benefit_value.setText(str(self._adapter.jpy_list[-1]))
        days = len(self._adapter.jpy_list) / 1440.
        self.label_days_value.setText("{0:.1f}".format(days))
        self.label_perday_value.setText("{0:.1f}".format(float(self._adapter.jpy_list[-1]) / days))
    
    @pyqtSlot(object)
    def updateAdapterByWorker(self, obj):
        """updateAdapterByWorker(self, obj) -> None

        update the adapter by the worker
        """
        try:
            assert isinstance(obj, dict) == True, TypeError
        except TypeError as ex:
            print(ex)
            return
        self._adapter.analysis_results = obj
        self.drawAnalysisResults()
    
    def updatePlots(self):
        """updatePlots(self) -> None

        update chart grpahs
        """
        start_ = int(self.le_start.text())
        if start_ >= len(self._adapter.data):
            raise ValueError('start must be smaller than the length of data.')
        end_ = min([int(self.le_end.text()), len(self._adapter.data)])

        obj = self._adapter.dataset_for_chart_graphs(start_, end_)
        self.chart_graphs.updateGraphs(obj)
        
    @pyqtSlot()
    def analyze(self):
        """analyze(self) -> None

        analyze OHLC dataset
        """
        if not self._thread_analysis.isRunning():
            if self.DEBUG:
                print("start thread.")
            self._worker_analysis.adapter = copy.deepcopy(self._adapter)
            self._thread_analysis.start()
        else:
            if self.DEBUG:
                print("Thread is running.")

    @pyqtSlot()
    def drawAnalysisResults(self):
        """drawAnalysisResults(self) -> None

        draw the results of analysis
        """
        obj = self._adapter.dataset_for_analysis_graphs(self._adapter.N_ema1, self._adapter.N_ema2)
        self.analysis_graphs.updateGraphs(obj)
Ejemplo n.º 32
0
class MainWindow(QMainWindow):

    upload_pictures = pyqtSignal(list)

    def __init__(self, **kwargs):
        super(MainWindow, self).__init__(**kwargs)

        load_ui('MainWindow.ui', self)
        self.setWindowTitle('Picup - {}'.format(__version__))

        apikey = get_api_key(self)
        self.upload_in_progress = False
        self.upload_thread = QThread()
        self.upload = Upload(apikey=apikey)
        self.upload_thread.start()
        self.upload.moveToThread(self.upload_thread.thread())

        self.listView_files_model = FileListModel()
        self.listView_files.setModel(self.listView_files_model)

        self.pushButton_close.clicked.connect(self.close)
        self.pushButton_add_picture.clicked.connect(self.add_file)
        self.pushButton_upload.clicked.connect(self.start_upload)
        self.pushButton_clear_list.clicked.connect(
                                        self.listView_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)


    @pyqtSlot()
    def add_file(self):

        if self.dialog.exec_():
            files = self.dialog.selectedFiles()
            self.listView_files_model.add_files(files)

    @pyqtSlot()
    def start_upload(self,):
        print(self.upload_thread.isRunning())
        if (len(self.listView_files_model.files)
            and not self.upload_in_progress):
            self.upload_in_progress = True
            link_dialog = ShowLinks(self.upload,
                                    len(self.listView_files_model.files),
                                    parent=self)
            link_dialog.show()

            self.upload_pictures.emit(self.listView_files_model.files)
            self.listView_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.')

        else:
            logger.debug('There is nothing to upload.')
            QMessageBox.information(self, 'Nüx da', 'Es wurden keine bilder zum hochladen hinzugefügt')

    @pyqtSlot()
    def upload_finished(self):
        self.upload_in_progress = False

    @pyqtSlot(type, tuple)
    def handle_error(self, exception_type, args):
        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 remove_selected(self,):
        for item in self.listView_files.selectedIndexes():
            self.listView_files_model.remove_element(item.row(), item.row())
class XMewindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.key_para = {}

        self.setupUi(self)
        self.run_first = True
        self.draw_first = True
        self.single_run_first = True
        self.initUI()

    def initUI(self):
        self.progressBar.setValue(int(0))

        self.CB_style.addItems(QStyleFactory.keys())  # 下拉框combobox,用来设置样式
        index = self.CB_style.findText(QApplication.style().objectName(),
                                       QtCore.Qt.MatchFixedString)
        self.CB_style.setCurrentIndex(index)
        self.CB_style.activated[str].connect(
            self.handleStyleChanged)  # handleStyleChanged用来修改样式

        self.QRB_open.setChecked(True)

        self.FitMode_box.currentIndexChanged.connect(self.fitModeChanged)

        self.conductance_length_layout = QVBoxLayout(self)
        self.conductance_count_layout = QVBoxLayout(self)
        self.length_layout = QVBoxLayout(self)
        self.single_trace_layout = QVBoxLayout(self)
        self.Run_btn.setEnabled(False)
        self.QPB_save_all.setEnabled(False)
        self.QPB_save_goodtrace.setEnabled(False)
        self.QPB_redraw.setEnabled(False)
        self.QPB_update.setEnabled(False)
        self.getInitParaValue(self)

        self.textBrowser.setPlainText(
            'Please load file firstly, then press run button to analysis.')
        self.Load_btn.clicked.connect(self.getFilepathList)
        self.Run_btn.clicked.connect(self.runButton)
        self.QPB_redraw.clicked.connect(self.reDrawBtn)
        self.QPB_save_all.clicked.connect(self.saveDataAndFig)
        self.QPB_save_goodtrace.clicked.connect(self.saveGoodTrace)
        self.QPB_reset.clicked.connect(self.resetBtn)
        self.QPB_update.clicked.connect(self.updateAdd)

        self.QRB_open.toggled.connect(lambda: self.processState(self.QRB_open))
        self.QRB_close.toggled.connect(
            lambda: self.processState(self.QRB_close))

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self, 'message', "Sure to quit ?",
            QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
            QtWidgets.QMessageBox.No)
        if reply == QtWidgets.QMessageBox.Yes:
            self.saveKeyPara()
            time.sleep(0.5)
            self.showStatusbarMessage('key parameter saved. ')
            print('save key parameter...')
            event.accept()
        else:
            event.ignore()

    def processState(self, btn):
        if btn.text() == 'open':
            if btn.isChecked() == True:
                self.key_para['process_open'] = True
                self.QRB_close.setChecked(False)
            else:
                self.key_para['process_open'] = False
                self.QRB_close.setChecked(True)

        if btn.text() == 'close':
            if btn.isChecked() == True:
                self.key_para['process_open'] = False
                self.QRB_open.setChecked(False)
            else:
                self.key_para['process_open'] = True
                self.QRB_open.setChecked(True)
        print('open: ', str(self.QRB_open.isChecked()), '   close: ',
              str(self.QRB_close.isChecked()))

    def fitModeChanged(self):
        if self.FitMode_box.currentIndex() == 0:
            self.key_para['fit_model'] = 'Fit1'
        elif self.FitMode_box.currentIndex() == 1:
            self.key_para['fit_model'] = 'Fit2'
        elif self.FitMode_box.currentIndex() == 2:
            self.key_para['fit_model'] = 'Fit3'
        print('fit_mode: ', str(self.FitMode_box.currentText()))

    def handleStyleChanged(self, style):
        QApplication.setStyle(style)

    def getInitParaValue(self, MainWindow):
        if os.path.exists('config'):
            _translate = QtCore.QCoreApplication.translate
            config = configparser.ConfigParser()
            config.read('config', encoding='utf-8')
            self.QPT_sampling_rate.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'sampling_rate')))
            self.QPT_strecting_rate.setPlainText(
                _translate("MainWindow",
                           config.get('key_para', 'stretching_rate')))
            self.QPT_high_cut.setPlainText(
                _translate("MainWindow", config.get('key_para', 'high_cut')))
            self.QPT_low_cut.setPlainText(
                _translate("MainWindow", config.get('key_para', 'low_cut')))
            self.QPT_high_length.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'high_len_cut')))
            self.QPT_low_length.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'low_len_cut')))
            self.QPT_add_length.setPlainText(
                _translate("MainWindow", config.get('key_para', 'add_length')))
            self.QPT_biasV.setPlainText(
                _translate("MainWindow", config.get('key_para', 'biasV')))
            self.QPT_zero_set.setPlainText(
                _translate("MainWindow", config.get('key_para', 'zero_set')))

            self.QPT_2D_binsx.setPlainText(
                _translate("MainWindow", config.get('key_para', '2D_bins_x')))
            self.QPT_2D_binsy.setPlainText(
                _translate("MainWindow", config.get('key_para', '2D_bins_y')))
            self.QPT_2D_xlim_l.setPlainText(
                _translate("MainWindow", config.get('key_para', '2D_xlim_l')))
            self.QPT_2D_xlim_r.setPlainText(
                _translate("MainWindow", config.get('key_para', '2D_xlim_r')))
            self.QPT_2D_ylim_l.setPlainText(
                _translate("MainWindow", config.get('key_para', '2D_ylim_l')))
            self.QPT_2D_ylim_r.setPlainText(
                _translate("MainWindow", config.get('key_para', '2D_ylim_r')))

            self.QPT_1D_bins.setPlainText(
                _translate("MainWindow", config.get('key_para', '1D_bins')))

            self.QPT_leng_xlim_l.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'leng_xlim_l')))
            self.QPT_leng_xlim_r.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'leng_xlim_r')))
            self.QPT_leng_bins.setPlainText(
                _translate("MainWindow", config.get('key_para', 'leng_bins')))

            self.QPT_start1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'start1')))
            self.QPT_end1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'end1')))
            self.QPT_start2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'start2')))
            self.QPT_end2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'end2')))
            self.QPT_lower_limit1.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'lower_limit1')))
            self.QPT_upper_limit1.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'upper_limit1')))
            self.QPT_lower_limit2.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'lower_limit2')))
            self.QPT_upper_limit2.setPlainText(
                _translate("MainWindow", config.get('key_para',
                                                    'upper_limit2')))

            self.QPT_f1_a1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_a1')))
            self.QPT_f1_b1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_b1')))
            self.QPT_f1_c1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_c1')))
            self.QPT_f1_d1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_d1')))
            self.QPT_f1_a2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_a2')))
            self.QPT_f1_b2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_b2')))
            self.QPT_f1_c2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_c2')))
            self.QPT_f1_d2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_d2')))
            self.QPT_f1_offset.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f1_offset')))

            self.QPT_f2_a1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_a1')))
            self.QPT_f2_a2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_a2')))
            self.QPT_f2_b1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_b1')))
            self.QPT_f2_b2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_b2')))
            self.QPT_f2_c1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_c1')))
            self.QPT_f2_c2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_c2')))
            self.QPT_f2_d1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_d1')))
            self.QPT_f2_d2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_d2')))
            self.QPT_f2_e1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_e1')))
            self.QPT_f2_e2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_e2')))
            self.QPT_f2_f1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_f1')))
            self.QPT_f2_f2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_f2')))
            self.QPT_f2_offset.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f2_offset')))

            self.QPT_f3_a1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f3_a1')))
            self.QPT_f3_b1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f3_b1')))
            self.QPT_f3_a2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f3_a2')))
            self.QPT_f3_b2.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f3_b2')))
            self.QPT_f3_r1.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f3_r1')))
            self.QPT_f3_offset.setPlainText(
                _translate("MainWindow", config.get('key_para', 'f3_offset')))

            self.showStatusbarMessage(
                'get key parameter value from config file.')
            print('get_config_value...')

    def getPanelValue(self):
        self.key_para['sampling_rate'] = int(
            self.QPT_sampling_rate.toPlainText())
        self.key_para['stretching_rate'] = float(
            self.QPT_strecting_rate.toPlainText())
        self.key_para['high_cut'] = float(self.QPT_high_cut.toPlainText())
        self.key_para['low_cut'] = float(self.QPT_low_cut.toPlainText())
        self.key_para['high_len_cut'] = float(
            self.QPT_high_length.toPlainText())
        self.key_para['low_len_cut'] = float(self.QPT_low_length.toPlainText())
        self.key_para['add_length'] = float(self.QPT_add_length.toPlainText())
        self.key_para['biasV'] = float(self.QPT_biasV.toPlainText())
        self.key_para['zero_set'] = float(self.QPT_zero_set.toPlainText())

        self.key_para['fit_model'] = str(self.FitMode_box.currentText())

        self.key_para['process_open'] = bool(self.QRB_open.isChecked())

        self.key_para['2D_bins_x'] = int(self.QPT_2D_binsx.toPlainText())
        self.key_para['2D_bins_y'] = int(self.QPT_2D_binsy.toPlainText())
        self.key_para['2D_xlim_l'] = float(self.QPT_2D_xlim_l.toPlainText())
        self.key_para['2D_xlim_r'] = float(self.QPT_2D_xlim_r.toPlainText())
        self.key_para['2D_ylim_l'] = float(self.QPT_2D_ylim_l.toPlainText())
        self.key_para['2D_ylim_r'] = float(self.QPT_2D_ylim_r.toPlainText())

        self.key_para['1D_xlim_l'] = float(self.QPT_1D_xlim_l.toPlainText())
        self.key_para['1D_xlim_r'] = float(self.QPT_1D_xlim_r.toPlainText())
        self.key_para['1D_bins'] = int(self.QPT_1D_bins.toPlainText())

        self.key_para['leng_xlim_l'] = float(
            self.QPT_leng_xlim_l.toPlainText())
        self.key_para['leng_xlim_r'] = float(
            self.QPT_leng_xlim_r.toPlainText())
        self.key_para['leng_bins'] = int(self.QPT_leng_bins.toPlainText())

        self.key_para['start1'] = float(self.QPT_start1.toPlainText())
        self.key_para['end1'] = float(self.QPT_end1.toPlainText())
        self.key_para['start2'] = float(self.QPT_start2.toPlainText())
        self.key_para['end2'] = float(self.QPT_end2.toPlainText())
        self.key_para['lower_limit1'] = float(
            self.QPT_lower_limit1.toPlainText())
        self.key_para['upper_limit1'] = float(
            self.QPT_upper_limit1.toPlainText())
        self.key_para['lower_limit2'] = float(
            self.QPT_lower_limit2.toPlainText())
        self.key_para['upper_limit2'] = float(
            self.QPT_upper_limit2.toPlainText())

        self.key_para['f1_a1'] = float(self.QPT_f1_a1.toPlainText())
        self.key_para['f1_a2'] = float(self.QPT_f1_a2.toPlainText())
        self.key_para['f1_b1'] = float(self.QPT_f1_b1.toPlainText())
        self.key_para['f1_b2'] = float(self.QPT_f1_b2.toPlainText())
        self.key_para['f1_c1'] = float(self.QPT_f1_c1.toPlainText())
        self.key_para['f1_c2'] = float(self.QPT_f1_c2.toPlainText())
        self.key_para['f1_d1'] = float(self.QPT_f1_d1.toPlainText())
        self.key_para['f1_d2'] = float(self.QPT_f1_d2.toPlainText())
        self.key_para['f1_offset'] = float(self.QPT_f1_offset.toPlainText())

        self.key_para['f2_a1'] = float(self.QPT_f2_a1.toPlainText())
        self.key_para['f2_b1'] = float(self.QPT_f2_b1.toPlainText())
        self.key_para['f2_c1'] = float(self.QPT_f2_c1.toPlainText())
        self.key_para['f2_d1'] = float(self.QPT_f2_d1.toPlainText())
        self.key_para['f2_e1'] = float(self.QPT_f2_e1.toPlainText())
        self.key_para['f2_f1'] = float(self.QPT_f2_f1.toPlainText())
        self.key_para['f2_a2'] = float(self.QPT_f2_a2.toPlainText())
        self.key_para['f2_b2'] = float(self.QPT_f2_b2.toPlainText())
        self.key_para['f2_c2'] = float(self.QPT_f2_c2.toPlainText())
        self.key_para['f2_d2'] = float(self.QPT_f2_d2.toPlainText())
        self.key_para['f2_e2'] = float(self.QPT_f2_e2.toPlainText())
        self.key_para['f2_f2'] = float(self.QPT_f2_f2.toPlainText())
        self.key_para['f2_offset'] = float(self.QPT_f2_offset.toPlainText())

        self.key_para['f3_a1'] = float(self.QPT_f3_a1.toPlainText())
        self.key_para['f3_b1'] = float(self.QPT_f3_b1.toPlainText())
        self.key_para['f3_a2'] = float(self.QPT_f3_a2.toPlainText())
        self.key_para['f3_b2'] = float(self.QPT_f3_b2.toPlainText())
        self.key_para['f3_r1'] = float(self.QPT_f3_r1.toPlainText())
        self.key_para['f3_offset'] = float(self.QPT_f3_offset.toPlainText())

        self.showStatusbarMessage('Loading panel parameters...')
        print('get_panel_value: ', self.key_para)

    def saveKeyPara(self):
        config = configparser.ConfigParser()
        config.add_section('key_para')
        config.set('key_para', 'sampling_rate',
                   str(self.QPT_sampling_rate.toPlainText()))
        config.set('key_para', 'stretching_rate',
                   str(self.QPT_strecting_rate.toPlainText()))
        config.set('key_para', 'high_cut',
                   str(self.QPT_high_cut.toPlainText()))
        config.set('key_para', 'low_cut', str(self.QPT_low_cut.toPlainText()))
        config.set('key_para', 'high_len_cut',
                   str(self.QPT_high_length.toPlainText()))
        config.set('key_para', 'low_len_cut',
                   str(self.QPT_low_length.toPlainText()))
        config.set('key_para', 'add_length',
                   str(self.QPT_add_length.toPlainText()))
        config.set('key_para', 'biasV', str(self.QPT_biasV.toPlainText()))
        config.set('key_para', 'zero_set',
                   str(self.QPT_zero_set.toPlainText()))

        config.set('key_para', '2D_bins_x',
                   str(self.QPT_2D_binsx.toPlainText()))
        config.set('key_para', '2D_bins_y',
                   str(self.QPT_2D_binsy.toPlainText()))
        config.set('key_para', '2D_xlim_l',
                   str(self.QPT_2D_xlim_l.toPlainText()))
        config.set('key_para', '2D_xlim_r',
                   str(self.QPT_2D_xlim_r.toPlainText()))
        config.set('key_para', '2D_ylim_l',
                   str(self.QPT_2D_ylim_l.toPlainText()))
        config.set('key_para', '2D_ylim_r',
                   str(self.QPT_2D_ylim_r.toPlainText()))

        config.set('key_para', '1D_xlim_l',
                   str(self.QPT_1D_xlim_l.toPlainText()))
        config.set('key_para', '1D_xlim_r',
                   str(self.QPT_1D_xlim_r.toPlainText()))
        config.set('key_para', '1D_bins', str(self.QPT_1D_bins.toPlainText()))

        config.set('key_para', 'leng_xlim_l',
                   str(self.QPT_leng_xlim_l.toPlainText()))
        config.set('key_para', 'leng_xlim_r',
                   str(self.QPT_leng_xlim_r.toPlainText()))
        config.set('key_para', 'leng_bins',
                   str(self.QPT_leng_bins.toPlainText()))

        config.set('key_para', 'start1', str(self.QPT_start1.toPlainText()))
        config.set('key_para', 'end1', str(self.QPT_end1.toPlainText()))
        config.set('key_para', 'start2', str(self.QPT_start2.toPlainText()))
        config.set('key_para', 'end2', str(self.QPT_end2.toPlainText()))
        config.set('key_para', 'lower_limit1',
                   str(self.QPT_lower_limit1.toPlainText()))
        config.set('key_para', 'upper_limit1',
                   str(self.QPT_upper_limit1.toPlainText()))
        config.set('key_para', 'lower_limit2',
                   str(self.QPT_lower_limit2.toPlainText()))
        config.set('key_para', 'upper_limit2',
                   str(self.QPT_upper_limit2.toPlainText()))

        config.set('key_para', 'f1_a1', str(self.QPT_f1_a1.toPlainText()))
        config.set('key_para', 'f1_b1', str(self.QPT_f1_b1.toPlainText()))
        config.set('key_para', 'f1_c1', str(self.QPT_f1_c1.toPlainText()))
        config.set('key_para', 'f1_d1', str(self.QPT_f1_d1.toPlainText()))
        config.set('key_para', 'f1_a2', str(self.QPT_f1_a2.toPlainText()))
        config.set('key_para', 'f1_b2', str(self.QPT_f1_b2.toPlainText()))
        config.set('key_para', 'f1_c2', str(self.QPT_f1_c2.toPlainText()))
        config.set('key_para', 'f1_d2', str(self.QPT_f1_d2.toPlainText()))
        config.set('key_para', 'f1_offset',
                   str(self.QPT_f1_offset.toPlainText()))

        config.set('key_para', 'f2_a1', str(self.QPT_f2_a1.toPlainText()))
        config.set('key_para', 'f2_b1', str(self.QPT_f2_b1.toPlainText()))
        config.set('key_para', 'f2_c1', str(self.QPT_f2_c1.toPlainText()))
        config.set('key_para', 'f2_d1', str(self.QPT_f2_d1.toPlainText()))
        config.set('key_para', 'f2_e1', str(self.QPT_f2_e1.toPlainText()))
        config.set('key_para', 'f2_f1', str(self.QPT_f2_f1.toPlainText()))
        config.set('key_para', 'f2_a2', str(self.QPT_f2_a2.toPlainText()))
        config.set('key_para', 'f2_b2', str(self.QPT_f2_b2.toPlainText()))
        config.set('key_para', 'f2_c2', str(self.QPT_f2_c2.toPlainText()))
        config.set('key_para', 'f2_d2', str(self.QPT_f2_d2.toPlainText()))
        config.set('key_para', 'f2_e2', str(self.QPT_f2_e2.toPlainText()))
        config.set('key_para', 'f2_f2', str(self.QPT_f2_f2.toPlainText()))
        config.set('key_para', 'f2_offset',
                   str(self.QPT_f2_offset.toPlainText()))

        config.set('key_para', 'f3_a1', str(self.QPT_f3_a1.toPlainText()))
        config.set('key_para', 'f3_b1', str(self.QPT_f3_b1.toPlainText()))
        config.set('key_para', 'f3_a2', str(self.QPT_f3_a2.toPlainText()))
        config.set('key_para', 'f3_b2', str(self.QPT_f3_b2.toPlainText()))
        config.set('key_para', 'f3_r1', str(self.QPT_f3_r1.toPlainText()))
        config.set('key_para', 'f3_offset',
                   str(self.QPT_f3_offset.toPlainText()))

        config.write(open('config', 'w'))
        self.showStatusbarMessage('key parameters saved!')

    def showStatusbarMessage(self, sbar):
        date = QDateTime.currentDateTime()
        currentTime = date.toString('yyyy-MM-dd hh:mm:ss')
        self.statusBar().showMessage('[' + currentTime + ']   ' + sbar)

    def showProgressBar(self, pbar):
        self.progressBar.setValue(pbar)

    def showTextBroswer(self, text):
        date = QDateTime.currentDateTime()
        currentTime = date.toString('yyyy-MM-dd hh:mm:ss')
        self.textBrowser.append('[' + currentTime + ']  ' + text)

    def getFilepathList(self):
        date = QDateTime.currentDateTime()
        currentTime = date.toString('yyyy-MM-dd hh:mm:ss')
        openfile_name = QFileDialog.getOpenFileNames(self, '选择文件', "./",
                                                     "TDMS Files(*.tdms)")

        self.key_para['file_path'] = openfile_name[0]

        if len(self.key_para['file_path']) > 1:
            self.textBrowser.append('[' + currentTime + ']  ' + 'File list:')
            for i in self.key_para['file_path']:
                p = i.split('/')[-1]
                self.textBrowser.append(p)
            self.key_para['img_path'] = 'stack_file'
        elif not self.key_para['file_path']:
            QMessageBox.warning(self, 'Warning',
                                'Please select at least one data file !',
                                QMessageBox.Ok)
            self.textBrowser.append('[' + currentTime + ']  ' +
                                    'Data file not selected, please re-select')
            self.showStatusbarMessage('Please select at least one data file. ')
        else:
            self.key_para['img_path'] = self.key_para['file_path'][0].split(
                '/')[-1][:-5]
            self.textBrowser.append('[' + currentTime + ']  ' + 'File list:')
            self.textBrowser.append(
                self.key_para['file_path'][0].split('/')[-1])

        if self.key_para['file_path']:
            self.showStatusbarMessage('Analysis file selected. ')
            self.key_para['load_file_bool'] = True
            self.Run_btn.setEnabled(True)
            print(self.key_para['img_path'])

    def runButton(self):
        self.Run_btn.setEnabled(False)
        self.QPB_save_all.setEnabled(False)
        self.QPB_save_goodtrace.setEnabled(False)
        self.QPB_update.setEnabled(False)

        # if self.run_first:
        #     self.run_first=False
        # else:
        #     self.conductance_length_layout.removeWidget(self.fig1)
        #     self.conductance_length_layout.removeWidget(self.toolbar_c2D)
        #     self.conductance_count_layout.removeWidget(self.fig2)
        #     self.conductance_count_layout.removeWidget(self.toolbar_cc)
        #     self.length_layout.removeWidget(self.fig3)
        #     self.length_layout.removeWidget(self.toolbar_length)
        #     sip.delete(self.fig1)  # sip用来从布局中删除控件
        #     sip.delete(self.toolbar_c2D)
        #     sip.delete(self.fig2)
        #     sip.delete(self.toolbar_cc)
        #     sip.delete(self.fig3)
        #     sip.delete(self.toolbar_length)
        #     print('figure refresh!')
        self.calThread()

    def calThread(self):

        self.start_time = time.perf_counter()
        self.getPanelValue()

        self.showProgressBar(int(0))  # 开始画图前应该去重置进度条

        self._dataThread = QThread()
        self.dataThread = Data_Analysis(self.key_para)  # 计算对象
        self.dataThread.sbar.connect(self.showStatusbarMessage)
        self.dataThread.pbar.connect(self.showProgressBar)
        self.dataThread.tbrow.connect(self.showTextBroswer)
        self.dataThread.run_end.connect(
            self.stopThread)  # 此信号标志着线程中run函数的结束,再将线程结束

        self.dataThread.moveToThread(self._dataThread)
        self._dataThread.started.connect(self.dataThread.run)
        self._dataThread.finished.connect(self.DrawPre)

        self.startThread()  # 执行计算线程

    def startThread(self):
        #   此处开启线程计算
        if self.key_para['load_file_bool']:
            if self.key_para['file_path']:
                self._dataThread.start()
                print('开启计算线程,现在线程状态 :', self._dataThread.isRunning())
        else:
            QMessageBox.warning(self, 'Warning', 'No File!')

    def stopThread(self):
        self._dataThread.quit()
        self._dataThread.wait()
        print('退出计算线程,现在线程状态 :', self._dataThread.isRunning())
        '''
        quit()函数是用来停止QThread的,但是由于Qt本身是事件循环机制,所以在调用完quit()后,QThread可能还没有完全停止.
        此时如果执行delete channel,程序就会报错。在执行quit()后,调用wait()来等待QThread子线程的结束(即从run()函数的返回),
        这样就能保证在清除QThread时,其子线程是停止运行的。

        '''

    def DrawPre(self):
        # if self.dataThread.isFinished():
        # print(self._dataThread.isRunning())
        print('计算进程安全退出,开始计算绘图数据..........')
        self.data = self.dataThread.data
        self.cut_trigger, self.len_cut_trigger, self.select_index=self.dataThread.cut_trigger,\
                                                                  self.dataThread.len_cut_trigger,self.dataThread.select_index
        # print(self.cut_trigger)
        res = self.checkState(self.cut_trigger)
        if not res:
            self.showProgressBar(int(0))
            return
        self.cal2DConductance()

    def checkState(self, cut_trigger):
        if len(cut_trigger) <= 1:
            QMessageBox.warning(
                self, 'Warning',
                'Please select appropriate range of segmentation',
                QMessageBox.Ok)
            self.Run_btn.setEnabled(True)
            return False
        return True

    def updateAdd(self):
        print('additional length长度改变,重新计算绘图数据.......')
        self.getPanelValue()
        self.cal2DConductance()

    def cal2DConductance(self):
        self._drawDataThread = QThread()
        self.drawDataThread = CalDrawData(self.key_para, self.cut_trigger,
                                          self.len_cut_trigger,
                                          self.select_index,
                                          self.data)  # 此处是计算绘图所需数据的进程

        self.drawDataThread.run_end.connect(self.stopDrawThread)
        self.drawDataThread.sbar.connect(self.showStatusbarMessage)
        self.drawDataThread.pbar.connect(self.showProgressBar)

        self.drawDataThread.moveToThread(self._drawDataThread)
        self._drawDataThread.started.connect(self.drawDataThread.run)
        self._drawDataThread.finished.connect(self.startDraw)

        self.startDrawThread()

    def startDrawThread(self):
        self._drawDataThread.start()
        # if not self.cut_trigger:
        #     QMessageBox.warning(self,"Warning","Please select appropriate range")
        #     self.cleanData()
        #     self.Run_btn.setEnabled(True)
        #     '''
        #     因为在函数runButton中,所有按钮都变成了不可用状态,这里当选择的切分范围或者是原始数据
        #     有问题的时候,需要重新选择合适的切分范围。
        #     '''
        # else:
        #     self._drawDataThread.start()
        #     print('开启线程计算绘图所需数据,现在线程状态 :',self._drawDataThread.isRunning())  这里暂时有问题,目前未解决

    def stopDrawThread(self):
        self._drawDataThread.quit()
        self._drawDataThread.wait()
        print('退出绘图所需数据线程,现在线程状态 :', self._drawDataThread.isRunning())

    def cleanData(self):
        pass

    def startDraw(self):
        self.xx, self.yy, self.ll, self.select_cut_trigger, self.mean_trigger1_len,self.mean_trigger2_len=self.drawDataThread.xx,\
                                                                                                          self.drawDataThread.yy,self.drawDataThread.ll,\
                                                                                                          self.drawDataThread.select_cut_trigger,\
                                                                                                          self.drawDataThread.mean_trigger1_len,\
                                                                                                          self.drawDataThread.mean_trigger2_len

        self.QPB_redraw.setEnabled(True)
        self.Draw()

        self.QPB_save_all.setEnabled(True)
        self.QPB_save_goodtrace.setEnabled(True)
        self.QPB_redraw.setEnabled(True)
        self.QPB_update.setEnabled(True)

        time_used = round((time.perf_counter() - self.start_time), 2)
        print("total time used: ", str(time_used))
        self.textBrowser.append('mean_trigger1_length: ' +
                                str(round(self.mean_trigger1_len, 3)) + ' nm')
        self.textBrowser.append('mean_trigger2_length: ' +
                                str(round(self.mean_trigger2_len, 3)) + ' nm')
        self.textBrowser.append('Data analysis time used: ' + str(time_used) +
                                ' s')

        self.Run_btn.setEnabled(True)

    def Draw(self):
        if self.draw_first:
            self.draw_first = False
        else:
            self.conductance_length_layout.removeWidget(self.fig1)
            self.conductance_length_layout.removeWidget(self.toolbar_c2D)
            self.conductance_count_layout.removeWidget(self.fig2)
            self.conductance_count_layout.removeWidget(self.toolbar_cc)
            self.length_layout.removeWidget(self.fig3)
            self.length_layout.removeWidget(self.toolbar_length)
            sip.delete(self.fig1)  # sip用来从布局中删除控件
            sip.delete(self.toolbar_c2D)
            sip.delete(self.fig2)
            sip.delete(self.toolbar_cc)
            sip.delete(self.fig3)
            sip.delete(self.toolbar_length)
            plt.close(
                'all'
            )  # 此句非常重要,matplotlib能够同时管理的figure是有限的,每次重新绘图之前应该对之前创建的figure进行清除,不然内存会占满甚至出现莫名的bug
            print('figure refresh!')

        size = 14
        fontsize = '14'
        cm = plt.cm.coolwarm

        _2D_bins_x = self.key_para['2D_bins_x']
        _2D_bins_y = self.key_para['2D_bins_y']
        _2D_xlim_l = self.key_para['2D_xlim_l']
        _2D_xlim_r = self.key_para['2D_xlim_r']
        _2D_ylim_l = self.key_para['2D_ylim_l']
        _2D_ylim_r = self.key_para['2D_ylim_r']
        _1D_xlim_l = self.key_para['1D_xlim_l']
        _1D_xlim_r = self.key_para['1D_xlim_r']
        _1D_bins = self.key_para['1D_bins']
        _leng_xlim_l = self.key_para['leng_xlim_l']
        _leng_xlim_r = self.key_para['leng_xlim_r']
        _leng_bins = self.key_para['leng_bins']

        trace_n = len(self.cut_trigger)
        select_trace_n = len(self.select_cut_trigger)
        select_ratio = select_trace_n / trace_n * 100
        print('select_ratio : ', select_ratio)
        self.QTB_all_trace.setText(str(trace_n))
        self.QTB_selected_trace.setText(str(select_trace_n))
        self.QTB_ratio.setText(str(round(select_ratio, 2)))
        self.textBrowser.append('Number of individual curves: ' + str(trace_n))
        # print(select_trace_n)

        self.fig1 = Conductance_Figure()
        self.hist_3D, self.x_3D, self.y_3D, image = self.fig1.axes.hist2d(
            self.xx,
            self.yy,
            bins=[_2D_bins_x, _2D_bins_y],
            range=[[_2D_xlim_l, _2D_xlim_r], [_2D_ylim_l, _2D_ylim_r]],
            density=1,
            vmin=0,
            vmax=1,
            cmap=cm)
        self.H_3D, self.x_3Dedges, self.y_3Dedges = np.histogram2d(
            self.xx, self.yy, bins=[500, 1000], range=[[-0.5, 3], [-10, 1]])
        self.fig1.fig.colorbar(image, pad=0.03, aspect=50)
        self.fig1.cursor = Cursor(self.fig1.axes,
                                  useblit=False,
                                  color='red',
                                  linewidth=1)
        self.fig1.fig.canvas.mpl_connect(
            'motion_notify_event',
            lambda event: self.fig1.cursor.onmove(event))
        self.fig1.axes.set_xlabel('Length / nm', fontsize=fontsize)
        self.fig1.axes.set_ylabel('Conductance', fontsize=fontsize)
        self.fig1.fig.tight_layout()
        self.toolbar_c2D = MyNavigationToolbar(
            self.fig1, self.fig1.main_frame)  # 这里需要指定父类
        self.conductance_length_layout.addWidget(self.fig1)
        self.conductance_length_layout.addWidget(self.toolbar_c2D)
        self.Conduct_Length_fig.setLayout(self.conductance_length_layout)

        self.fig2 = Conductance_Figure()
        self.fig2.axes.set_xlabel('Conductance', fontsize=fontsize)
        self.fig2.axes.set_ylabel('Counts', fontsize=fontsize)
        self.fig2.axes.set_xlim((_1D_xlim_l, _1D_xlim_r))
        #self.fig2.axes.set_yticks([])
        self.fig2.axes.grid(True)
        self.count_1D, self.bins_1D, patches = self.fig2.axes.hist(
            self.yy,
            bins=_1D_bins,
            color='green',
            alpha=0.8,
            range=[_1D_xlim_l, _1D_xlim_r])
        self.hist_1D, self.bin_edges_1D = np.histogram(self.yy,
                                                       bins=1100,
                                                       range=[-10, 1])
        ax2 = plt.gca()
        ax2.yaxis.get_major_formatter().set_powerlimits((0, 1))

        self.fig2.cursor = Cursor(self.fig2.axes,
                                  useblit=False,
                                  color='red',
                                  linewidth=1)
        self.fig2.fig.canvas.mpl_connect(
            'motion_notify_event',
            lambda event: self.fig2.cursor.onmove(event))
        self.fig2.fig.tight_layout()
        self.toolbar_cc = MyNavigationToolbar(self.fig2, self.fig2.main_frame)
        self.conductance_count_layout.addWidget(self.fig2)
        self.conductance_count_layout.addWidget(self.toolbar_cc)
        self.Conductance_fig.setLayout(self.conductance_count_layout)

        self.fig3 = Conductance_Figure()
        length_c = np.array(self.ll).reshape(-1)
        mean_ll = np.mean(length_c)
        mu = round(mean_ll, 2)
        sigma = np.std(length_c)
        self.count_len, self.bins_len, patchs = self.fig3.axes.hist(
            length_c,
            density=True,
            bins=_leng_bins,
            range=[_leng_xlim_l, _leng_xlim_r])
        self.hist_len, self.bin_edges_len = np.histogram(length_c,
                                                         bins=100,
                                                         range=[0, 3])
        ax3 = plt.gca()
        ax3.yaxis.get_major_formatter().set_powerlimits((0, 1))

        self.fig3.cursor = Cursor(self.fig3.axes,
                                  useblit=False,
                                  color='red',
                                  linewidth=1)
        self.fig3.fig.canvas.mpl_connect(
            'motion_notify_event',
            lambda event: self.fig3.cursor.onmove(event))
        y = norm.pdf(self.bins_len, mu, sigma)
        self.fig3.axes.set_xlabel('Length / nm', fontsize=fontsize)
        self.fig3.axes.set_ylabel('counts', fontsize=fontsize)
        self.fig3.axes.set_xlim((_leng_xlim_l, _leng_xlim_r))
        #self.fig3.axes.set_yticks([])
        self.fig3.axes.grid(True)
        self.fig3.axes.plot(self.bins_len,
                            y,
                            'r--',
                            label='length: ' + str(mu))
        self.fig3.axes.legend(loc=1)
        self.fig3.fig.tight_layout()
        self.toolbar_length = MyNavigationToolbar(self.fig3,
                                                  self.fig3.main_frame)
        self.length_layout.addWidget(self.fig3)
        self.length_layout.addWidget(self.toolbar_length)
        self.Length_fig.setLayout(self.length_layout)

    def reDrawBtn(self):
        reply = QMessageBox.question(
            self, 'Redraw?',
            'Have you updated the parameters and decided to redraw?',
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.reDraw()
        elif reply == QMessageBox.No:
            pass

    def reDraw(self):
        if self.key_para['load_file_bool']:
            if self.key_para['file_path']:
                self.getPanelValue()
                self.Draw()
                QMessageBox.information(self, 'Information',
                                        'The graph has been update')
        else:
            QMessageBox.warning(self, 'Warning', 'No File!')

    def resetBtn(self):
        reply = QMessageBox.question(
            self, 'Reset?', 'Would you like to reset all parameters?',
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.resetParameter()
        elif reply == QMessageBox.No:
            pass

    def resetParameter(self):
        _translate = QtCore.QCoreApplication.translate
        self.QPT_sampling_rate.setPlainText(_translate("MainWindow", "20000"))
        self.QPT_strecting_rate.setPlainText(_translate("MainWindow", "10"))
        self.QPT_high_cut.setPlainText(_translate("MainWindow", "1.2"))
        self.QPT_low_cut.setPlainText(_translate("MainWindow", "-6.5"))
        self.QPT_high_length.setPlainText(_translate("MainWindow", "-0.3"))
        self.QPT_low_length.setPlainText(_translate("MainWindow", "-6"))
        self.QPT_add_length.setPlainText(_translate("MainWindow", "500"))
        self.QPT_biasV.setPlainText(_translate("MainWindow", "0.1"))
        self.QPT_zero_set.setPlainText(_translate("MainWindow", "-0.3"))

        self.QPT_2D_binsx.setPlainText(_translate("MainWindow", "500"))
        self.QPT_2D_binsy.setPlainText(_translate("MainWindow", "800"))
        self.QPT_2D_xlim_l.setPlainText(_translate("MainWindow", "-0.2"))
        self.QPT_2D_xlim_r.setPlainText(_translate("MainWindow", "2"))
        self.QPT_2D_ylim_l.setPlainText(_translate("MainWindow", "-8"))
        self.QPT_2D_ylim_r.setPlainText(_translate("MainWindow", "1.5"))

        self.QPT_1D_bins.setPlainText(_translate("MainWindow", "800"))
        self.QPT_1D_xlim_l.setPlainText(_translate("MainWindow", "-8"))
        self.QPT_1D_xlim_r.setPlainText(_translate("MainWindow", "1.5"))

        self.QPT_leng_xlim_l.setPlainText(_translate("MainWindow", "0"))
        self.QPT_leng_xlim_r.setPlainText(_translate("MainWindow", "3"))
        self.QPT_leng_bins.setPlainText(_translate("MainWindow", "100"))

        self.QPT_start1.setPlainText(_translate("MainWindow", "-2"))
        self.QPT_end1.setPlainText(_translate("MainWindow", "-3"))
        self.QPT_start2.setPlainText(_translate("MainWindow", "-4"))
        self.QPT_end2.setPlainText(_translate("MainWindow", "-6"))
        self.QPT_lower_limit1.setPlainText(_translate("MainWindow", "-55"))
        self.QPT_upper_limit1.setPlainText(_translate("MainWindow", "55"))
        self.QPT_lower_limit2.setPlainText(_translate("MainWindow", "-55"))
        self.QPT_upper_limit2.setPlainText(_translate("MainWindow", "55"))

        self.QPT_f1_a1.setPlainText(_translate("MainWindow", "-9.1137"))
        self.QPT_f1_b1.setPlainText(_translate("MainWindow", "-27.646"))
        self.QPT_f1_c1.setPlainText(_translate("MainWindow", "-1.1614e-11"))
        self.QPT_f1_d1.setPlainText(_translate("MainWindow", "4.1597e-13"))
        self.QPT_f1_a2.setPlainText(_translate("MainWindow", "9.2183"))
        self.QPT_f1_b2.setPlainText(_translate("MainWindow", "-27.8018"))
        self.QPT_f1_c2.setPlainText(_translate("MainWindow", "-1.18929e-11"))
        self.QPT_f1_d2.setPlainText(_translate("MainWindow", "-1.4714e-13"))
        self.QPT_f1_offset.setPlainText(_translate("MainWindow", "0"))

        self.QPT_f2_a1.setPlainText(_translate("MainWindow", "7.11645"))
        self.QPT_f2_a2.setPlainText(_translate("MainWindow", "12.59542"))
        self.QPT_f2_b1.setPlainText(_translate("MainWindow", "-32.28028"))
        self.QPT_f2_b2.setPlainText(_translate("MainWindow", "-23.00707"))
        self.QPT_f2_c1.setPlainText(_translate("MainWindow", "-1.16402"))
        self.QPT_f2_c2.setPlainText(_translate("MainWindow", "-1.5182"))
        self.QPT_f2_d1.setPlainText(_translate("MainWindow", "4.42553"))
        self.QPT_f2_d2.setPlainText(_translate("MainWindow", "-6.14423"))
        self.QPT_f2_e1.setPlainText(_translate("MainWindow", "0.01091"))
        self.QPT_f2_e2.setPlainText(_translate("MainWindow", "0.2286"))
        self.QPT_f2_f1.setPlainText(_translate("MainWindow", "-1.17779"))
        self.QPT_f2_f2.setPlainText(_translate("MainWindow", "-1.2272"))
        self.QPT_f2_offset.setPlainText(_translate("MainWindow", "0"))

        self.QPT_f3_a1.setPlainText(_translate("MainWindow", "3.1435"))
        self.QPT_f3_b1.setPlainText(_translate("MainWindow", "-14.62"))
        self.QPT_f3_a1.setPlainText(_translate("MainWindow", "-3.1009"))
        self.QPT_f3_b1.setPlainText(_translate("MainWindow", "-14.456"))
        self.QPT_f3_offset.setPlainText(_translate("MainWindow", "0"))

    def saveDataAndFig(self):
        self.zeroPad()
        img_path = self.key_para['img_path']
        self.createDir(img_path)
        self.fig1.fig.savefig(str(img_path) + '/2D_Conductance.png',
                              dpi=100,
                              bbox_inches='tight')
        self.fig2.fig.savefig(str(img_path) + '/Conductance_count.png',
                              dpi=100,
                              bbox_inches='tight')
        self.fig3.fig.savefig(str(img_path) + '/Length_count.png',
                              dpi=100,
                              bbox_inches='tight')

        self.saveData()

        QMessageBox.information(self, 'Information',
                                'Save figures and data succeed!')
        self.showStatusbarMessage('All figures and data saved !')
        self.showTextBroswer('Save figures and data succeed! ')

    def zeroPad(self):
        _2D_bins_x = 500
        _2D_bins_y = 1000
        pad_num = 500
        # if _2D_bins_x<_2D_bins_y:
        #     self.x_3D_new=np.pad(self.x_3D[1:],(0,pad_num),'constant', constant_values=(0))
        #     self.y_3D_new=self.y_3D[1:]
        # else:
        #     self.y_3D_new = np.pad(self.y_3D[1:], (0, pad_num), 'constant', constant_values=(0))
        #     self.x_3D_new = self.x_3D[1:]
        self.x_3D_new = np.pad(self.x_3Dedges[1:], (0, pad_num),
                               'constant',
                               constant_values=(0))
        self.y_3D_new = self.y_3Dedges[1:]

    # =================保存图像对应的数据======================#
    def saveData(self):
        img_path = self.key_para['img_path']
        data_path = str(img_path) + '_' + 'Analysis'
        self.createDir(data_path)

        np.savetxt(str(data_path) + '/WA-BJ_3Dhist.txt',
                   self.H_3D * 2,
                   fmt='%d',
                   delimiter='\t')
        hist_3D_scales = np.array([self.x_3D_new, self.y_3D_new]).T
        np.savetxt(str(data_path) + '/WA-BJ_3Dhist_scales.txt',
                   hist_3D_scales,
                   fmt='%.5e',
                   delimiter='\t')
        hist_log = np.array([self.bin_edges_1D[:-1], self.hist_1D]).T
        np.savetxt(str(data_path) + '/WA-BJ_logHist.txt',
                   hist_log,
                   fmt='%.5e',
                   delimiter='\t')
        hist_plat = np.array([self.bin_edges_len[:-1], self.hist_len]).T
        np.savetxt(str(data_path) + '/WA-BJ_plateau.txt',
                   hist_plat,
                   fmt='%.5e',
                   delimiter='\t')

    def saveGoodTrace(self):
        img_path = self.key_para['img_path']
        data_path = str(img_path) + '_' + 'Analysis'
        self.createDir(data_path)
        xx = np.array(self.xx)
        yy = np.array(self.yy)
        # temp={'length':xx,'conductance':yy}
        # save_trace=pd.DataFrame(temp)
        GT = np.array([xx, yy]).T
        self.showStatusbarMessage('Start to save to goodtrace ...... ')
        try:
            # save_trace.to_csv(str(img_path) + '/goodtrace.txt',header=0, index = 0,sep='\t')
            np.savetxt(str(data_path) + '/goodtrace.txt',
                       GT,
                       fmt='%.5e',
                       delimiter='\t')
        except Exception as e:
            QMessageBox.warning(self, 'Warning', 'Save to goodtrace failed !')
            self.showStatusbarMessage('Save to goodtrace failed !')
        else:
            QMessageBox.information(self, 'Information',
                                    'Save to goodtrace succeed!')
            self.showStatusbarMessage('Save to goodtrace succeed!')
            self.showTextBroswer('Save to goodtrace succeed! ')

    def createDir(self, path):
        if not os.path.exists(str(path)):
            os.mkdir(str(path))
            print('Folder created !')
            self.showStatusbarMessage('Create new folder:' + str(path))
        else:
            print('Folder already exist !')
            self.showStatusbarMessage('Folder already exist !')
class SerialOwner(QObject, StatusMixin):  # GUI thread
    """
    Encapsulates a serial port + reader (with thread) + writer (with thread)
    and the associated signals/slots.
    """
    # Outwards, to world:
    started = pyqtSignal()
    finished = pyqtSignal()
    state_change = pyqtSignal(int, str)
    # Inwards, to possessions:
    reader_start_requested = pyqtSignal(serial.Serial)
    writer_start_requested = pyqtSignal(serial.Serial)
    reader_stop_requested = pyqtSignal()
    writer_stop_requested = pyqtSignal()
    controller_stop_requested = pyqtSignal()
    status_requested = pyqtSignal()

    # noinspection PyUnresolvedReferences
    def __init__(
            self,
            serial_args: Dict[str, Any],
            parent: QObject = None,
            rx_eol: bytes = LF,
            tx_eol: bytes = LF,
            callback_id: int = None,
            name: str = '?',
            input_encoding: str = 'ascii',  # serial device to us
            output_encoding: str = 'utf8',  # us to serial device
            reader_class: Type[SR] = SerialReader,
            reader_kwargs: Dict[str, Any] = None,
            writer_class: Type[SW] = SerialWriter,
            writer_kwargs: Dict[str, Any] = None,
            controller_class: Type[SC] = SerialController,
            controller_kwargs: Dict[str, Any] = None,
            read_timeout_sec: float = READ_TIMEOUT_SEC,
            write_timeout_sec: float = WRITE_TIMEOUT_SEC,
            inter_byte_timeout_sec: float = INTER_BYTE_TIMEOUT_SEC,
            **kwargs):
        """
        serial_args: as per PySerial:
            port
            baudrate
            bytesize
            parity
            stopbits
            xonxoff
            rtscts
            dsrdtr
        """
        super().__init__(parent=parent, name=name, logger=log, **kwargs)
        self.callback_id = callback_id
        reader_kwargs = reader_kwargs or {}  # type: Dict[str, Any]
        writer_kwargs = writer_kwargs or {}  # type: Dict[str, Any]
        controller_kwargs = controller_kwargs or {}  # type: Dict[str, Any]
        self.serial_port = None  # type: Serial
        self.readerthread = QThread(self)
        self.writerthread = QThread(self)
        self.controllerthread = QThread(self)
        self.state = ThreadOwnerState.stopped

        # Serial port
        self.serial_args = serial_args
        self.serial_args.update(
            dict(
                timeout=read_timeout_sec,
                write_timeout=write_timeout_sec,
                inter_byte_timeout=inter_byte_timeout_sec,
            ))
        # timeout:
        #   read timeout... in seconds, when numeric
        #   See http://pyserial.readthedocs.org/en/latest/pyserial_api.html
        #   Low values: thread more responsive to termination; more CPU.
        #   High values: the converse.
        #   None: wait forever.
        #   0: non-blocking (avoid here).
        # inter_byte_timeout (formerly inter_byte_timeout):
        #   governs when read() calls return when there is a sufficient time
        #   between incoming bytes;
        #   https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py  # noqa
        #   http://www.unixwiz.net/techtips/termios-vmin-vtime.html
        self.debug("Creating SerialOwner: {}".format(serial_args))

        # Serial reader/writer/controller objects
        reader_kwargs.setdefault('name', name)
        reader_kwargs.setdefault('eol', rx_eol)
        reader_kwargs.setdefault('input_encoding', input_encoding)
        self.reader = reader_class(**reader_kwargs)
        writer_kwargs.setdefault('name', name)
        writer_kwargs.setdefault('eol', tx_eol)
        self.writer = writer_class(**writer_kwargs)
        controller_kwargs.setdefault('name', name)
        controller_kwargs.setdefault('output_encoding', output_encoding)
        self.controller = controller_class(**controller_kwargs)

        # Assign objects to thread
        self.reader.moveToThread(self.readerthread)
        self.writer.moveToThread(self.writerthread)
        self.controller.moveToThread(self.controllerthread)

        # Connect object and thread start/stop events
        # ... start sequence
        self.writerthread.started.connect(self.writerthread_started)
        self.writer_start_requested.connect(self.writer.start)
        self.writer.started.connect(self.writer_started)
        self.readerthread.started.connect(self.readerthread_started)
        self.reader_start_requested.connect(self.reader.start)
        self.controllerthread.started.connect(self.controllerthread_started)
        self.started.connect(self.controller.on_start)

        # ... stop
        self.controller_stop_requested.connect(self.controller.stop)
        self.controller.finished.connect(self.controllerthread.quit)
        self.controllerthread.finished.connect(self.controllerthread_finished)
        self.controllerthread.finished.connect(self.reader_stop_requested)
        self.controllerthread.finished.connect(self.writer_stop_requested)
        self.reader_stop_requested.connect(self.reader.stop,
                                           Qt.DirectConnection)  # NB!
        self.reader.finished.connect(self.readerthread.quit)
        self.readerthread.finished.connect(self.readerthread_finished)
        self.writer_stop_requested.connect(self.writerthread.quit)
        self.writerthread.finished.connect(self.writerthread_finished)

        # Connect the status events
        self.reader.status_sent.connect(self.status_sent)
        self.reader.error_sent.connect(self.error_sent)
        self.writer.status_sent.connect(self.status_sent)
        self.writer.error_sent.connect(self.error_sent)
        self.controller.status_sent.connect(self.status_sent)
        self.controller.error_sent.connect(self.error_sent)
        self.status_requested.connect(self.controller.report_status)

        # Connect the control events
        self.reader.line_received.connect(self.controller.on_receive)
        self.controller.data_send_requested.connect(self.writer.send)

    # -------------------------------------------------------------------------
    # General state control
    # -------------------------------------------------------------------------

    def is_running(self) -> bool:
        running = self.state != ThreadOwnerState.stopped
        self.debug("is_running: {} (state: {})".format(running,
                                                       self.state.name))
        return running

    def set_state(self, state: ThreadOwnerState) -> None:
        self.debug("state: {} -> {}".format(self.state.name, state.name))
        self.state = state
        self.state_change.emit(self.callback_id, state.name)

    # -------------------------------------------------------------------------
    # Starting
    # -------------------------------------------------------------------------
    # We must have control over the order. We mustn't call on_start()
    # until BOTH threads have started. Therefore we must start them
    # sequentially, not simultaneously. The reader does not exit, so can't
    # notify us that it's started (since such notifications are via the Qt
    # message loop); therefore, we must start the writer first.

    def start(self) -> None:
        self.status("Starting serial")
        if self.state != ThreadOwnerState.stopped:
            self.error("Can't start: state is: {}".format(self.state.name))
            return
        self.set_state(ThreadOwnerState.starting)
        self.info("Opening serial port: {}".format(self.serial_args))
        try:
            self.serial_port = serial.Serial(**self.serial_args)
        except Exception as e:  # serial port errors
            self.error("Serial port error: {}; stopping".format(str(e)))
            self.error(traceback.format_exc())
            self.stop()
            return
        self.debug("starting writer thread")
        self.writerthread.start()

    @pyqtSlot()
    @exit_on_exception
    def writerthread_started(self) -> None:
        self.writer_start_requested.emit(self.serial_port)

    @pyqtSlot()
    @exit_on_exception
    def writer_started(self) -> None:
        self.debug("start: starting reader thread")
        self.readerthread.start()

    @pyqtSlot()
    @exit_on_exception
    def readerthread_started(self) -> None:
        self.reader_start_requested.emit(self.serial_port)
        # We'll never get a callback from that; it's now busy.
        self.debug("start: starting controller thread")
        self.controllerthread.start()

    @pyqtSlot()
    @exit_on_exception
    def controllerthread_started(self) -> None:
        self.set_state(ThreadOwnerState.running)
        self.started.emit()

    # -------------------------------------------------------------------------
    # Stopping
    # -------------------------------------------------------------------------
    # The stop sequence is more fluid, to cope with any problems.

    def stop(self) -> None:
        if self.state == ThreadOwnerState.stopped:
            self.error("Can't stop: state is: {}".format(self.state.name))
            return
        self.set_state(ThreadOwnerState.stopping)
        self.debug("stop: asking threads to finish")
        if self.check_everything_finished():
            return
        self.controller_stop_requested.emit()

    @pyqtSlot()
    @exit_on_exception
    def reader_finished(self) -> None:
        self.debug("SerialController.reader_finished")
        self.readerthread.quit()

    @pyqtSlot()
    @exit_on_exception
    def readerthread_finished(self) -> None:
        self.debug("stop: reader thread stopped")
        self.check_everything_finished()

    @pyqtSlot()
    @exit_on_exception
    def writerthread_finished(self) -> None:
        self.debug("stop: writer thread stopped")
        self.check_everything_finished()

    @pyqtSlot()
    @exit_on_exception
    def controllerthread_finished(self) -> None:
        self.debug("stop: controller thread stopped")
        self.check_everything_finished()

    def check_everything_finished(self) -> bool:
        if (self.readerthread.isRunning() or self.writerthread.isRunning()
                or self.controllerthread.isRunning()):
            return False
        # If we get here: yes, everything is finished. Tidy up.
        if self.serial_port:
            self.debug("Closing serial port")
            self.serial_port.close()  # for Windows
        self.set_state(ThreadOwnerState.stopped)
        self.finished.emit()
        return True

    # -------------------------------------------------------------------------
    # Info
    # -------------------------------------------------------------------------

    @pyqtSlot()
    def report_status(self) -> None:
        self.status("state: {}".format(self.state.name))
        # I think it's OK to request serial port information from the GUI
        # thread...
        try:
            sp = self.serial_port
            paritybits = 0 if sp.parity == serial.PARITY_NONE else 1
            n_bits = sp.bytesize + paritybits + sp.stopbits
            time_per_char_microsec = n_bits * 1000000 / sp.baudrate

            portset = OrderedDict()
            portset['name'] = sp.name
            portset['port'] = sp.port
            portset['baudrate'] = sp.baudrate
            portset['bytesize'] = sp.bytesize
            portset['parity'] = sp.parity
            portset['stopbits'] = sp.stopbits
            portset['timeout'] = sp.timeout
            portset['xonxoff'] = sp.xonxoff
            portset['rtscts'] = sp.rtscts
            portset['dsrdtr'] = sp.dsrdtr
            portset['write_timeout'] = sp.write_timeout
            portset['inter_byte_timeout'] = sp.inter_byte_timeout
            self.status("Serial port settings: " +
                        ", ".join("{}={}".format(k, v)
                                  for k, v in portset.items()))

            portinfo = OrderedDict()
            portinfo['in_waiting'] = sp.in_waiting
            if platform.system() in ['Linux', 'Windows']:
                portinfo['out_waiting'] = sp.out_waiting
            portinfo['break_condition'] = sp.break_condition
            portinfo['rts'] = sp.rts
            portinfo['cts'] = sp.cts
            portinfo['dtr'] = sp.dtr
            portinfo['dsr'] = sp.dsr
            portinfo['ri'] = sp.ri
            portinfo['cd'] = sp.cd
            portinfo['rs485_mode'] = sp.rs485_mode
            portinfo['[time_per_char_microsec]'] = time_per_char_microsec
            self.status("Serial port info: " +
                        ", ".join("{}={}".format(k, v)
                                  for k, v in portinfo.items()))
        except Exception as e:  # serial port errors
            self.warning(
                "Serial port is unhappy - may be closed, or trying to read a "
                "non-existent attribute; error: {}".format(str(e)))
            self.warning(traceback.format_exc())

        self.status_requested.emit()
Ejemplo n.º 35
0
class MainWindow(QMainWindow, gui.Ui_MainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.setupUi(self)

        self.thread = QThread()

        self.tests = []
        self.filelist = []
        self.filename = os.path.expanduser('~/Documents/FTPDownloaderDefault.pickle')

        if os.path.isfile(self.filename):
            with open(self.filename, 'rb') as f:
                self.tests, self.filelist, self.loadIP, self.loadPass, self.loadReq, self.loadUser = pickle.load(f)

            self.lineIP.setText(self.loadIP)
            self.lineUser.setText(self.loadUser)
            self.linePass.setText(self.loadPass)
            self.lineReq.setText(self.loadReq)

        self.load_archivos()
        self.load_casos()

        self.downloader = Downloader(self.lineIP.text(), self.lineUser.text(), self.linePass.text(),
                                     self.lineReq.text(), self.filelist, self.tests)
        self.downloader.moveToThread(self.thread)
        self.downloader.log.connect(self.print_log)
        self.downloader.progress.connect(self.descargado)
        self.thread.started.connect(self.downloader.prepara_descarga)
        self.downloader.finished.connect(self.terminado)

        self.actionNuevo.triggered.connect(self.reset_all)
        self.actionLoad.triggered.connect(self.load_template)
        self.actionSave.triggered.connect(self.save_as)
        self.actionCerrar.triggered.connect(sys.exit)

        self.pushAddPrueba.clicked.connect(self.add_caso)
        self.lineCaso.returnPressed.connect(self.add_caso)
        self.pushAddArchivo.clicked.connect(self.add_archivo)
        self.lineNombreMainframe.returnPressed.connect(self.add_archivo)
        self.pushCargarArchivos.clicked.connect(self.load_list)
        self.pushClearArchivos.clicked.connect(self.clear_archivos)
        self.pushClearPruebas.clicked.connect(self.clear_pruebas)
        self.pushDeletePrueba.clicked.connect(self.borrar_caso)
        self.pushDeleteArchivo.clicked.connect(self.borrar_archivo)
        self.pushRenamePrueba.clicked.connect(self.renombrar_caso)
        self.pushRenameArchivo.clicked.connect(self.renombrar_archivo)

        self.pushDownload.clicked.connect(self.start_downloads)
        self.pushStop.clicked.connect(self.stop_process)

        self.pushButton.clicked.connect(self.about)

        atexit.register(self.save_state)

    def save_as(self):
        filename = QFileDialog.getSaveFileName(QFileDialog(), 'Guardar como', os.path.expanduser('~/Documents/'),
                                               '*.pickle')

        if filename[0]:
            save_ip = self.lineIP.text()
            save_user = self.lineUser.text()
            save_pass = self.linePass.text()
            save_req = self.lineReq.text()

            with open(filename[0] + '.pickle', 'wb') as s:
                pickle.dump([self.tests, self.filelist, save_ip, save_pass, save_req, save_user], s)

            self.print_log('Se guardo la plantilla en {}'.format(filename[0]))

    def load_template(self):

        filename = QFileDialog.getOpenFileName(QFileDialog(), 'Abrir', os.path.expanduser('~/Documents/'), '*.pickle')
        if filename[0]:
            with open(filename[0], 'rb') as f:
                self.tests, self.filelist, self.loadIP, self.loadPass, self.loadReq, self.loadUser = pickle.load(f)

            self.lineIP.setText(self.loadIP)
            self.lineUser.setText(self.loadUser)
            self.linePass.setText(self.loadPass)
            self.lineReq.setText(self.loadReq)
            self.load_archivos()
            self.load_casos()

            self.print_log('Se cargo la planitlla {}'.format(filename[0]))

    def load_list(self):
        filename = QFileDialog.getOpenFileName(QFileDialog(), 'Abrir')

        if filename[0]:
            with open(filename[0], 'r') as f:
                files = f.read().splitlines()

            for i in files:
                self.filelist.append(('', i.upper(), False))
                self.load_archivos()

    def print_log(self, message):
        log = '[{}] {}'.format(strftime("%H:%M:%S", localtime()), message)
        self.plainTextLog.appendPlainText(log)

    def load_casos(self):
        self.listPruebas.clear()
        for caso in self.tests:
            self.listPruebas.addItem(caso.capitalize())

    def load_archivos(self):
        self.listArchivos.clear()
        for item in self.filelist:
            self.listArchivos.addItem(
                '{} - {}   - {}'.format(item[0], item[1].upper(), 'Tx' if item[2] == True else 'Único'))

    def add_caso(self):
        test = self.lineCaso.text()

        if test:
            self.tests.append(test.capitalize())
            self.lineCaso.clear()
            self.load_casos()
        else:
            self.print_log('Campo Caso vacio.')

    def add_archivo(self):
        mainframe = self.lineNombreMainframe.text()
        nombre = self.lineNombre.text()
        tx = True if self.checkTx.isChecked() else False

        if mainframe:
            if not nombre:
                self.print_log('Nombre vacio. Se nombrara como archivo en mainframe.')
            self.filelist.append((nombre, mainframe.upper(), tx))
            self.lineNombreMainframe.clear()
            self.lineNombre.clear()
            self.load_archivos()
        else:
            self.print_log('Nombre en Mainframe vacio.')

    def clear_archivos(self):
        confirm = self.del_confirmation('Reset', 'Desea borrar todos los archivos?')

        if confirm == gui.QtWidgets.QMessageBox.Yes:
            self.filelist.clear()
            self.listArchivos.clear()
            self.load_archivos()

    def clear_pruebas(self):
        confirm = self.del_confirmation('Reset', 'Desea borrar todas las pruebas?')

        if confirm == gui.QtWidgets.QMessageBox.Yes:
            self.tests.clear()
            self.listPruebas.clear()
            self.load_casos()

    def borrar_caso(self):
        index = self.listPruebas.currentRow()
        if index == -1:
            self.print_log('Seleccionar item.')
        else:
            self.tests.pop()
            self.load_casos()

    def borrar_archivo(self):
        index = self.listArchivos.currentRow()
        if index == -1:
            self.print_log('Seleccionar item.')
        else:
            self.filelist.pop(index)
            self.load_archivos()

    def renombrar_caso(self):
        new_name = self.lineCaso.text()
        index = self.listPruebas.currentRow()

        if not new_name:
            self.print_log('Ingresar nombre nuevo')
        elif index == -1:
            self.print_log('Seleccionar item.')
        else:
            self.tests[index] = new_name
            self.lineCaso.clear()
            self.load_casos()

    def renombrar_archivo(self):
        index = self.listArchivos.currentRow()

        new_name = self.lineNombre.text()
        new_mainframe_name = self.lineNombreMainframe.text()
        new_state = True if self.checkTx.isChecked() else False

        if not new_mainframe_name:
            self.print_log('Ingresar nombre nuevo.')
        elif index == -1:
            self.print_log('Seleccionar item.')
        else:
            self.filelist[index] = (new_name, new_mainframe_name, new_state)
            self.lineNombre.clear()
            self.lineNombreMainframe.clear()
            self.load_archivos()

    def reset_all(self):
        confirm = self.del_confirmation('Reset', 'Se borrarán todos los datos,\n'
                                            'excepto la info de login.\n'
                                            'Desea continuar?')
        if confirm == QMessageBox.Yes:
            self.clear_archivos()
            self.clear_pruebas()
            self.lineReq.clear()
            self.lineCaso.clear()
            self.lineNombreMainframe.clear()
            self.lineNombre.clear()
            self.checkTx.setCheckState(0)
            self.plainTextLog.clear()

    def save_state(self):
        save_ip = self.lineIP.text()
        save_user = self.lineUser.text()
        save_pass = self.linePass.text()
        save_req = self.lineReq.text()

        with open(self.filename, 'wb') as s:
            pickle.dump([self.tests, self.filelist, save_ip, save_pass, save_req, save_user], s)

    def start_downloads(self):

        progress_max = 100

        if not self.thread.isRunning():
            if len(self.tests) == 0:
                progress_max = len(self.filelist)
            else:
                progress_max = len(self.tests) * len(self.filelist)
            self.progressBar.setMaximum(progress_max)
            print(progress_max)
            print(self.progressBar.value())
            self.progressBar.setValue(0)
            self.thread.start()
        else:
            self.print_log('Hay una descarga en proceso. Cancelar o reintentar al finalizar.')

    def terminado(self):
        self.thread.terminate()
        self.progressBar.setValue(len(self.tests) * len(self.filelist))

    def stop_process(self):
        if self.thread.isRunning():
            self.downloader.cancelar.emit()
        else:
            self.print_log('No hay descargas en curso.')

    def descargado(self):
        self.progressBar.setValue(self.progressBar.value() + 1)
        print(self.progressBar.value())

    def about(self):
        QMessageBox.information(self, 'About', '  \'FTPDownloader 2016\' \n        Ignacio Freire',
                                QMessageBox.Ok)

    def del_confirmation(self, title, message):
        choice = QMessageBox.question(self, title, message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        return choice
Ejemplo n.º 36
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)