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)
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)
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)
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))
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")
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() # 다시 처음부터 시작
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
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()
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()
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)
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)
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')
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()
class WorkerThread(QObject): sig_close_requested = pyqtSignal() def __init__(self) -> None: super().__init__() self._worker: Optional[Worker] = None self._thread: Optional[QThread] = None def set_worker(self, worker: Worker): """Sets the Worker associated with this thread. Note this function has to be called before connecting any signals, otherwise the signals won't be associated with the right thread. """ assert self._worker is None self._worker = worker self._thread = QThread() self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.on_thread_started) # close() is a blocking connection so the thread is properly # done after the signal was emit'ed and we don't have to fuss # around with sig_finished() and other stuff self.sig_close_requested.connect(self._worker.close, type=Qt.BlockingQueuedConnection) def start(self) -> None: assert self._worker is not None assert self._thread is not None self._thread.start() def is_running(self) -> bool: assert self._thread is not None return bool(self._thread.isRunning()) def close(self) -> None: assert self._thread is not None assert self._worker is not None assert self._worker._close is False, "WorkerThread.close() was called twice" self._worker._close = True self.sig_close_requested.emit() self._thread.quit() self._thread.wait()
class 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)
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()
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)
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 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)
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)
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)
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()
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
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 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)
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())
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_()
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 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)
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()
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
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()
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)
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()