class AsyncThreadController(_AsyncAbstractController): def __init__(self, # See parent_. parent=None): super().__init__(parent) # Create a worker and a thread it runs in. This approach was # inspired by the example given in the QThread_ docs. self._worker = _AsyncWorker() self._workerThread = QThread(self) # Attach the worker to the thread's event queue. self._worker.moveToThread(self._workerThread) # Hook up signals. self._worker.startSignal.connect(self._worker.onStart) # Everything is ready. Start the worker thread, so it will # be ready for functions to run. self._workerThread.start() # See `_start`_. def _start(self, future): self._worker.startSignal.emit(future) # See `_terminate`_. def _terminate(self): # Shut down the thread the worker runs in. self._workerThread.quit() self._workerThread.wait() # Delete the worker, since it doesn't have a parent and therefore # won't be deleted by the Qt object tree. sip.delete(self._worker) del self._worker
class MetaDataCollector(QObject): sig_metadata_ready = pyqtSignal(Location, dict) sig_request_metadata = pyqtSignal(Location) sig_delete_metadatas = pyqtSignal(list) def __init__(self, vfs: StdioFilesystem) -> None: super().__init__() self._worker = MetaDataCollectorWorker(vfs) self._thread = QThread() self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.init) self.sig_request_metadata.connect(self._worker.on_metadata_requested) self.sig_delete_metadatas.connect(self._worker.on_delete_metadata_requested) self._worker.sig_metadata_ready.connect(self._on_metadata_ready) self._thread.start() def close(self): self._worker._close = True self._thread.quit() self._thread.wait() def request_metadata(self, location: Location) -> None: self.sig_request_metadata.emit(location) def request_delete_metadatas(self, locations: List[Location]) -> None: self.sig_delete_metadatas.emit(locations) def _on_metadata_ready(self, location: Location, metadata: Any): self.sig_metadata_ready.emit(location, metadata)
class MatcherUI(QDialog): thread_invoker = pyqtSignal() def __init__(self, argv, terminate=False): super(MatcherUI, self).__init__() self.btnStop = QPushButton('Stop') self.image_pane = QLabel() self.progress = QProgressBar(self) self.layout = QVBoxLayout() self.layout.addWidget(self.btnStop) self.layout.addWidget(self.progress) self.layout.addWidget(self.image_pane) self.setLayout(self.layout) self.thread = QThread() self.thread.start() self.worker = Worker(argv) self.worker.moveToThread(self.thread) self.thread_invoker.connect(self.worker.task) self.thread_invoker.emit() self.worker.frameRendered.connect(self.updatePixmap) self.worker.finished.connect(self.finishIt) self.worker.progress.connect(self.progress.setValue) self.terminate = terminate self.btnStop.clicked.connect(lambda: self.worker.stop()) self.btnStop.clicked.connect(self.terminateIt) self.terminated = False def updatePixmap(self, image): #it is called only when the pixmap is really updated by the thread. #resize image in advance. #w,h = image.width(), image.height() #scaled_image = image.scaled(int(w*self.preview_ratio), int(h*self.preview_ratio)) pixmap = QPixmap.fromImage(image) self.image_pane.setPixmap(pixmap) #is it ok here? self.update() def terminateIt(self): self.close() if self.terminate: sys.exit(1) #terminated self.terminated = True def finishIt(self): self.close() def closeEvent(self, event): self.stop_thread() def stop_thread(self): self.worker.stop() self.thread.quit() self.thread.wait()
def run(logger): menu = Menu() requests = queue.Queue() def resolve(command): command.execute(menu) with LinewiseSocket.context(socket_file) as sock: logger('listening on ' + socket_file + '\n') server_thread = QThread() server = Server(requests, logger=logger) server.moveToThread(server_thread) server_thread.started.connect(server.run) listener_thread = QThread() receiver = Listener(sock, requests) receiver.moveToThread(listener_thread) listener_thread.started.connect(receiver.run) server.dispatched.connect(resolve) menu.finished.connect(server_thread.exit) menu.finished.connect(listener_thread.exit) server_thread.start() logger('started server thread\n') listener_thread.start() logger('started receiver thread\n') return menu.exec_()
def get_metadata(self, gal=None): thread = QThread(self) thread.setObjectName("App.get_metadata") fetch_instance = fetch.Fetch() if gal: galleries = [gal] else: if gui_constants.CONTINUE_AUTO_METADATA_FETCHER: galleries = [g for g in self.manga_list_view.gallery_model._data if not g.exed] else: galleries = self.manga_list_view.gallery_model._data if not galleries: self.notification_bar.add_text("Looks like we've already gone through all galleries!") return None fetch_instance.galleries = galleries self.notification_bar.begin_show() fetch_instance.moveToThread(thread) def done(status): self.notification_bar.end_show() fetch_instance.deleteLater() fetch_instance.GALLERY_PICKER.connect(self._web_metadata_picker) fetch_instance.GALLERY_EMITTER.connect(self.manga_list_view.replace_edit_gallery) fetch_instance.AUTO_METADATA_PROGRESS.connect(self.notification_bar.add_text) thread.started.connect(fetch_instance.auto_web_metadata) fetch_instance.FINISHED.connect(done) thread.finished.connect(thread.deleteLater) thread.start()
class PreviewParent(QObject): """ Class which communicates with the PlayerWorker Class (running on a separate thread) """ # Signal when the frame position changes in the preview player def onPositionChanged(self, current_frame): self.parent.movePlayhead(current_frame) # Signal when the playback mode changes in the preview player (i.e PLAY, PAUSE, STOP) def onModeChanged(self, current_mode): log.info('onModeChanged') @pyqtSlot(object, object) def Init(self, parent, timeline, video_widget): # Important vars self.parent = parent self.timeline = timeline # Background Worker Thread (for preview video process) self.background = QThread(self) self.worker = PlayerWorker() # no parent! # Init worker variables self.worker.Init(parent, timeline, video_widget) # Hook up signals to Background Worker self.worker.position_changed.connect(self.onPositionChanged) self.worker.mode_changed.connect(self.onModeChanged) self.background.started.connect(self.worker.Start) self.worker.finished.connect(self.background.quit) # Move Worker to new thread, and Start self.worker.moveToThread(self.background) self.background.start()
class AsyncThreadController(AsyncAbstractController): def __init__(self, # |parent| parent=None): super(AsyncThreadController, self).__init__(parent) # Create a worker and a thread it runs in. This approach was # inspired by example given in the `QThread docs # <http://qt-project.org/doc/qt-4.8/qthread.html>`_. self._worker = _AsyncWorker() self._workerThread = QThread(parent) # Attach the worker to the thread's event queue. self._worker.moveToThread(self._workerThread) # Hook up signals. self._worker.startSignal.connect(self._worker.onStart) # Everything is ready. Start the worker thread, so it will # be ready for functions to run. self._workerThread.start() # |_start| def _start(self, future): self._worker.startSignal.emit(future) # |terminate| def _terminate(self): # Shut down the thread the Worker runs in. self._workerThread.quit() self._workerThread.wait() # Finally, detach (and probably garbage collect) the objects # used by this class. del self._worker del self._workerThread
def start_msg_thread(self, runningObjectParam, onRun = None, onDone = None, onErrorMsg = None, onSuccessMsg = None ): if self.running: return self.running = True thr = QThread() thrName = 'EndicataLabToolMainWnd start_msg_thread[%d]' % (len( self.threadPool ) + 1) print thrName thr.setObjectName( thrName ) self.threadPool.append( thr ) self.runningObject = runningObjectParam self.runningObject.moveToThread(thr) if onRun: thr.started.connect( onRun ) else: thr.started.connect( self.runningObject.run ) self.runningObject.finished.connect( self.onUnlock ) if onDone: self.runningObject.done.connect( onDone ) if onErrorMsg: self.runningObject.errorMsg.connect( onErrorMsg ) if onSuccessMsg: self.runningObject.successMsg.connect( onSuccessMsg ) thr.start()
class MeasurementThread(QObject): specSignal = pyqtSignal(np.ndarray) progressSignal = pyqtSignal(float, str) finishSignal = pyqtSignal(np.ndarray) def __init__(self, spectrometer, parent=None): if getattr(self.__class__, '_has_instance', False): RuntimeError('Cannot create another instance') self.__class__._has_instance = True try: super(MeasurementThread, self).__init__(parent) self.spectrometer = spectrometer self.abort = False self.thread = QThread(parent) self.moveToThread(self.thread) self.thread.started.connect(self.process) self.thread.finished.connect(self.stop) except: (type, value, traceback) = sys.exc_info() sys.excepthook(type, value, traceback) def start(self): self.thread.start(QThread.HighPriority) @pyqtSlot() def stop(self): self.abort = True self.thread.quit() self.thread.wait(5000) def __del__(self): self.__class__.has_instance = False #try: # self.specSignal.disconnect() # self.progressSignal.disconnect() # self.finishSignal.disconnect() #except: # (type, value, traceback) = sys.exc_info() # sys.excepthook(type, value, traceback) def work(self): self.specSignal.emit(self.spec) @pyqtSlot() def process(self): while not self.abort: try: self.spec = self.spectrometer.intensities() self.spec = self.spec[0:1024] self.work() except: (type, value, traceback) = sys.exc_info() print(type) print(value) print(traceback) sys.excepthook(type, value, traceback)
def add_gallery(gallery_list): def append_to_model(x): self.manga_list_view.gallery_model.insertRows(x, None, len(x)) class A(QObject): done = pyqtSignal() prog = pyqtSignal(int) def __init__(self, obj, parent=None): super().__init__(parent) self.obj = obj self.galleries = [] def add_to_db(self): gui_constants.NOTIF_BAR.begin_show() gui_constants.NOTIF_BAR.add_text("Populating database...") for y, x in enumerate(self.obj): gui_constants.NOTIF_BAR.add_text( "Populating database {}/{}".format(y + 1, len(self.obj)) ) gallerydb.add_method_queue(gallerydb.GalleryDB.add_gallery_return, False, x) self.galleries.append(x) y += 1 self.prog.emit(y) append_to_model(self.galleries) gui_constants.NOTIF_BAR.end_show() self.done.emit() loading.progress.setMaximum(len(gallery_list)) a_instance = A(gallery_list) thread = QThread(self) thread.setObjectName("Database populate") def loading_show(): loading.setText("Populating database.\nPlease wait...") loading.show() def loading_hide(): loading.close() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit() def del_later(): try: a_instance.deleteLater() except NameError: pass a_instance.moveToThread(thread) a_instance.prog.connect(loading.progress.setValue) thread.started.connect(loading_show) thread.started.connect(a_instance.add_to_db) a_instance.done.connect(loading_hide) a_instance.done.connect(del_later) thread.finished.connect(thread.deleteLater) thread.start()
def populate(self): "Populates the database with series from local drive'" msgbox = QMessageBox() msgbox.setText("<font color='red'><b>Use with care.</b></font> Choose a folder containing all your series'.") msgbox.setInformativeText("Oniichan, are you sure you want to do this?") msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msgbox.setDefaultButton(QMessageBox.No) if msgbox.exec() == QMessageBox.Yes: path = QFileDialog.getExistingDirectory(None, "Choose a folder containing your series'") if len(path) is not 0: data_thread = QThread() loading_thread = QThread() loading = misc.Loading() if not loading.ON: misc.Loading.ON = True fetch_instance = fetch.Fetch() fetch_instance.series_path = path loading.show() def finished(status): if status: self.manga_list_view.series_model.populate_data() # TODO: make it spawn a dialog instead (from utils.py or misc.py) if loading.progress.maximum() == loading.progress.value(): misc.Loading.ON = False loading.hide() data_thread.quit else: loading.setText("<font color=red>An error occured. Try restarting..</font>") loading.progress.setStyleSheet("background-color:red") data_thread.quit def fetch_deleteLater(): try: fetch_instance.deleteLater except NameError: pass def thread_deleteLater(): #NOTE: Isn't this bad? data_thread.deleteLater def a_progress(prog): loading.progress.setValue(prog) loading.setText("Searching on local disk...\n(Will take a while on first time)") fetch_instance.moveToThread(data_thread) fetch_instance.DATA_COUNT.connect(loading.progress.setMaximum) fetch_instance.PROGRESS.connect(a_progress) data_thread.started.connect(fetch_instance.local) fetch_instance.FINISHED.connect(finished) fetch_instance.FINISHED.connect(fetch_deleteLater) fetch_instance.FINISHED.connect(thread_deleteLater) data_thread.start()
def make_transfer_dialog() -> TransferDialog: dialog = TransferDialog("/home/juser/Target Directory", None) thread = QThread(dialog) worker = TransferDialogTest(dialog, dialog) worker.moveToThread(thread) thread.started.connect(worker.on_started) thread.start() global g_keep_alive g_keep_alive += [worker, thread] return dialog
def start_up(self): def normalize_first_time(): settings.set(2, "Application", "first time level") def done(): self.manga_list_view.gallery_model.init_data() if gui_constants.ENABLE_MONITOR and gui_constants.MONITOR_PATHS and all(gui_constants.MONITOR_PATHS): self.init_watchers() if gui_constants.FIRST_TIME_LEVEL != 2: normalize_first_time() if gui_constants.FIRST_TIME_LEVEL < 2: class FirstTime(file_misc.BasePopup): def __init__(self, parent=None): super().__init__(parent) main_layout = QVBoxLayout() info_lbl = QLabel( "Hi there! Some big changes are about to occur!\n" + "Please wait.. This will take at most a few minutes.\n" + "If not then try restarting the application." ) info_lbl.setAlignment(Qt.AlignCenter) main_layout.addWidget(info_lbl) prog = QProgressBar(self) prog.setMinimum(0) prog.setMaximum(0) prog.setTextVisible(False) main_layout.addWidget(prog) main_layout.addWidget(QLabel("Note: This popup will close itself when everything is ready")) self.main_widget.setLayout(main_layout) ft_widget = FirstTime(self) log_i("Invoking first time level 2") bridge = gallerydb.Bridge() thread = QThread(self) thread.setObjectName("Startup") bridge.moveToThread(thread) thread.started.connect(bridge.rebuild_galleries) bridge.DONE.connect(ft_widget.close) bridge.DONE.connect(self.setEnabled) bridge.DONE.connect(done) bridge.DONE.connect(bridge.deleteLater) thread.finished.connect(thread.deleteLater) thread.start() ft_widget.adjustSize() ft_widget.show() self.setEnabled(False) else: done()
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 WorkerFacade(QObject): sig_thing_requested = pyqtSignal(str, types.FunctionType) sig_quit_requested = pyqtSignal() def __init__(self, name): super().__init__() self.quit = False self.name = name self.worker = Worker("worker-" + name) self.thread = QThread() self.worker.moveToThread(self.thread) # startup and shutdown self.thread.started.connect(self.worker.init) self.sig_quit_requested.connect(self.worker.deinit) self.worker.sig_finished.connect(self.thread.quit) # requests and receive self.worker.sig_thing_finished.connect(self.on_thing_finished) self.sig_thing_requested.connect(self.worker.on_thing_requested) self.thread.start() def request_thing(self, thing, callback): self.worker.thing_requested(thing, callback) # self.sig_thing_requested.emit(self.name + "-" + thing, callback) def on_thing_finished(self, thing, callback): callback(thing) def on_quit(self): if self.worker.quit: return print(self.name, "WorkerFacade.on_quit()") # This signal won't be received until all work is done!!!! self.sig_quit_requested.emit() # Thus notify worker via sidechannel to stop doing it's job self.worker.quit = True # waiting for the thread to finish print(self.name, "evdi waiting") evdi = QAbstractEventDispatcher.instance() while self.thread.wait(10) is False: evdi.processEvents(QEventLoop.AllEvents) print(self.name, "evdi waiting: done")
class FinderUi(QWidget): """ User Interface functions """ def __init__(self): super(FinderUi, self).__init__() # Load UI file from Qt Designer uic.loadUi(find_data_file("finder.ui"), self) # Setup the UDP listener in it's own thread self.finder = BCSFinder() self.thread = QThread() self.finder.moveToThread(self.thread) self.thread.started.connect(self.finder.listen) self.thread.finished.connect(app.exit) self.thread.start() # Connect to slots self.pushButton.clicked.connect(self.ping) self.finderTable.cellDoubleClicked.connect(self.open) self.finder.found.connect(self.located) def ping(self): # Clear table while(self.finderTable.rowCount() > 0): self.finderTable.removeRow(0) # Send ping self.finder.ping() def open(self, row, col): # When double clicked, open default web browser to the BCS webbrowser.open('http://{}/'.format(self.finderTable.item(row, 0).text())) def located(self, bcs): # Found a BCS, add a row to the table row = self.finderTable.rowCount() self.finderTable.insertRow(row) if bcs['port'] != 0 and bcs['port'] != 80: self.finderTable.setItem(row, 0, QTableWidgetItem(bcs['address'] + ":" + str(bcs['port']))) else: self.finderTable.setItem(row, 0, QTableWidgetItem(bcs['address'])) self.finderTable.setItem(row, 1, QTableWidgetItem(bcs['name'])) self.finderTable.setItem(row, 2, QTableWidgetItem(bcs['mac'])) self.finderTable.setItem(row, 3, QTableWidgetItem(bcs['version'])) self.finderTable.setItem(row, 4, QTableWidgetItem(bcs['type'])) self.finderTable.resizeColumnsToContents()
class PreviewParent(QObject): """ Class which communicates with the PlayerWorker Class (running on a separate thread) """ # Signal when the frame position changes in the preview player def onPositionChanged(self, current_frame): self.parent.movePlayhead(current_frame) # Check if we are at the end of the timeline if self.worker.player.Mode() == openshot.PLAYBACK_PLAY and current_frame >= self.worker.timeline_length and self.worker.timeline_length != -1: # Yes, pause the video self.parent.actionPlay.trigger() self.worker.timeline_length = -1 # Signal when the playback mode changes in the preview player (i.e PLAY, PAUSE, STOP) def onModeChanged(self, current_mode): log.info('onModeChanged') @pyqtSlot(object, object) def Init(self, parent, timeline, video_widget): # Important vars self.parent = parent self.timeline = timeline # Background Worker Thread (for preview video process) self.background = QThread(self) self.worker = PlayerWorker() # no parent! # Init worker variables self.worker.Init(parent, timeline, video_widget) # Hook up signals to Background Worker self.worker.position_changed.connect(self.onPositionChanged) self.worker.mode_changed.connect(self.onModeChanged) self.background.started.connect(self.worker.Start) self.worker.finished.connect(self.background.quit) # Connect preview thread to main UI signals self.parent.previewFrameSignal.connect(self.worker.previewFrame) self.parent.refreshFrameSignal.connect(self.worker.refreshFrame) self.parent.LoadFileSignal.connect(self.worker.LoadFile) self.parent.PlaySignal.connect(self.worker.Play) self.parent.PauseSignal.connect(self.worker.Pause) self.parent.SeekSignal.connect(self.worker.Seek) self.parent.SpeedSignal.connect(self.worker.Speed) self.parent.StopSignal.connect(self.worker.Stop) # Move Worker to new thread, and Start self.worker.moveToThread(self.background) self.background.start()
def web_metadata(self, url, btn_widget, pgr_widget): self.link_lbl.setText(url) f = fetch.Fetch() thread = QThread(self) thread.setObjectName('Gallerydialog web metadata') 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) f.deleteLater() 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.link = url f.galleries = [dummy_gallery] f.moveToThread(thread) f.GALLERY_PICKER.connect(gallery_picker) f.GALLERY_EMITTER.connect(self.set_web_metadata) thread.started.connect(f.auto_web_metadata) thread.finished.connect(thread.deleteLater) f.FINISHED.connect(status) thread.start()
def add_gallery(gallery_list): def append_to_model(x): self.manga_list_view.sort_model.insertRows(x, None, len(x)) self.manga_list_view.sort_model.init_search( self.manga_list_view.sort_model.current_term) class A(QObject): done = pyqtSignal() prog = pyqtSignal(int) def __init__(self, obj, parent=None): super().__init__(parent) self.obj = obj self.galleries = [] def add_to_db(self): for y, x in enumerate(self.obj): gallerydb.add_method_queue( gallerydb.GalleryDB.add_gallery_return, False, x) self.galleries.append(x) y += 1 self.prog.emit(y) append_to_model(self.galleries) self.done.emit() loading.progress.setMaximum(len(gallery_list)) self.a_instance = A(gallery_list) thread = QThread(self) thread.setObjectName('Database populate') def loading_show(numb): if loading.isHidden(): loading.show() loading.setText('Populating database ({}/{})\nPlease wait...'.format( numb, loading.progress.maximum())) loading.progress.setValue(numb) loading.show() def loading_hide(): loading.close() self.manga_list_view.gallery_model.ROWCOUNT_CHANGE.emit() self.a_instance.moveToThread(thread) self.a_instance.prog.connect(loading_show) thread.started.connect(self.a_instance.add_to_db) self.a_instance.done.connect(loading_hide) self.a_instance.done.connect(self.a_instance.deleteLater) #a_instance.add_to_db() thread.finished.connect(thread.deleteLater) thread.start()
class QThreadedWorker(QObject): def __init__(self): super().__init__() self.thread = QThread() self.moveToThread(self.thread) self.thread.start() # Thread initialisation self.finished.connect(self.thread.quit) self.finished.connect(self.deleteLater) self.thread.finished.connect(self.thread.deleteLater) finished = pyqtSignal() def finish(self): self.finished.emit()
def _check_update(self): class upd_chk(QObject): UPDATE_CHECK = pyqtSignal(str) def __init__(self, **kwargs): super().__init__(**kwargs) def fetch_vs(self): import requests import time try: log_d("Checking Update") time.sleep(1.5) r = requests.get( "https://raw.githubusercontent.com/Pewpews/happypanda/master/VS.txt", verify="cacert.pem" ) a = r.text vs = a.strip() self.UPDATE_CHECK.emit(vs) except: log.exception("Checking Update: FAIL") self.UPDATE_CHECK.emit("this is a very long text which is is sure to be over limit") def check_update(vs): log_i("Received version: {}\nCurrent version: {}".format(vs, gui_constants.vs)) if vs != gui_constants.vs: if len(vs) < 10: self.notification_bar.add_text( "Version {} of Happypanda is".format(vs) + " available. Click here to update!", False ) self.notification_bar.clicked.connect( lambda: utils.open_web_link("https://github.com/Pewpews/happypanda/releases") ) self.notification_bar.set_clickable(True) else: self.notification_bar.add_text("An error occurred while checking for new version") self.update_instance = upd_chk() thread = QThread(self) self.update_instance.moveToThread(thread) thread.started.connect(self.update_instance.fetch_vs) self.update_instance.UPDATE_CHECK.connect(check_update) self.update_instance.UPDATE_CHECK.connect(self.update_instance.deleteLater) thread.finished.connect(thread.deleteLater) thread.start()
class TrackerGui(MainUi): def update_cam_list(self): cam_list = [str(cam) for cam in PyETCore.inst.cameras.values()] self.eye_cam_combo_box.clear() self.seeing_cam_combo_box.clear() self.eye_cam_combo_box.addItems(cam_list) self.seeing_cam_combo_box.addItems(cam_list) def update_res_list(self): self.eye_res_combo_box.clear() self.seeing_res_combo_box.clear() if PyETCore.inst.eye_cam: r_list = PyETCore.inst.eye_cam.str_resolutions self.eye_res_combo_box.addItems(r_list) if PyETCore.inst.seeing_cam: r_list = PyETCore.inst.seeing_cam.str_resolutions self.seeing_res_combo_box.addItems(r_list) def setup_ui(self, MainWindow): super().setupUi(MainWindow) self.f_updater = FrameUpdater() self.thread = QThread() self.f_updater.eye_frame_ready.connect(self.on_frame_ready) self.f_updater.seeing_frame_ready.connect(self.on_frame_ready) self.f_updater.moveToThread(self.thread) self.thread.started.connect(self.f_updater.process) self.thread.start() self.update_cam_list() self.update_res_list() self.start_cap_btn.released.connect(PyETCore.inst.record) self.stop_cap_btn.released.connect(PyETCore.inst.stop_record) def on_frame_ready(self, pixmap, cam_type): pixmap = pixmap.scaled(self.cam_view.size(), Qt.KeepAspectRatio) if cam_type is 1: self.cam_view_2.setPixmap(pixmap) elif cam_type is 2: self.cam_view.setPixmap(pixmap) app = QtCore.QCoreApplication.instance() app.processEvents()
class SearchStream(QObject): sig_close_requested = pyqtSignal() def __init__(self, abspath: str, pattern: str) -> None: super().__init__() self._worker = SearchStreamWorker(abspath, pattern) self._thread = QThread(self) self._worker.moveToThread(self._thread) self._thread.started.connect(self._worker.init) # close() is a blocking connection so the thread is properly # done after the signal was emit'ed and we don't have to fuss # around with sig_finished() and other stuff self.sig_close_requested.connect(self._worker.close, type=Qt.BlockingQueuedConnection) def start(self) -> None: self._thread.start() def close(self) -> None: assert self._worker._close is False self._worker._close = True self.sig_close_requested.emit() self._thread.quit() self._thread.wait() @property def sig_file_added(self): return self._worker.sig_file_added @property def sig_finished(self): return self._worker.sig_finished @property def sig_message(self): return self._worker.sig_message @property def sig_error(self): return self._worker.sig_error
def start_pool_workers(self, files_list): try: for file in files_list: worker = WorkerYandexUpload(os.path.normpath(file), self.settings_dialog.getAPIKey()) thread = QThread() list_widget_item = self.add_list_widget_item() worker.progress_bar.connect(self.listWidget.itemWidget(list_widget_item).set_progress) worker.name.connect(self.listWidget.itemWidget(list_widget_item).set_name) worker.status.connect(self.listWidget.itemWidget(list_widget_item).set_info) worker.hide_progress_bar.connect(self.listWidget.itemWidget(list_widget_item).hide_progress) worker.set_icon.connect(self.listWidget.itemWidget(list_widget_item).set_pixmap) # worker.send_email.connect(self.send_mail) # TODO разлочить worker.moveToThread(thread) thread.started.connect(worker.run) thread.start() logging.debug('Создан новый работник') except Exception as e: logging.debug(e)
class WebJumpPrompt(Prompt): label = "url/webjump:" complete_options = { "autocomplete": True, } history = PromptHistory() ask_completions = Signal(object, str, str) force_new_buffer = False def completer_model(self): data = [] for name, w in WEBJUMPS.items(): if w.allow_args: name = name data.append((name, w.doc)) return PromptTableModel(data) def enable(self, minibuffer): Prompt.enable(self, minibuffer) self.new_buffer = PromptNewBuffer(self.force_new_buffer) self.new_buffer.enable(minibuffer) minibuffer.input().textEdited.connect(self._text_edited) self._wc_model = QStringListModel() self._wb_model = minibuffer.input().completer_model() self._cthread = QThread(app()) self._creceiver = CompletionReceiver() self._creceiver.moveToThread(self._cthread) self._completion_timer = 0 self._active_webjump = None self._cthread.finished.connect(self._cthread.deleteLater) self.ask_completions.connect(self._creceiver.get_completions) self._creceiver.got_completions.connect(self._got_completions) self._cthread.start() def _text_edited(self, text): model = self._wb_model for name, w in WEBJUMPS.items(): if w.allow_args and w.complete_fn: name = name if text.startswith(name): model = self._wc_model self._active_webjump = (w, name) if self._completion_timer != 0: self.killTimer(self._completion_timer) self._completion_timer = self.startTimer(10) break if self.minibuffer.input().completer_model() != model: self.minibuffer.input().popup().hide() self.minibuffer.input().set_completer_model(model) def timerEvent(self, _): text = self.minibuffer.input().text() w, name = self._active_webjump self.ask_completions.emit(w, name, text[len(name):]) self.killTimer(self._completion_timer) self._completion_timer = 0 @Slot(list) def _got_completions(self, data): self._wc_model.setStringList(data) self.minibuffer.input().show_completions() def close(self): Prompt.close(self) self._cthread.quit() # not sure if those are required; self._wb_model.deleteLater() self._wc_model.deleteLater() def get_buffer(self): return self.new_buffer.get_buffer()
class MainWindown(QMainWindow): load_data_ready = pyqtSignal(object) def __init__(self): self.PosData = None self.Clusterer = None self.show_visual = False self.show_bg = False self.pos_vis = None self.clustering_vis = None self.point_size = None self.pool = Pool(processes=1) # experimenting self.app = QApplication([]) QMainWindow.__init__(self) ui_fname = 'main_window.ui' self.ui = uic.loadUi(ui_fname) self.ui.button_data_fname.clicked.connect(self.button_open_data_file) self.ui.button_range_fname.clicked.connect(self.button_open_range_file) self.ui.button_load_data.clicked.connect(self.button_load_data_click) self.ui.button_show_ion_map.clicked.connect( self.button_show_ion_map_click) self.ui.button_run.clicked.connect(self.button_run_click) self.ui.button_terminate.clicked.connect(self.button_terminate_click) self.ui.button_show_visual.clicked.connect( self.button_show_visual_click) self.ui.button_show_visual.setEnabled(False) self.ui.button_save_output.clicked.connect(self.button_save_click) self.ui.groupBox_expert.hide() self.ui.checkBox_show_bg.stateChanged.connect(self.checked_bg) self.ui.checkBox_show_expert.stateChanged.connect(self.checked_expert) # self.my_stream = MyStream() # self.my_stream.message.connect(self.on_my_stream_message) # sys.stdout = self.my_stream # basically overide the stdout method with my_stream, and output it to the text box. self.ui.show() sys.exit(self.app.exec()) @pyqtSlot(int, name='checked_bg') def checked_bg(self, state): if state == Qt.Checked: self.show_bg = True else: self.show_bg = False @pyqtSlot(int, name='checked_expert') def checked_expert(self, state): if state == Qt.Checked: self.ui.groupBox_expert.show() else: self.ui.groupBox_expert.hide() @pyqtSlot(str, name='on_myStream_message') def on_my_stream_message(self, message): self.ui.textBrowser.append(message) @pyqtSlot(name='button_open_data_file') def button_open_data_file(self): fname = QFileDialog.getOpenFileName(self, 'Open Pos File', os.getcwd(), "Pos files (*.pos)") self.ui.le_data_fname.setText(fname[0]) @pyqtSlot(name='button_open_range_file') def button_open_range_file(self): fname = QFileDialog.getOpenFileName(self, 'Open Range File', os.getcwd(), "Range files (*.rrng, *.RRNG)") self.ui.le_rrng_fname.setText(fname[0]) @pyqtSlot(name='button_load_data_click') def button_load_data_click(self): data_fname = self.ui.le_data_fname.text() rrng_fname = self.ui.le_rrng_fname.text() self.point_size = int(self.ui.le_point_size.text()) self.ui.textBrowser.setText('Data file loaded: ' + data_fname + '\n' + 'Range file loaded: ' + rrng_fname + '\n') self.ui.textBrowser.append('Loadeding...') self.DataLoader = APTDataLoader(data_fname, rrng_fname) self.thread_data_loader = QThread() self.DataLoader.data_ready.connect(self.get_load_data) self.DataLoader.finished.connect(self.thread_data_loader.quit) self.DataLoader.moveToThread(self.thread_data_loader) self.thread_data_loader.started.connect(self.DataLoader.load_APT_data) self.thread_data_loader.start() @pyqtSlot(object, name='get_load_data') def get_load_data(self, data): if data == None: self.ui.textBrowser.append( 'Invalid filenames. Please make sure filenames are correct and files located in current folder.' ) else: self.PosData = data self.ui.textBrowser.append('Loaded Success.\n') @pyqtSlot(name='button_show_ion_map_click') def button_show_ion_map_click(self): self.ui.textBrowser.append('Processing...\n') self.pos_vis = APTDataViewer(self.PosData, point_size=self.point_size, max_ion=2000) @pyqtSlot(name='button_run_click') def button_run_click(self): ion_types = self.ui.le_ion_types.text() ion_types = ion_types.split(', ') method = self.ui.le_method.text() eps = float(self.ui.le_eps.text()) minpts = int(self.ui.le_minpts.text()) min_cluster_size = int(self.ui.le_min_cluster_size.text( )) if self.ui.le_min_cluster_size.text() != 'None' else minpts rho = float(self.ui.le_density.text()) det_eff = float(self.ui.le_det_eff.text()) con = float(self.ui.le_solute_con.text()) k = int( self.ui.le_k.text()) if self.ui.le_k.text() != 'None' else minpts significant_separation = float( self.ui.le_significant_separation.text()) cluster_member_prob = float(self.ui.le_cluster_member_prob.text()) min_node_size = int(self.ui.le_min_node_size.text( )) if self.ui.le_min_node_size.text() != 'None' else minpts est_bg = float(self.ui.le_est_bg.text() ) if self.ui.le_est_bg.text() != 'None' else np.inf node_similarity = float(self.ui.le_similarity.text()) if self.PosData == None: self.ui.textBrowser.append('Please Load Data First!\n') else: self.ui.textBrowser.append('Clustering started...') try: clustering_parameters = { 'ion_types': ion_types, 'method': method, 'eps': eps, 'minpts': minpts, 'min_node_size': min_node_size, 'significant_separation': significant_separation, 'k': k, 'node_similarity': node_similarity, 'cluster_member_prob': cluster_member_prob, 'est_bg': est_bg, 'min_cluster_size': min_cluster_size } self.DoClustering = DoClustering(self.PosData, clustering_parameters) self.thread_do_clustering = QThread() self.DoClustering.cluster_ready.connect( self.get_clustering_result) self.DoClustering.finished.connect( self.thread_do_clustering.quit) self.DoClustering.moveToThread(self.thread_do_clustering) self.thread_do_clustering.started.connect( self.DoClustering.cluster_analysis) self.thread_do_clustering.start() except: self.ui.textBrowser.append( 'Something is wrong in cluster analysis setting, please check again...' ) @pyqtSlot(object, name='get_clustering_result') def get_clustering_result(self, data): self.Clusterer = data self.ui.textBrowser.append('Clustering finished.\n') self.ui.button_show_visual.setEnabled(True) @pyqtSlot(name='button_terminate_click') def button_terminate_click(self): self.thread_do_clustering.quit() self.ui.textBrowser.append('Cluster analysis terminated!\n') @pyqtSlot(name='button_show_visual_click') def button_show_visual_click(self): self.clustering_vis = ClusteringView(self.Clusterer, self.show_bg, self.point_size) @pyqtSlot(name='button_save_click') def button_save_click(self): output_filename = self.ui.le_output_fname.text() try: if self.Clusterer.RD is not None: self.Clusterer.write_RD_to_file(output_filename + '_ol_RD_CD') self.Clusterer.cluster_stats(output_filename + '_cluster_stats') self.Clusterer.write_cluster_to_file(output_filename + '_clusters') self.Clusterer.write_indexed_cluster_rrng(output_filename + '_clusters') self.Clusterer.write_log(output_filename + '_log') except: self.ui.textBrowser.append('Invalid output filename. Try again.')
class ThreadlinkUtility(QMainWindow): """Main class for the PCBA Test Utility. Creates main window for the program, the file menu, status bar, and the settings/configuration window. """ def __init__(self): super().__init__() self.system_font = QApplication.font().family() self.label_font = QFont(self.system_font, 12) self.config_font = QFont(self.system_font, 12) self.config_path_font = QFont(self.system_font, 12) self.settings = QSettings("BeadedStream", "Threadlink TestUtility") settings_defaults = { "user_id": "", "port1_tac_id": "", "hex_files_path": "/path/to/hex/files", "report_dir_path": "/path/to/report/folder", "atprogram_file_path": "/path/to/atprogram.exe" } for key in settings_defaults: if not self.settings.value(key): self.settings.setValue(key, settings_defaults[key]) self.sm = serialmanager.SerialManager() self.serial_thread = QThread() self.sm.moveToThread(self.serial_thread) self.serial_thread.start() self.m = model.Model() self.r = report.Report() self.sm.port_unavailable_signal.connect(self.port_unavailable) # Part number : [serial prefix, procedure class] self.product_data = { "45211-01": ["THL", threadlink.Threadlink], } # Create program actions. self.config = QAction("Settings", self) self.config.setShortcut("Ctrl+E") self.config.setStatusTip("Program Settings") self.config.triggered.connect(self.configuration) self.quit = QAction("Quit", self) self.quit.setShortcut("Ctrl+Q") self.quit.setStatusTip("Exit Program") self.quit.triggered.connect(self.close) self.about_tu = QAction("About Threadlink Utility", self) self.about_tu.setShortcut("Ctrl+U") self.about_tu.setStatusTip("About Program") self.about_tu.triggered.connect(self.about_program) self.aboutqt = QAction("About Qt", self) self.aboutqt.setShortcut("Ctrl+I") self.aboutqt.setStatusTip("About Qt") self.aboutqt.triggered.connect(self.about_qt) # Create menubar self.menubar = self.menuBar() self.file_menu = self.menubar.addMenu("&File") self.file_menu.addAction(self.config) self.file_menu.addAction(self.quit) self.serial_menu = self.menubar.addMenu("&Serial") self.serial_menu.installEventFilter(self) self.ports_menu = QMenu("&Ports", self) self.serial_menu.addMenu(self.ports_menu) self.ports_menu.aboutToShow.connect(self.populate_ports) self.ports_group = QActionGroup(self) self.ports_group.triggered.connect(self.connect_port) self.help_menu = self.menubar.addMenu("&Help") self.help_menu.addAction(self.about_tu) self.help_menu.addAction(self.aboutqt) self.initUI() self.center() def center(self): """Centers the application on the screen the mouse pointer is currently on.""" frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) def resource_path(self, relative_path): """Gets the path of the application relative root path to allow us to find the logo.""" if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.abspath("."), relative_path) def initUI(self): """"Sets up the UI.""" RIGHT_SPACING = 350 LINE_EDIT_WIDTH = 200 self.central_widget = QWidget() self.tester_id_lbl = QLabel("Please enter tester ID: ") self.tester_id_lbl.setFont(self.label_font) self.pcba_pn_lbl = QLabel("Please select PCBA part number: ") self.pcba_pn_lbl.setFont(self.label_font) self.pcba_sn_lbl = QLabel("Please enter or scan DUT serial number: ") self.pcba_sn_lbl.setFont(self.label_font) self.tester_id_input = QLineEdit() self.tester_id_input.setText(self.settings.value("user_id")) self.pcba_sn_input = QLineEdit() self.tester_id_input.setFixedWidth(LINE_EDIT_WIDTH) self.pcba_sn_input.setFixedWidth(LINE_EDIT_WIDTH) self.pcba_pn_input = QComboBox() self.pcba_pn_input.addItem("45211-01") self.pcba_pn_input.setFixedWidth(LINE_EDIT_WIDTH) self.start_btn = QPushButton("Start") self.start_btn.setFixedWidth(200) self.start_btn.setAutoDefault(True) self.start_btn.clicked.connect(self.parse_values) self.logo_img = QPixmap(self.resource_path("h_logo.png")) self.logo_img = self.logo_img.scaledToWidth(600) self.logo = QLabel() self.logo.setPixmap(self.logo_img) hbox_logo = QHBoxLayout() hbox_logo.addStretch() hbox_logo.addWidget(self.logo) hbox_logo.addStretch() hbox_test_id = QHBoxLayout() hbox_test_id.addStretch() hbox_test_id.addWidget(self.tester_id_lbl) hbox_test_id.addWidget(self.tester_id_input) hbox_test_id.addSpacing(RIGHT_SPACING) hbox_pn = QHBoxLayout() hbox_pn.addStretch() hbox_pn.addWidget(self.pcba_pn_lbl) hbox_pn.addWidget(self.pcba_pn_input) hbox_pn.addSpacing(RIGHT_SPACING) hbox_sn = QHBoxLayout() hbox_sn.addStretch() hbox_sn.addWidget(self.pcba_sn_lbl) hbox_sn.addWidget(self.pcba_sn_input) hbox_sn.addSpacing(RIGHT_SPACING) hbox_start_btn = QHBoxLayout() hbox_start_btn.addStretch() hbox_start_btn.addWidget(self.start_btn) hbox_start_btn.addSpacing(RIGHT_SPACING) vbox = QVBoxLayout() vbox.addStretch() vbox.addLayout(hbox_logo) vbox.addSpacing(100) vbox.addLayout(hbox_test_id) vbox.addSpacing(50) vbox.addLayout(hbox_pn) vbox.addSpacing(50) vbox.addLayout(hbox_sn) vbox.addSpacing(50) vbox.addLayout(hbox_start_btn) vbox.addStretch() self.central_widget.setLayout(vbox) self.setCentralWidget(self.central_widget) self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT) self.setWindowTitle( "BeadedStream Manufacturing Threadlink Test Utility") def create_messagebox(self, type, title, text, info_text): """A helper method for creating message boxes.""" msgbox = QMessageBox(self) msgbox.setWindowTitle(title) msgbox.setText(text) msgbox.setInformativeText(info_text) if type == "Warning": msgbox.setIcon(QMessageBox.Warning) elif type == "Error": msgbox.setIcon(QMessageBox.Error) elif type == "Information": msgbox.setIcon(QMessageBox.Information) else: raise InvalidMsgType return msgbox def about_program(self): """Displays information about the program.""" QMessageBox.about(self, "About Threadlink Test Utility", ABOUT_TEXT) def about_qt(self): """Displays information about Qt.""" QMessageBox.aboutQt(self, "About Qt") def populate_ports(self): """Populates ports menu from connected COM ports.""" ports = serialmanager.SerialManager.scan_ports() self.ports_menu.clear() if not ports: self.ports_menu.addAction("None") self.sm.close_port() for port in ports: port_description = port.description action = self.ports_menu.addAction(port_description) port_name = port.device if self.sm.is_connected(port_name): action.setCheckable(True) action.setChecked(True) self.ports_group.addAction(action) def connect_port(self, action: QAction): """Connects to a COM port by parsing the text from a clicked QAction menu object.""" p = "COM[0-9]+" m = re.search(p, action.text()) if m: port_name = m.group() self.sm.open_port(port_name) else: QMessageBox.warning(self, "Warning", "Invalid port selection!") def port_unavailable(self): """Displays warning message about unavailable port.""" QMessageBox.warning(self, "Warning", "Port unavailable!") def parse_values(self): """Parses and validates input values from the start page.""" self.tester_id = self.tester_id_input.text().upper() self.settings.setValue("user_id", self.tester_id) self.pcba_pn = self.pcba_pn_input.currentText() self.pcba_sn = self.pcba_sn_input.text().upper() if (self.tester_id and self.pcba_pn and self.pcba_sn): # The serial number should be seven characters long and start with # the specific prefix for the given product. if (self.pcba_sn[0:3] == self.product_data[self.pcba_pn][0] and len(self.pcba_sn) == 7): self.r.write_data("tester_id", self.tester_id, "PASS") self.r.write_data("pcba_sn", self.pcba_sn, "PASS") self.r.write_data("pcba_pn", self.pcba_pn, "PASS") else: self.err_msg = self.create_messagebox("Warning", "Error", "Error", "Bad serial number!") self.err_msg.show() return else: self.err_msg = self.create_messagebox("Warning", "Error", "Error", "Missing value!") self.err_msg.show() return self.start_procedure() def start_procedure(self): """Sets up procedure layout by creating test statuses and initializing the appropriate board class (currently D505, potentially others in the future).""" central_widget = QWidget() status_lbl_stylesheet = ("QLabel {border: 2px solid grey;" "color: black; font-size: 20px}") status_style_pass = """QLabel {background: #8cff66; border: 2px solid grey; font-size: 20px}""" # ______Labels______ self.tester_id_status = QLabel(f"Tester ID: {self.tester_id}") self.pcba_pn_status = QLabel(f"PCBA PN: {self.pcba_pn}") self.pcba_sn_status = QLabel(f"PCBA SN: {self.pcba_sn}") self.input_i_status = QLabel(f"Input Current: _____ mA") self.supply_5v_status = QLabel("5V Supply: _____V") self.output_2p5v_status = QLabel("2.5V Output: _____V") self.supply_1p8v_status = QLabel("1.8V Supply: _____V") self.xmega_prog_status = QLabel("XMega Programming: _____") self.one_wire_prog_status = QLabel("1-Wire Programming:_____") self.internal_5v_status = QLabel("Internal 5V: _____V") self.tac_id_status = QLabel("TAC ID: _____") self.hall_effect_status = QLabel("Hall Effect Sensor Test:_____") self.led_test_status = QLabel("LED Test:_____") self.tester_id_status.setStyleSheet(status_style_pass) self.pcba_pn_status.setStyleSheet(status_style_pass) self.pcba_sn_status.setStyleSheet(status_style_pass) self.input_i_status.setStyleSheet(status_lbl_stylesheet) self.supply_5v_status.setStyleSheet(status_lbl_stylesheet) self.output_2p5v_status.setStyleSheet(status_lbl_stylesheet) self.supply_1p8v_status.setStyleSheet(status_lbl_stylesheet) self.xmega_prog_status.setStyleSheet(status_lbl_stylesheet) self.one_wire_prog_status.setStyleSheet(status_lbl_stylesheet) self.internal_5v_status.setStyleSheet(status_lbl_stylesheet) self.tac_id_status.setStyleSheet(status_lbl_stylesheet) self.hall_effect_status.setStyleSheet(status_lbl_stylesheet) self.led_test_status.setStyleSheet(status_lbl_stylesheet) # ______Layout______ status_vbox1 = QVBoxLayout() status_vbox1.setSpacing(10) status_vbox1.addWidget(self.tester_id_status) status_vbox1.addWidget(self.pcba_pn_status) status_vbox1.addWidget(self.pcba_sn_status) status_vbox1.addWidget(self.input_i_status) status_vbox1.addWidget(self.supply_5v_status) status_vbox1.addWidget(self.output_2p5v_status) status_vbox1.addWidget(self.supply_1p8v_status) status_vbox1.addWidget(self.xmega_prog_status) status_vbox1.addWidget(self.one_wire_prog_status) status_vbox1.addWidget(self.internal_5v_status) status_vbox1.addWidget(self.tac_id_status) status_vbox1.addWidget(self.hall_effect_status) status_vbox1.addWidget(self.led_test_status) status_vbox1.addStretch() status_group = QGroupBox("Test Statuses") status_group.setFont(self.label_font) status_group.setLayout(status_vbox1) # Use the product data dictionary to call the procdure class that # corresponds to the part number. Create an instance of it passing it # the instances of test_utility, model, serial_manager and report. self.procedure = self.product_data[self.pcba_pn][1](self, self.m, self.sm, self.r) grid = QGridLayout() grid.setColumnStretch(0, 5) grid.setColumnStretch(1, 15) grid.addWidget(status_group, 0, 0, Qt.AlignTop) grid.addWidget(self.procedure, 0, 1) # layout = QHBoxLayout() # layout.addWidget(self.procedure) central_widget.setLayout(grid) self.setCentralWidget(central_widget) def configuration(self): """Sets up configuration/settings window elements.""" FILE_BTN_WIDTH = 30 self.settings_widget = QDialog(self) port1_lbl = QLabel("Port 1 TAC ID:") port1_lbl.setFont(self.config_font) self.port1_tac_id = QLineEdit(self.settings.value("port1_tac_id")) port_layout = QGridLayout() port_layout.addWidget(port1_lbl, 0, 0) port_layout.addWidget(self.port1_tac_id, 0, 1) port_group = QGroupBox("TAC IDs") port_group.setLayout(port_layout) self.hex_btn = QPushButton("[...]") self.hex_btn.setFixedWidth(FILE_BTN_WIDTH) self.hex_btn.clicked.connect(self.set_hex_dir) self.hex_lbl = QLabel("Choose the location of hex files: ") self.hex_lbl.setFont(self.config_font) self.hex_path_lbl = QLabel(self.settings.value("hex_files_path")) self.hex_path_lbl.setFont(self.config_path_font) self.hex_path_lbl.setStyleSheet("QLabel {color: blue}") self.report_btn = QPushButton("[...]") self.report_btn.setFixedWidth(FILE_BTN_WIDTH) self.report_btn.clicked.connect(self.set_report_location) self.report_lbl = QLabel("Set report save location: ") self.report_lbl.setFont(self.config_font) self.report_path_lbl = QLabel(self.settings.value("report_dir_path")) self.report_path_lbl.setFont(self.config_path_font) self.report_path_lbl.setStyleSheet("QLabel {color: blue}") self.atprogram_btn = QPushButton("[...]") self.atprogram_btn.setFixedWidth(FILE_BTN_WIDTH) self.atprogram_btn.clicked.connect(self.choose_atprogram_file) self.atprogram_lbl = QLabel("Select atprogram.exe.") self.atprogram_lbl.setFont(self.config_font) self.atprogram_path_lbl = QLabel( self.settings.value("atprogram_file_path")) self.atprogram_path_lbl.setFont(self.config_path_font) self.atprogram_path_lbl.setStyleSheet("QLabel {color: blue}") save_loc_layout = QGridLayout() save_loc_layout.addWidget(self.hex_lbl, 0, 0) save_loc_layout.addWidget(self.hex_btn, 0, 1) save_loc_layout.addWidget(self.hex_path_lbl, 1, 0) save_loc_layout.addWidget(self.report_lbl, 2, 0) save_loc_layout.addWidget(self.report_btn, 2, 1) save_loc_layout.addWidget(self.report_path_lbl, 3, 0) save_loc_layout.addWidget(self.atprogram_lbl, 4, 0) save_loc_layout.addWidget(self.atprogram_btn, 4, 1) save_loc_layout.addWidget(self.atprogram_path_lbl, 5, 0) save_loc_group = QGroupBox("Save Locations") save_loc_group.setLayout(save_loc_layout) apply_btn = QPushButton("Apply Settings") apply_btn.clicked.connect(self.apply_settings) cancel_btn = QPushButton("Cancel") cancel_btn.clicked.connect(self.cancel_settings) button_layout = QHBoxLayout() button_layout.addWidget(cancel_btn) button_layout.addStretch() button_layout.addWidget(apply_btn) hbox_top = QHBoxLayout() hbox_top.addWidget(port_group) hbox_bottom = QHBoxLayout() # hbox_bottom.addStretch() hbox_bottom.addWidget(save_loc_group) # hbox_bottom.addStretch() grid = QGridLayout() grid.addLayout(hbox_top, 0, 0) grid.addLayout(hbox_bottom, 1, 0) grid.addLayout(button_layout, 2, 0) grid.setHorizontalSpacing(100) self.settings_widget.setLayout(grid) self.settings_widget.setWindowTitle( "Threadlink Configuration Settings") self.settings_widget.show() # self.settings_widget.resize(800, 600) # frameGm = self.frameGeometry() # screen = QApplication.desktop().screenNumber( # QApplication.desktop().cursor().pos()) # centerPoint = QApplication.desktop().screenGeometry(screen).center() # frameGm.moveCenter(centerPoint) # self.settings_widget.move(frameGm.topLeft()) def set_hex_dir(self): """Opens file dialog for selecting the hex files directory.""" hex_files_path = QFileDialog.getExistingDirectory( self, "Select hex files directory.") self.hex_path_lbl.setText(hex_files_path) def set_report_location(self): """Opens file dialog for setting the save location for the report.""" report_dir = QFileDialog.getExistingDirectory( self, "Select report save location.") self.report_path_lbl.setText(report_dir) def choose_atprogram_file(self): """Opens file dialog for selecting the atprogram executable.""" atprogram_file_path = QFileDialog.getOpenFileName( self, "Select atprogram.exe.", "", "Application (*.exe)")[0] self.atprogram_path_lbl.setText(atprogram_file_path) def cancel_settings(self): """Close the settings widget without applying changes.""" self.settings_widget.close() def apply_settings(self): """Read user inputs and apply settings.""" p = r"([a-fA-F0-9]){8}" port1_value = self.port1_tac_id.text() if re.fullmatch(p, port1_value): self.settings.setValue("port1_tac_id", port1_value) else: QMessageBox.warning( self.settings_widget, "Warning!", f"Bad TAC ID!\n" "IDs are 8 digit hex values.\n" "E.g.: 000a5296") return self.settings.setValue("hex_files_path", self.hex_path_lbl.text()) self.settings.setValue("report_dir_path", self.report_path_lbl.text()) self.settings.setValue("atprogram_file_path", self.atprogram_path_lbl.text()) QMessageBox.information(self.settings_widget, "Information", "Settings applied!") self.settings_widget.close() def closeEvent(self, event): """Override QWidget closeEvent to provide user with confirmation dialog and ensure threads are terminated appropriately.""" event.accept() quit_msg = "Are you sure you want to exit the program?" confirmation = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) if confirmation == QMessageBox.Yes: self.serial_thread.quit() self.serial_thread.wait() event.accept() else: event.ignore()
class ConsoleWidget(RichJupyterWidget, PMDockObject): def __init__(self, *args, **kwargs): super(ConsoleWidget, self).__init__(*args, **kwargs) self.is_first_execution = True self.confirm_restart = False # print(cmd) self.commands_pool = [] # [('import sys',True,''),(cmd,False,'')] # print(self.commands_pool) def change_ui_theme(self, style: str): if style == 'Fusion': self.style_sheet = default_light_style_sheet self.syntax_style = default_light_syntax_style elif style == 'Qdarkstyle': self.style_sheet = default_dark_style_sheet self.syntax_style = default_dark_syntax_style elif style.lower() == 'windowsvista': self.style_sheet = default_light_style_sheet self.syntax_style = default_light_syntax_style elif style.lower() == 'windows': self.style_sheet = default_light_style_sheet self.syntax_style = default_light_syntax_style def _handle_kernel_died(self, since_last_heartbit): self.is_first_execution = True self.restart_kernel(None, True) self.initialize_ipython_builtins() self.execute_command('') return True def _handle_execute_input(self, msg): super()._handle_execute_result(msg) def setup_ui(self): self.kernel_manager = None self.kernel_client = None # initialize by thread self.init_thread = QThread(self) self.console_object = ConsoleInitThread() self.console_object.moveToThread(self.init_thread) self.console_object.initialized.connect(self.slot_initialized) self.init_thread.finished.connect(self.console_object.deleteLater) self.init_thread.finished.connect(self.init_thread.deleteLater) self.init_thread.started.connect(self.console_object.run) self.init_thread.start() cursor: QTextCursor = self._prompt_cursor cursor.movePosition(QTextCursor.End) _ = lambda s: s self.context_menu = QMenu() restart_action = self.context_menu.addAction(_('Restart')) restart_action.triggered.connect(self._restart_kernel) save_action = self.context_menu.addAction(_('Save as ')) cancel_action = self.context_menu.addAction(_('Undo')) redo_action = self.context_menu.addAction(_('Redo')) delete_action = self.context_menu.addAction(_('Delete')) style = self.lib.Program.get_settings()['theme'] self.change_ui_theme(style) def _custom_context_menu_requested(self, pos): self.context_menu.exec_(self.mapToGlobal(pos)) def _restart_kernel(self, arg1): self.is_first_execution = True self.restart_kernel(None, True) self.initialize_ipython_builtins() self.execute_command('') return True def connect_to_datamanager(self, data_manager): self.data_manager = data_manager self.lib = self.data_manager def slot_initialized(self, kernel_manager, kernel_client): """ Args: kernel_manager: `qtconsole.manager.QtKernelManager` kernel_client: `qtconsole.manager.QtKernelManager.client` Returns: """ self.kernel_manager = kernel_manager self.kernel_client = kernel_client self.initialize_ipython_builtins() def initialize_ipython_builtins(self): from pmgwidgets import get_parent_path path = get_parent_path(__file__, 5) cmd = 'import sys;sys.path.append(r\'%s\')' % path self.execute_command(cmd, True, '') ini_py = os.path.join(self.lib.get_main_program_dir(), 'extensions', 'packages', 'ipython_console', 'initialisation.py') self.execute_file(ini_py, hidden=True) for source, hidden, hint_text in self.commands_pool: self.execute_command(source, hidden, hint_text) def _update_list(self): try: super(ConsoleWidget, self)._update_list() except BaseException: import traceback traceback.print_exc() def _handle_complete_reply(self, msg): """ 重写,加上trycatch,直接禁用了没有其他的变化,故不做类型标注。 :param msg: :return: """ try: super()._handle_complete_reply(msg) except BaseException: import traceback traceback.print_exc() def _banner_default(self): """ 自定义控制台开始的文字 Returns: """ return 'Welcome To PyMiner!\n' def closeEvent(self, event): if self.init_thread.isRunning(): self.console_object.stop() self.init_thread.quit() self.init_thread.wait(500) super(ConsoleWidget, self).closeEvent(event) def execute_file(self, file: str, hidden: bool = False): if not os.path.exists(file) or not file.endswith('.py'): raise FileNotFoundError(f'{file} not found or invalid') base = os.path.basename(file) cmd = os.path.splitext(base)[0] with open(file, 'r', encoding='utf-8') as f: source = f.read() self.execute_command(source, hidden=hidden, hint_text=cmd) def execute_command(self, source, hidden: bool = False, hint_text: str = ''): """ :param source: :param hidden: :param hint_text: 运行代码前显示的提示 :return: """ cursor: QTextCursor = self._prompt_cursor cursor.movePosition(QTextCursor.End) # 运行文件时,显示文件名,无换行符,执行选中内容时,包含换行符 # 检测换行符,在ipy console中显示执行脚本内容 hint_row_list = hint_text.split("\n") for hint in hint_row_list: if hint != "": cursor.insertText('%s\n' % hint) self._insert_continuation_prompt(cursor) else: # 删除多余的continuation_prompt self.undo() self._finalize_input_request( ) # display input string buffer in console. cursor.movePosition(QTextCursor.End) if self.kernel_client is None: self.commands_pool.append((source, hidden, hint_text)) else: self._execute(source, hidden) def _handle_stream(self, msg): cursor: QTextCursor = self._prompt_cursor cursor.movePosition(QTextCursor.End) print(msg['content']['text']) msg_lines = msg['content']['text'].strip().split('\n') data_b64 = msg_lines[-1] try: data_b64_dic = pickle.loads(base64.b64decode(data_b64)) # got data except BaseException: pass else: msg['content']['text'] = '\n'.join(msg_lines[:-1]) data = {} for key in data_b64_dic: var = data_b64_dic[key] try: data[key] = pickle.loads( base64.b64decode(data_b64_dic[key])) except BaseException: import traceback traceback.print_exc() self.data = self.data_manager.get_all_var() data = { k: v for k, v in data.items() if k not in self.data or str(self.data[k]) != str(v) } try: self.data_manager.set_var_dict(data, 'ipython') except Exception as e: msg['content']['text'] += f'\n{type(e).__name__}: {e}' import traceback traceback.print_exc() super()._handle_stream(msg) def append_stream(self, text): """重写的方法。原本before_prompt属性是False。""" self._append_plain_text(text, before_prompt=False) def _execute(self, source: str, hidden: bool = False): if not self.is_source_code_legal(source): QMessageBox.warning(self, '警告', '命令\n\"%s\"\n为无效命令。' % source) return # if self.is_first_execution: # self.is_first_execution = False # else: # self.data = self.data_manager.get_all_var() # send data # data_b64_dic = {} # for key in self.data: # var = self.data[key] # try: # data_b64_dic[key] = base64.b64encode( # pickle.dumps(var)).decode('ascii') # except BaseException: # import traceback # traceback.print_exc() # data_b64 = base64.b64encode( # pickle.dumps(data_b64_dic)).decode('ascii') # source = f'__inject("{data_b64}")\n{source}' super()._execute(source, hidden) def is_source_code_legal(self, source_code: str) -> bool: """ 判断注入到shell的命令是否合法,不合法的话,就避免执行这个函数。 :param source_code: :return: """ s = source_code.strip() s = s.split('(')[0] # if s in self.illegal_commands: # return False return True
class MainWindow(QMainWindow): def __init__(self, params_obj): super(MainWindow, self).__init__() self.params_cb = params_obj self.sim = Simulation(params_obj) self.sim.set_params(params_obj) grid = QGridLayout() grid.addWidget( self.createActivation('Stance to lift off', 6, params_obj.set_activation, params_obj.toggle), 0, 0) grid.addWidget( self.createActivation('Swing to touch down', 3, params_obj.set_activation, params_obj.toggle), 0, 1) grid.addWidget( self.createActivation('Touch down to stance', 2, params_obj.set_activation, params_obj.toggle), 1, 0) grid.addWidget( self.createActivation('Lift off to swing', 2, params_obj.set_activation, params_obj.toggle), 1, 1) grid.addWidget(self.createReflex(key='Hip angle liftoff'), 2, 0) grid.addWidget(self.createReflex(key='Ankle unloading liftoff'), 2, 1) grid.addWidget(self.createReflex(key='Hip angle touchdown'), 3, 0) grid.addWidget(self.createReflex(key='Ankle unloading touchdown'), 3, 1) self.sim_thread = QThread() self.sim.moveToThread(self.sim_thread) self.sim_thread.started.connect(self.sim.run) self.sim_thread.start() b = QPushButton('Run') b.pressed.connect(self.sim_thread.start) grid.addWidget(b, 3, 2) self.w = QWidget() self.w.setLayout(grid) self.setCentralWidget(self.w) self.setWindowTitle("Parameter tuning") self.resize(400, 300) self.show() def createActivation(self, key, n_values, value_cb, toggle_cb): print key groupBox = QGroupBox(key) radio1 = QRadioButton('Enable ?') radio1.setChecked(False) radio1.toggled.connect(lambda: toggle_cb(key)) vbox = QVBoxLayout() vbox.addWidget(radio1) for i in range(n_values): vbox.addWidget(self.get_slider(key, value_cb, i)) vbox.addStretch(1) groupBox.setLayout(vbox) return groupBox def get_slider(self, key, cb_method, idx=0): slider = QSlider(Qt.Horizontal) slider.setFocusPolicy(Qt.StrongFocus) slider.setTickPosition(QSlider.TicksBothSides) slider.setTickInterval(10) slider.setTickPosition(1) slider.setSingleStep(1) slider.sliderReleased.connect( lambda: cb_method(key, idx, slider.value())) return slider def createReflex(self, key='Default'): groupBox = QGroupBox(key) vbox = QVBoxLayout() slider = QSlider(Qt.Horizontal) slider.setFocusPolicy(Qt.StrongFocus) slider.setTickPosition(QSlider.TicksBothSides) slider.setTickInterval(10) slider.setSingleStep(1) vbox.addWidget(slider) vbox.addStretch(1) groupBox.setLayout(vbox) return groupBox
class Windows(QDialog, mainUI.Ui_Dialog): isRunning = False def __init__(self, parent=None): super(Windows, self).__init__(parent) self.selectedDict = None self.currentConfig = dict() self.localWords = [] self.selectedGroups = [] self.workerThread = QThread(self) self.workerThread.start() self.updateCheckThead = QThread(self) self.updateCheckThead.start() self.audioDownloadThread = QThread(self) self.updateCheckWork = None self.loginWorker = None self.queryWorker = None self.pullWorker = None self.audioDownloadWorker = None self.setupUi(self) self.setWindowTitle(MODEL_NAME) self.setupLogger() self.initCore() self.checkUpdate() # self.__dev() # 以备调试时使用 def __dev(self): def on_dev(): logger.debug('whatever') self.devBtn = QPushButton('Magic Button', self.mainTab) self.devBtn.clicked.connect(on_dev) self.gridLayout_4.addWidget(self.devBtn, 4, 3, 1, 1) def closeEvent(self, event): # 插件关闭时退出所有线程 if self.workerThread.isRunning(): self.workerThread.requestInterruption() self.workerThread.quit() self.workerThread.wait() if self.updateCheckThead.isRunning(): self.updateCheckThead.quit() self.updateCheckThead.wait() if self.audioDownloadThread.isRunning(): self.audioDownloadThread.requestInterruption() self.workerThread.quit() self.workerThread.wait() event.accept() def setupLogger(self): """初始化 Logger """ def onDestroyed(): logger.removeHandler(QtHandler) # 防止 debug 信息写入stdout/stderr 导致 Anki 崩溃 logging.basicConfig(handlers=[logging.FileHandler('dict2anki.log', 'w', 'utf-8')], level=logging.DEBUG, format='[%(asctime)s][%(levelname)8s] -- %(message)s - (%(name)s)') logTextBox = QPlainTextEdit(self) logTextBox.setLineWrapMode(QPlainTextEdit.NoWrap) layout = QVBoxLayout() layout.addWidget(logTextBox) self.logTab.setLayout(layout) QtHandler = Handler(self) logger.addHandler(QtHandler) QtHandler.newRecord.connect(logTextBox.appendPlainText) # 日志Widget销毁时移除 Handlers logTextBox.destroyed.connect(onDestroyed) def setupGUIByConfig(self): config = mw.addonManager.getConfig(__name__) self.deckComboBox.setCurrentText(config['deck']) self.dictionaryComboBox.setCurrentIndex(config['selectedDict']) self.apiComboBox.setCurrentIndex(config['selectedApi']) self.usernameLineEdit.setText(config['credential'][config['selectedDict']]['username']) self.passwordLineEdit.setText(config['credential'][config['selectedDict']]['password']) self.cookieLineEdit.setText(config['credential'][config['selectedDict']]['cookie']) self.definitionCheckBox.setChecked(config['definition']) self.imageCheckBox.setChecked(config['image']) self.sentenceCheckBox.setChecked(config['sentence']) self.phraseCheckBox.setChecked(config['phrase']) self.AmEPhoneticCheckBox.setChecked(config['AmEPhonetic']) self.BrEPhoneticCheckBox.setChecked(config['BrEPhonetic']) self.BrEPronRadioButton.setChecked(config['BrEPron']) self.AmEPronRadioButton.setChecked(config['AmEPron']) self.noPronRadioButton.setChecked(config['noPron']) self.selectedGroups = config['selectedGroup'] def initCore(self): self.usernameLineEdit.hide() self.usernameLabel.hide() self.passwordLabel.hide() self.passwordLineEdit.hide() self.dictionaryComboBox.addItems([d.name for d in dictionaries]) self.apiComboBox.addItems([d.name for d in apis]) self.deckComboBox.addItems(getDeckList()) self.setupGUIByConfig() def getAndSaveCurrentConfig(self) -> dict: """获取当前设置""" currentConfig = dict( selectedDict=self.dictionaryComboBox.currentIndex(), selectedApi=self.apiComboBox.currentIndex(), selectedGroup=self.selectedGroups, deck=self.deckComboBox.currentText(), username=self.usernameLineEdit.text(), password=Mask(self.passwordLineEdit.text()), cookie=Mask(self.cookieLineEdit.text()), definition=self.definitionCheckBox.isChecked(), sentence=self.sentenceCheckBox.isChecked(), image=self.imageCheckBox.isChecked(), phrase=self.phraseCheckBox.isChecked(), AmEPhonetic=self.AmEPhoneticCheckBox.isChecked(), BrEPhonetic=self.BrEPhoneticCheckBox.isChecked(), BrEPron=self.BrEPronRadioButton.isChecked(), AmEPron=self.AmEPronRadioButton.isChecked(), noPron=self.noPronRadioButton.isChecked(), ) logger.info(f'当前设置:{currentConfig}') self._saveConfig(currentConfig) self.currentConfig = currentConfig return currentConfig @staticmethod def _saveConfig(config): _config = deepcopy(config) _config['credential'] = [dict(username='', password='', cookie='')] * len(dictionaries) _config['credential'][_config['selectedDict']] = dict( username=_config.pop('username'), password=str(_config.pop('password')), cookie=str(_config.pop('cookie')) ) maskedConfig = deepcopy(_config) maskedCredential = [ dict( username=c['username'], password=Mask(c['password']), cookie=Mask(c['cookie'])) for c in maskedConfig['credential'] ] maskedConfig['credential'] = maskedCredential logger.info(f'保存配置项:{maskedConfig}') mw.addonManager.writeConfig(__name__, _config) def checkUpdate(self): @pyqtSlot(str, str) return def on_haveNewVersion(version, changeLog): if askUser(f'有新版本:{version}是否更新?\n\n{changeLog.strip()}'): openLink(RELEASE_URL) self.updateCheckWork = VersionCheckWorker() self.updateCheckWork.moveToThread(self.updateCheckThead) self.updateCheckWork.haveNewVersion.connect(on_haveNewVersion) self.updateCheckWork.finished.connect(self.updateCheckThead.quit) self.updateCheckWork.start.connect(self.updateCheckWork.run) self.updateCheckWork.start.emit() @pyqtSlot(int) def on_dictionaryComboBox_currentIndexChanged(self, index): """词典候选框改变事件""" self.currentDictionaryLabel.setText(f'当前选择词典: {self.dictionaryComboBox.currentText()}') config = mw.addonManager.getConfig(__name__) self.cookieLineEdit.setText(config['credential'][index]['cookie']) @pyqtSlot() def on_pullRemoteWordsBtn_clicked(self): """获取单词按钮点击事件""" if not self.deckComboBox.currentText(): showInfo('\n请选择或输入要同步的牌组') return self.mainTab.setEnabled(False) self.progressBar.setValue(0) self.progressBar.setMaximum(0) currentConfig = self.getAndSaveCurrentConfig() self.selectedDict = dictionaries[currentConfig['selectedDict']]() # 登陆线程 self.loginWorker = LoginStateCheckWorker(self.selectedDict.checkCookie, json.loads(self.cookieLineEdit.text() or '{}')) self.loginWorker.moveToThread(self.workerThread) self.loginWorker.start.connect(self.loginWorker.run) self.loginWorker.logSuccess.connect(self.onLogSuccess) self.loginWorker.logFailed.connect(self.onLoginFailed) self.loginWorker.start.emit() @pyqtSlot() def onLoginFailed(self): showCritical('第一次登录或cookie失效!请重新登录') self.progressBar.setValue(0) self.progressBar.setMaximum(1) self.mainTab.setEnabled(True) self.cookieLineEdit.clear() self.loginDialog = LoginDialog( loginUrl=self.selectedDict.loginUrl, loginCheckCallbackFn=self.selectedDict.loginCheckCallbackFn, parent=self ) self.loginDialog.loginSucceed.connect(self.onLogSuccess) self.loginDialog.show() @pyqtSlot(str) def onLogSuccess(self, cookie): self.cookieLineEdit.setText(cookie) self.getAndSaveCurrentConfig() self.selectedDict.checkCookie(json.loads(cookie)) self.selectedDict.getGroups() container = QDialog(self) group = wordGroup.Ui_Dialog() group.setupUi(container) for groupName in [str(group_name) for group_name, _ in self.selectedDict.groups]: item = QListWidgetItem() item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setText(groupName) item.setCheckState(Qt.Unchecked) group.wordGroupListWidget.addItem(item) # 恢复上次选择的单词本分组 if self.selectedGroups: for groupName in self.selectedGroups[self.currentConfig['selectedDict']]: items = group.wordGroupListWidget.findItems(groupName, Qt.MatchExactly) for item in items: item.setCheckState(Qt.Checked) else: self.selectedGroups = [list()] * len(dictionaries) def onAccepted(): """选择单词本弹窗确定事件""" # 清空 listWidget self.newWordListWidget.clear() self.needDeleteWordListWidget.clear() self.mainTab.setEnabled(False) selectedGroups = [group.wordGroupListWidget.item(index).text() for index in range(group.wordGroupListWidget.count()) if group.wordGroupListWidget.item(index).checkState() == Qt.Checked] # 保存分组记录 self.selectedGroups[self.currentConfig['selectedDict']] = selectedGroups self.progressBar.setValue(0) self.progressBar.setMaximum(1) logger.info(f'选中单词本{selectedGroups}') self.getRemoteWordList(selectedGroups) def onRejected(): """选择单词本弹窗取消事件""" self.progressBar.setValue(0) self.progressBar.setMaximum(1) self.mainTab.setEnabled(True) group.buttonBox.accepted.connect(onAccepted) group.buttonBox.rejected.connect(onRejected) container.exec() def getRemoteWordList(self, selected_groups: [str]): """根据选中到分组获取分组下到全部单词,并添加到 newWordListWidget""" group_map = dict(self.selectedDict.groups) self.localWords = getWordsByDeck(self.deckComboBox.currentText()) # 启动单词获取线程 self.pullWorker = RemoteWordFetchingWorker(self.selectedDict, [(group_name, group_map[group_name],) for group_name in selected_groups]) self.pullWorker.moveToThread(self.workerThread) self.pullWorker.start.connect(self.pullWorker.run) self.pullWorker.tick.connect(lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.pullWorker.setProgress.connect(self.progressBar.setMaximum) self.pullWorker.doneThisGroup.connect(self.insertWordToListWidget) self.pullWorker.done.connect(self.on_allPullWork_done) self.pullWorker.start.emit() @pyqtSlot(list) def insertWordToListWidget(self, words: list): """一个分组获取完毕事件""" for word in words: wordItem = QListWidgetItem(word, self.newWordListWidget) wordItem.setData(Qt.UserRole, None) self.newWordListWidget.clearSelection() @pyqtSlot() def on_allPullWork_done(self): """全部分组获取完毕事件""" localWordList = set(getWordsByDeck(self.deckComboBox.currentText())) remoteWordList = set([self.newWordListWidget.item(row).text() for row in range(self.newWordListWidget.count())]) newWords = remoteWordList - localWordList # 新单词 needToDeleteWords = localWordList - remoteWordList # 需要删除的单词 logger.info(f'本地: {localWordList}') logger.info(f'远程: {remoteWordList}') logger.info(f'待查: {newWords}') logger.info(f'待删: {needToDeleteWords}') waitIcon = QIcon(':/icons/wait.png') delIcon = QIcon(':/icons/delete.png') self.newWordListWidget.clear() self.needDeleteWordListWidget.clear() for word in needToDeleteWords: item = QListWidgetItem(word) item.setCheckState(Qt.Checked) item.setIcon(delIcon) self.needDeleteWordListWidget.addItem(item) for word in newWords: item = QListWidgetItem(word) item.setIcon(waitIcon) self.newWordListWidget.addItem(item) self.newWordListWidget.clearSelection() self.dictionaryComboBox.setEnabled(True) self.apiComboBox.setEnabled(True) self.deckComboBox.setEnabled(True) self.pullRemoteWordsBtn.setEnabled(True) self.queryBtn.setEnabled(self.newWordListWidget.count() > 0) self.syncBtn.setEnabled(self.newWordListWidget.count() == 0 and self.needDeleteWordListWidget.count() > 0) if self.needDeleteWordListWidget.count() == self.newWordListWidget.count() == 0: logger.info('无需同步') tooltip('无需同步') self.mainTab.setEnabled(True) @pyqtSlot() def on_queryBtn_clicked(self): logger.info('点击查询按钮') currentConfig = self.getAndSaveCurrentConfig() self.queryBtn.setEnabled(False) self.pullRemoteWordsBtn.setEnabled(False) self.syncBtn.setEnabled(False) wordList = [] selectedWords = self.newWordListWidget.selectedItems() if selectedWords: # 如果选中单词则只查询选中的单词 for wordItem in selectedWords: wordBundle = dict() row = self.newWordListWidget.row(wordItem) wordBundle['term'] = wordItem.text() for configName in BASIC_OPTION + EXTRA_OPTION: wordBundle[configName] = currentConfig[configName] wordBundle['row'] = row wordList.append(wordBundle) else: # 没有选择则查询全部 for row in range(self.newWordListWidget.count()): wordBundle = dict() wordItem = self.newWordListWidget.item(row) wordBundle['term'] = wordItem.text() for configName in BASIC_OPTION + EXTRA_OPTION: wordBundle[configName] = currentConfig[configName] wordBundle['row'] = row wordList.append(wordBundle) logger.info(f'待查询单词{wordList}') # 查询线程 self.progressBar.setMaximum(len(wordList)) self.queryWorker = QueryWorker(wordList, apis[currentConfig['selectedApi']]) self.queryWorker.moveToThread(self.workerThread) self.queryWorker.thisRowDone.connect(self.on_thisRowDone) self.queryWorker.thisRowFailed.connect(self.on_thisRowFailed) self.queryWorker.tick.connect(lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.queryWorker.allQueryDone.connect(self.on_allQueryDone) self.queryWorker.start.connect(self.queryWorker.run) self.queryWorker.start.emit() @pyqtSlot(int, dict) def on_thisRowDone(self, row, result): """该行单词查询完毕""" doneIcon = QIcon(':/icons/done.png') wordItem = self.newWordListWidget.item(row) wordItem.setIcon(doneIcon) wordItem.setData(Qt.UserRole, result) @pyqtSlot(int) def on_thisRowFailed(self, row): failedIcon = QIcon(':/icons/failed.png') failedWordItem = self.newWordListWidget.item(row) failedWordItem.setIcon(failedIcon) @pyqtSlot() def on_allQueryDone(self): failed = [] for i in range(self.newWordListWidget.count()): wordItem = self.newWordListWidget.item(i) if not wordItem.data(Qt.UserRole): failed.append(wordItem.text()) if failed: logger.warning(f'查询失败或未查询:{failed}') self.pullRemoteWordsBtn.setEnabled(True) self.queryBtn.setEnabled(True) self.syncBtn.setEnabled(True) @pyqtSlot() def on_syncBtn_clicked(self): failedGenerator = (self.newWordListWidget.item(row).data(Qt.UserRole) is None for row in range(self.newWordListWidget.count())) if any(failedGenerator): if not askUser('存在未查询或失败的单词,确定要加入单词本吗?\n 你可以选择失败的单词点击 "查询按钮" 来重试。'): return currentConfig = self.getAndSaveCurrentConfig() model = getOrCreateModel(MODEL_NAME) getOrCreateModelCardTemplate(model, 'default') deck = getOrCreateDeck(self.deckComboBox.currentText()) logger.info('同步点击') audiosDownloadTasks = [] newWordCount = self.newWordListWidget.count() # 判断是否需要下载发音 if currentConfig['noPron']: logger.info('不下载发音') whichPron = None else: whichPron = 'AmEPron' if self.AmEPronRadioButton.isChecked() else 'BrEPron' logger.info(f'下载发音{whichPron}') added = 0 for row in range(newWordCount): wordItem = self.newWordListWidget.item(row) wordItemData = wordItem.data(Qt.UserRole) if wordItemData: addNoteToDeck(deck, model, currentConfig, wordItemData) added += 1 # 添加发音任务 if whichPron and wordItemData.get(whichPron): audiosDownloadTasks.append((f"{whichPron}_{wordItemData['term']}.mp3", wordItemData[whichPron],)) mw.reset() logger.info(f'发音下载任务:{audiosDownloadTasks}') if audiosDownloadTasks: self.syncBtn.setEnabled(False) self.progressBar.setValue(0) self.progressBar.setMaximum(len(audiosDownloadTasks)) if self.audioDownloadThread is not None: self.audioDownloadThread.requestInterruption() self.audioDownloadThread.quit() self.audioDownloadThread.wait() self.audioDownloadThread = QThread(self) self.audioDownloadThread.start() self.audioDownloadWorker = AudioDownloadWorker(audiosDownloadTasks) self.audioDownloadWorker.moveToThread(self.audioDownloadThread) self.audioDownloadWorker.tick.connect(lambda: self.progressBar.setValue(self.progressBar.value() + 1)) self.audioDownloadWorker.start.connect(self.audioDownloadWorker.run) self.audioDownloadWorker.done.connect(lambda: tooltip(f'发音下载完成')) self.audioDownloadWorker.done.connect(self.audioDownloadThread.quit) self.audioDownloadWorker.start.emit() self.newWordListWidget.clear() needToDeleteWordItems = [ self.needDeleteWordListWidget.item(row) for row in range(self.needDeleteWordListWidget.count()) if self.needDeleteWordListWidget.item(row).checkState() == Qt.Checked ] needToDeleteWords = [i.text() for i in needToDeleteWordItems] deleted = 0 if needToDeleteWords and askUser(f'确定要删除这些单词吗:{needToDeleteWords[:3]}...({len(needToDeleteWords)}个)', title='Dict2Anki', parent=self): needToDeleteWordNoteIds = getNotes(needToDeleteWords, currentConfig['deck']) mw.col.remNotes(needToDeleteWordNoteIds) deleted += 1 mw.col.reset() mw.reset() for item in needToDeleteWordItems: self.needDeleteWordListWidget.takeItem(self.needDeleteWordListWidget.row(item)) logger.info('删除完成') logger.info('完成') if not audiosDownloadTasks: tooltip(f'添加{added}个笔记\n删除{deleted}个笔记')
class NetifconQueryTab(AnalysisTab): """A netifcon query.""" def __init__(self, parent, policy, perm_map): super(NetifconQueryTab, self).__init__(parent) self.log = logging.getLogger(__name__) self.policy = policy self.query = NetifconQuery(policy) self.setupUi() def __del__(self): self.thread.quit() self.thread.wait(5000) logging.getLogger("setools.netifconquery").removeHandler(self.handler) def setupUi(self): self.load_ui("apol/netifconquery.ui") # set up user autocompletion user_completion_list = [str(u) for u in self.policy.users()] user_completer_model = QStringListModel(self) user_completer_model.setStringList(sorted(user_completion_list)) self.user_completion = QCompleter() self.user_completion.setModel(user_completer_model) self.user.setCompleter(self.user_completion) # set up role autocompletion role_completion_list = [str(r) for r in self.policy.roles()] role_completer_model = QStringListModel(self) role_completer_model.setStringList(sorted(role_completion_list)) self.role_completion = QCompleter() self.role_completion.setModel(role_completer_model) self.role.setCompleter(self.role_completion) # set up type autocompletion type_completion_list = [str(t) for t in self.policy.types()] type_completer_model = QStringListModel(self) type_completer_model.setStringList(sorted(type_completion_list)) self.type_completion = QCompleter() self.type_completion.setModel(type_completer_model) self.type_.setCompleter(self.type_completion) # setup indications of errors on source/target/default self.errors = set() self.orig_palette = self.type_.palette() self.error_palette = self.type_.palette() self.error_palette.setColor(QPalette.Base, Qt.red) self.clear_name_error() self.clear_user_error() self.clear_type_error() self.clear_role_error() self.clear_range_error() # set up results self.table_results_model = NetifconTableModel(self) self.sort_proxy = QSortFilterProxyModel(self) self.sort_proxy.setSourceModel(self.table_results_model) self.table_results.setModel(self.sort_proxy) self.table_results.sortByColumn(0, Qt.AscendingOrder) # set up processing thread self.thread = QThread() self.worker = QueryResultsUpdater(self.query, self.table_results_model) self.worker.moveToThread(self.thread) self.worker.raw_line.connect(self.raw_results.appendPlainText) self.worker.finished.connect(self.update_complete) self.worker.finished.connect(self.thread.quit) self.thread.started.connect(self.worker.update) # create a "busy, please wait" dialog self.busy = QProgressDialog(self) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) self.busy.canceled.connect(self.thread.requestInterruption) self.busy.reset() # update busy dialog from query INFO logs self.handler = LogHandlerToSignal() self.handler.message.connect(self.busy.setLabelText) logging.getLogger("setools.netifconquery").addHandler(self.handler) # Ensure settings are consistent with the initial .ui state self.set_name_regex(self.name_regex.isChecked()) self.criteria_frame.setHidden(not self.criteria_expander.isChecked()) self.notes.setHidden(not self.notes_expander.isChecked()) # Range criteria is available only if policy is MLS if not self.policy.mls: self.range_criteria.setEnabled(False) self.range_criteria.setToolTip("MLS is disabled in this policy.") self.range_.setToolTip("MLS is disabled in this policy.") self.range_exact.setToolTip("MLS is disabled in this policy.") self.range_overlap.setToolTip("MLS is disabled in this policy.") self.range_subset.setToolTip("MLS is disabled in this policy.") self.range_superset.setToolTip("MLS is disabled in this policy.") # connect signals self.buttonBox.clicked.connect(self.run) self.name.textEdited.connect(self.clear_name_error) self.name.editingFinished.connect(self.set_name) self.name_regex.toggled.connect(self.set_name_regex) self.user.textEdited.connect(self.clear_user_error) self.user.editingFinished.connect(self.set_user) self.user_regex.toggled.connect(self.set_user_regex) self.role.textEdited.connect(self.clear_role_error) self.role.editingFinished.connect(self.set_role) self.role_regex.toggled.connect(self.set_role_regex) self.type_.textEdited.connect(self.clear_type_error) self.type_.editingFinished.connect(self.set_type) self.type_regex.toggled.connect(self.set_type_regex) self.range_.textEdited.connect(self.clear_range_error) self.range_.editingFinished.connect(self.set_range) # # Name criteria # def clear_name_error(self): self.clear_criteria_error(self.name, "Match the device name.") def set_name(self): try: self.query.name = self.name.text() except Exception as ex: self.log.error("Device name error: {0}".format(ex)) self.set_criteria_error(self.name, ex) def set_name_regex(self, state): self.log.debug("Setting name_regex {0}".format(state)) self.query.name_regex = state self.clear_name_error() self.set_name() # # User criteria # def clear_user_error(self): self.clear_criteria_error(self.user, "Match the user of the context.") def set_user(self): try: self.query.user = self.user.text() except Exception as ex: self.log.error("Context user error: {0}".format(ex)) self.set_criteria_error(self.user, ex) def set_user_regex(self, state): self.log.debug("Setting user_regex {0}".format(state)) self.query.user_regex = state self.clear_user_error() self.set_user() # # Role criteria # def clear_role_error(self): self.clear_criteria_error(self.role, "Match the role of the context.") def set_role(self): try: self.query.role = self.role.text() except Exception as ex: self.log.error("Context role error: {0}".format(ex)) self.set_criteria_error(self.role, ex) def set_role_regex(self, state): self.log.debug("Setting role_regex {0}".format(state)) self.query.role_regex = state self.clear_role_error() self.set_role() # # Type criteria # def clear_type_error(self): self.clear_criteria_error(self.type_, "Match the type of the context.") def set_type(self): try: self.query.type_ = self.type_.text() except Exception as ex: self.log.error("Context type error: {0}".format(ex)) self.set_criteria_error(self.type_, ex) def set_type_regex(self, state): self.log.debug("Setting type_regex {0}".format(state)) self.query.type_regex = state self.clear_type_error() self.set_type() # # Range criteria # def clear_range_error(self): self.clear_criteria_error(self.range_, "Match the range of the context.") def set_range(self): try: self.query.range_ = self.range_.text() except Exception as ex: self.log.info("Context range error: " + str(ex)) self.set_criteria_error(self.range_, ex) # # Save/Load tab # def save(self): """Return a dictionary of settings.""" if self.errors: raise TabFieldError("Field(s) are in error: {0}".format(" ".join( o.objectName() for o in self.errors))) settings = {} save_checkboxes(self, settings, [ "criteria_expander", "notes_expander", "name_regex", "user_regex", "role_regex", "type_regex", "range_exact", "range_overlap", "range_subset", "range_superset" ]) save_lineedits(self, settings, ["name", "user", "role", "type_", "range_"]) save_textedits(self, settings, ["notes"]) return settings def load(self, settings): load_checkboxes(self, settings, [ "criteria_expander", "notes_expander", "name_regex", "user_regex", "role_regex", "type_regex", "range_exact", "range_overlap", "range_subset", "range_superset" ]) load_lineedits(self, settings, ["name", "user", "role", "type_", "range_"]) load_textedits(self, settings, ["notes"]) # # Results runner # def run(self, button): # right now there is only one button. self.query.range_overlap = self.range_overlap.isChecked() self.query.range_subset = self.range_subset.isChecked() self.query.range_superset = self.range_superset.isChecked() # start processing self.busy.setLabelText("Processing query...") self.busy.show() self.raw_results.clear() self.thread.start() def update_complete(self, count): self.log.info("{0} netifcon statement(s) found.".format(count)) # update sizes/location of result displays if not self.busy.wasCanceled(): self.busy.setLabelText( "Resizing the result table's columns; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeColumnsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText( "Resizing the result table's rows; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeRowsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText( "Moving the raw result to top; GUI may be unresponsive") self.busy.repaint() self.raw_results.moveCursor(QTextCursor.Start) self.busy.reset()
class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent=parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setWindowTitle("Youtube downloader") self.checked_all = False # declares which state should be setted by check_all-button # thread self.thread = None self.downloader = None # videos self.videos = [] # slots self.ui.addButton.clicked.connect(self.search) self.ui.downloadButton.clicked.connect(self.download) self.ui.checkAllButton.clicked.connect(self.check_all) self.ui.cleanButton.clicked.connect(self.clean_list) # ui self.ui.listWidget.setSelectionMode( QAbstractItemView.SelectionMode.NoSelection) self.ui.mp3CheckBox.setChecked(True) self.ui.labelInformation.setVisible(False) self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.mp3_options = { 'format': 'bestaudio', # 'bestaudio/best', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '320', # 192', }], 'logger': MainWindow.MyLogger(), 'progress_hooks': [self.my_hook], } # TODO don't work video downloading # self.video_options = { # 'format': 'bestvideo+bestaudio', # 'noplaylist': True, # # 'ext': '3gp', # 'bestaudio/best', # 'logger': MainWindow.MyLogger(), # 'progress_hooks': [self.my_hook], # } # TODO test # self.ui.searchLineEdit.setText( # "https://www.youtube.com/watch?v=8GW6sLrK40k&list=PLit8GzUQ7d7F0Lf2WNNL754TYQHb23b8t&t=0s&index=2") self.ui.searchLineEdit.setText( "https://www.youtube.com/watch?v=kfchvCyHmsc") # TODO test self.search() class MyLogger: # TODO def debug(self, msg): #if msg.startswith("[download]"): print("XD") def warning(self, msg): print("XDD12" + msg) def error(self, msg): print("XDD123" + msg) def my_hook(self, d): if d['status'] == 'finished': print('Done downloading, now converting ...') @pyqtSlot() def search(self): self.ui.labelInformation.setVisible(False) if self.ui.searchLineEdit.text(): try: # TODO so bad it is very slow, but this is a youtube-dl problem with yt.YoutubeDL(self.get_options()) as ydl: result = ydl.extract_info( self.ui.searchLineEdit.text(), download=False # We just want to extract the info ) # TODO test pp = pprint.PrettyPrinter(indent=4) pp.pprint(result) self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.update_list(result) except yt.DownloadError: self.print_error( str(yt.DownloadError) ) # ("Download is not possible.\nCheck you internet connection or link.") @pyqtSlot() def download(self): self.ui.labelInformation.setVisible(False) if self.videos: try: # TODO test # path = QtWidgets.QFileDialog.getExistingDirectory( # self, # "Otwórz", # expanduser("~"), # QtWidgets.QFileDialog.ShowDirsOnly # ) # TODO test path = '/home/marek/Desktop' if path and self.videos: self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.thread = QThread() self.downloader = Downloader(path, self.videos, self.get_options()) self.downloader.moveToThread(self.thread) self.thread.started.connect(self.downloader.download) self.download_is_running(True) self.downloader.finished.connect( lambda: self.download_is_running(False)) self.downloader.error_occured.connect(self.print_error) self.thread.start() except yt.DownloadError: self.print_error( "Download is not possible.\nCheck you internet connection or link." ) def update_list(self, videos): if 'entries' in videos: for video in videos['entries']: if video['title'] not in (v.original_title for v in self.videos): record = Video(video['url'], video['title'], video['duration'], video['thumbnail']) record.is_checked = True record.filesize = self.get_filesize( video['formats'], 'best') # TODO now its only best quality self.videos.append(record) else: if videos['title'] not in (v.original_title for v in self.videos): record = Video(videos['url'], videos['title'], videos['duration'], videos['thumbnail']) record.is_checked = True record.filesize = self.get_filesize( videos['formats'], 'best') # TODO now its only best quality self.videos.append(record) self.show_list() @pyqtSlot() def check_all(self): self.ui.labelInformation.setVisible(False) if self.videos: for v in self.videos: v.is_checked = not self.checked_all self.checked_all = not self.checked_all text = 'Odznacz' if self.checked_all else 'Zaznacz' self.ui.checkAllButton.setText(text) def show_list(self): self.ui.listWidget.clear() for video in self.videos: # create an item item = QtWidgets.QListWidgetItem(self.ui.listWidget) # create a custom widget item_widget = ListItem() # set thumbnail url = video.thumbnail data = urllib.request.urlopen(url).read() image = QtGui.QImage() image.loadFromData(data) image = image.scaled(100, 100, QtCore.Qt.KeepAspectRatio) item_widget.setPixmap(QtGui.QPixmap(image)) item_widget.setTitle(video.title) item_widget.setDuration(video.duration) item_widget.setChecked(video.is_checked) # self.checkboxes.append(Item_Widget.getCheckBox()) # set the size from the item to the same of the widget item.setSizeHint(item_widget.sizeHint()) # Item.setFlags(Item.flags() | QtCore.Qt.ItemIsSelectable) # TODO not working # Item. # I add it to the list self.ui.listWidget.addItem(item) # self.items.append(Item) video.checkbox = item_widget.getCheckBox() video.line_edit = item_widget.getLineEdit() self.ui.listWidget.setItemWidget(item, item_widget) @pyqtSlot() def clean_list(self): self.videos = [] self.checked_all = False self.ui.labelInformation.setVisible(False) self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.show_list() def download_is_running(self, is_running): self.ui.downloadButton.setEnabled(not is_running) self.ui.progressBarSingle.setVisible(is_running) self.ui.progressBarMultiple.setVisible(is_running) if self.thread: self.thread.exit() def get_options(self): if self.ui.mp3CheckBox.isChecked(): return self.mp3_options else: return self.video_options @pyqtSlot(str) def print_error(self, msg): self.ui.labelInformation.setVisible(True) self.ui.labelInformation.setText(msg) def get_filesize(self, formats, choosen_format): # TODO it's fake in case of MP3 files. Here is only video filesize if choosen_format == 'best': filesizes = [frm['filesize'] for frm in formats] print('----------------' + str(max(filesizes))) return max(filesizes) else: # TODO there is a problem in downloading videos at all raise NotImplementedError
def start(self, entity, kwargs): self.entity = entity self.kwargs = kwargs QThread.start(self)
class MLSRuleQueryTab(AnalysisTab): """An MLS rule query.""" def __init__(self, parent, policy, perm_map): super(MLSRuleQueryTab, self).__init__(parent) self.log = logging.getLogger(__name__) self.policy = policy self.query = MLSRuleQuery(policy) self.setupUi() def __del__(self): self.thread.quit() self.thread.wait(5000) logging.getLogger("setools.mlsrulequery").removeHandler(self.handler) def setupUi(self): self.load_ui("apol/mlsrulequery.ui") # set up source/target autocompletion typeattr_completion_list = [str(t) for t in self.policy.types()] typeattr_completion_list.extend(str(a) for a in self.policy.typeattributes()) typeattr_completer_model = QStringListModel(self) typeattr_completer_model.setStringList(sorted(typeattr_completion_list)) self.typeattr_completion = QCompleter() self.typeattr_completion.setModel(typeattr_completer_model) self.source.setCompleter(self.typeattr_completion) self.target.setCompleter(self.typeattr_completion) # setup indications of errors on source/target/default self.errors = set() self.orig_palette = self.source.palette() self.error_palette = self.source.palette() self.error_palette.setColor(QPalette.Base, Qt.red) self.clear_source_error() self.clear_target_error() self.clear_default_error() # populate class list self.class_model = SEToolsListModel(self) self.class_model.item_list = sorted(self.policy.classes()) self.tclass.setModel(self.class_model) # set up results self.table_results_model = MLSRuleTableModel(self) self.sort_proxy = QSortFilterProxyModel(self) self.sort_proxy.setSourceModel(self.table_results_model) self.table_results.setModel(self.sort_proxy) self.table_results.sortByColumn(1, Qt.AscendingOrder) # set up processing thread self.thread = QThread() self.worker = QueryResultsUpdater(self.query, self.table_results_model) self.worker.moveToThread(self.thread) self.worker.raw_line.connect(self.raw_results.appendPlainText) self.worker.finished.connect(self.update_complete) self.worker.finished.connect(self.thread.quit) self.thread.started.connect(self.worker.update) # create a "busy, please wait" dialog self.busy = QProgressDialog(self) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) self.busy.canceled.connect(self.thread.requestInterruption) self.busy.reset() # update busy dialog from query INFO logs self.handler = LogHandlerToSignal() self.handler.message.connect(self.busy.setLabelText) logging.getLogger("setools.mlsrulequery").addHandler(self.handler) # Ensure settings are consistent with the initial .ui state self.set_source_regex(self.source_regex.isChecked()) self.set_target_regex(self.target_regex.isChecked()) self.criteria_frame.setHidden(not self.criteria_expander.isChecked()) self.notes.setHidden(not self.notes_expander.isChecked()) # connect signals self.buttonBox.clicked.connect(self.run) self.clear_ruletypes.clicked.connect(self.clear_all_ruletypes) self.all_ruletypes.clicked.connect(self.set_all_ruletypes) self.source.textEdited.connect(self.clear_source_error) self.source.editingFinished.connect(self.set_source) self.source_regex.toggled.connect(self.set_source_regex) self.target.textEdited.connect(self.clear_target_error) self.target.editingFinished.connect(self.set_target) self.target_regex.toggled.connect(self.set_target_regex) self.tclass.selectionModel().selectionChanged.connect(self.set_tclass) self.invert_class.clicked.connect(self.invert_tclass_selection) self.default_range.textEdited.connect(self.clear_default_error) self.default_range.editingFinished.connect(self.set_default_range) # # Ruletype criteria # def _set_ruletypes(self, value): self.range_transition.setChecked(value) def set_all_ruletypes(self): self._set_ruletypes(True) def clear_all_ruletypes(self): self._set_ruletypes(False) # # Source criteria # def clear_source_error(self): self.clear_criteria_error(self.source, "Match the source type/attribute of the rule.") def set_source(self): try: self.query.source = self.source.text() except Exception as ex: self.log.error("Source type/attribute error: {0}".format(ex)) self.set_criteria_error(self.source, ex) def set_source_regex(self, state): self.log.debug("Setting source_regex {0}".format(state)) self.query.source_regex = state self.clear_source_error() self.set_source() # # Target criteria # def clear_target_error(self): self.clear_criteria_error(self.target, "Match the target type/attribute of the rule.") def set_target(self): try: self.query.target = self.target.text() except Exception as ex: self.log.error("Target type/attribute error: {0}".format(ex)) self.set_criteria_error(self.target, ex) def set_target_regex(self, state): self.log.debug("Setting target_regex {0}".format(state)) self.query.target_regex = state self.clear_target_error() self.set_target() # # Class criteria # def set_tclass(self): selected_classes = [] for index in self.tclass.selectionModel().selectedIndexes(): selected_classes.append(self.class_model.data(index, Qt.UserRole)) self.query.tclass = selected_classes def invert_tclass_selection(self): invert_list_selection(self.tclass.selectionModel()) # # Default criteria # def clear_default_error(self): self.clear_criteria_error(self.default_range, "Match the default type the rule.") def set_default_range(self): try: self.query.default = self.default_range.text() except Exception as ex: self.log.error("Default range error: {0}".format(ex)) self.set_criteria_error(self.default_range, ex) # # Save/Load tab # def save(self): """Return a dictionary of settings.""" if self.errors: raise TabFieldError("Field(s) are in error: {0}". format(" ".join(o.objectName() for o in self.errors))) settings = {} save_checkboxes(self, settings, ["criteria_expander", "notes_expander", "range_transition", "source_indirect", "source_regex", "target_indirect", "target_regex"]) save_lineedits(self, settings, ["source", "target", "default_range"]) save_listviews(self, settings, ["tclass"]) save_textedits(self, settings, ["notes"]) return settings def load(self, settings): load_checkboxes(self, settings, ["criteria_expander", "notes_expander", "range_transition", "source_indirect", "source_regex", "target_indirect", "target_regex"]) load_lineedits(self, settings, ["source", "target", "default_range"]) load_listviews(self, settings, ["tclass"]) load_textedits(self, settings, ["notes"]) # # Results runner # def run(self, button): # right now there is only one button. self.query.ruletype = ['range_transition'] self.query.source_indirect = self.source_indirect.isChecked() self.query.target_indirect = self.target_indirect.isChecked() # start processing self.busy.setLabelText("Processing query...") self.busy.show() self.raw_results.clear() self.thread.start() def update_complete(self, count): self.log.info("{0} MLS rule(s) found.".format(count)) # update sizes/location of result displays if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's columns; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeColumnsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's rows; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeRowsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText("Moving the raw result to top; GUI may be unresponsive") self.busy.repaint() self.raw_results.moveCursor(QTextCursor.Start) self.busy.reset()
class InitialSIDQueryTab(SEToolsWidget, QScrollArea): """An initial SID query.""" def __init__(self, parent, policy, perm_map): super(InitialSIDQueryTab, self).__init__(parent) self.log = logging.getLogger(__name__) self.policy = policy self.query = InitialSIDQuery(policy) self.setupUi() def __del__(self): self.thread.quit() self.thread.wait(5000) logging.getLogger("setools.initsidquery").removeHandler(self.handler) def setupUi(self): self.load_ui("initsidquery.ui") # set up user autocompletion user_completion_list = [str(u) for u in self.policy.users()] user_completer_model = QStringListModel(self) user_completer_model.setStringList(sorted(user_completion_list)) self.user_completion = QCompleter() self.user_completion.setModel(user_completer_model) self.user.setCompleter(self.user_completion) # set up role autocompletion role_completion_list = [str(r) for r in self.policy.roles()] role_completer_model = QStringListModel(self) role_completer_model.setStringList(sorted(role_completion_list)) self.role_completion = QCompleter() self.role_completion.setModel(role_completer_model) self.role.setCompleter(self.role_completion) # set up type autocompletion type_completion_list = [str(t) for t in self.policy.types()] type_completer_model = QStringListModel(self) type_completer_model.setStringList(sorted(type_completion_list)) self.type_completion = QCompleter() self.type_completion.setModel(type_completer_model) self.type_.setCompleter(self.type_completion) # setup indications of errors on source/target/default self.orig_palette = self.type_.palette() self.error_palette = self.type_.palette() self.error_palette.setColor(QPalette.Base, Qt.red) self.clear_name_error() self.clear_user_error() self.clear_type_error() self.clear_role_error() self.clear_range_error() # set up results self.table_results_model = InitialSIDTableModel(self) self.sort_proxy = QSortFilterProxyModel(self) self.sort_proxy.setSourceModel(self.table_results_model) self.table_results.setModel(self.sort_proxy) self.table_results.sortByColumn(0, Qt.AscendingOrder) # set up processing thread self.thread = QThread() self.worker = QueryResultsUpdater(self.query, self.table_results_model) self.worker.moveToThread(self.thread) self.worker.raw_line.connect(self.raw_results.appendPlainText) self.worker.finished.connect(self.update_complete) self.worker.finished.connect(self.thread.quit) self.thread.started.connect(self.worker.update) # create a "busy, please wait" dialog self.busy = QProgressDialog(self) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) self.busy.canceled.connect(self.thread.requestInterruption) self.busy.reset() # update busy dialog from query INFO logs self.handler = LogHandlerToSignal() self.handler.message.connect(self.busy.setLabelText) logging.getLogger("setools.initsidquery").addHandler(self.handler) # Ensure settings are consistent with the initial .ui state self.set_name_regex(self.name_regex.isChecked()) self.criteria_frame.setHidden(not self.criteria_expander.isChecked()) self.notes.setHidden(not self.notes_expander.isChecked()) # Range criteria is available only if policy is MLS if not self.policy.mls: self.range_criteria.setEnabled(False) self.range_criteria.setToolTip("MLS is disabled in this policy.") self.range_.setToolTip("MLS is disabled in this policy.") self.range_exact.setToolTip("MLS is disabled in this policy.") self.range_overlap.setToolTip("MLS is disabled in this policy.") self.range_subset.setToolTip("MLS is disabled in this policy.") self.range_superset.setToolTip("MLS is disabled in this policy.") # connect signals self.buttonBox.clicked.connect(self.run) self.name.textEdited.connect(self.clear_name_error) self.name.editingFinished.connect(self.set_name) self.name_regex.toggled.connect(self.set_name_regex) self.user.textEdited.connect(self.clear_user_error) self.user.editingFinished.connect(self.set_user) self.user_regex.toggled.connect(self.set_user_regex) self.role.textEdited.connect(self.clear_role_error) self.role.editingFinished.connect(self.set_role) self.role_regex.toggled.connect(self.set_role_regex) self.type_.textEdited.connect(self.clear_type_error) self.type_.editingFinished.connect(self.set_type) self.type_regex.toggled.connect(self.set_type_regex) self.range_.textEdited.connect(self.clear_range_error) self.range_.editingFinished.connect(self.set_range) # # Name criteria # def clear_name_error(self): self.name.setToolTip("Match the name.") self.name.setPalette(self.orig_palette) def set_name(self): try: self.query.name = self.name.text() except Exception as ex: self.log.error("Name error: {0}".format(ex)) self.name.setToolTip("Error: " + str(ex)) self.name.setPalette(self.error_palette) def set_name_regex(self, state): self.log.debug("Setting name_regex {0}".format(state)) self.query.name_regex = state self.clear_name_error() self.set_name() # # User criteria # def clear_user_error(self): self.user.setToolTip("Match the user of the context.") self.user.setPalette(self.orig_palette) def set_user(self): try: self.query.user = self.user.text() except Exception as ex: self.log.error("Context user error: {0}".format(ex)) self.user.setToolTip("Error: " + str(ex)) self.user.setPalette(self.error_palette) def set_user_regex(self, state): self.log.debug("Setting user_regex {0}".format(state)) self.query.user_regex = state self.clear_user_error() self.set_user() # # Role criteria # def clear_role_error(self): self.role.setToolTip("Match the role of the context.") self.role.setPalette(self.orig_palette) def set_role(self): try: self.query.role = self.role.text() except Exception as ex: self.log.error("Context role error: {0}".format(ex)) self.role.setToolTip("Error: " + str(ex)) self.role.setPalette(self.error_palette) def set_role_regex(self, state): self.log.debug("Setting role_regex {0}".format(state)) self.query.role_regex = state self.clear_role_error() self.set_role() # # Type criteria # def clear_type_error(self): self.type_.setToolTip("Match the type of the context.") self.type_.setPalette(self.orig_palette) def set_type(self): try: self.query.type_ = self.type_.text() except Exception as ex: self.log.error("Context type error: {0}".format(ex)) self.type_.setToolTip("Error: " + str(ex)) self.type_.setPalette(self.error_palette) def set_type_regex(self, state): self.log.debug("Setting type_regex {0}".format(state)) self.query.type_regex = state self.clear_type_error() self.set_type() # # Range criteria # def clear_range_error(self): self.range_.setToolTip("Match the range of the context.") self.range_.setPalette(self.orig_palette) def set_range(self): try: self.query.range_ = self.range_.text() except Exception as ex: self.log.info("Context range error: " + str(ex)) self.range_.setToolTip("Error: " + str(ex)) self.range_.setPalette(self.error_palette) # # Results runner # def run(self, button): # right now there is only one button. self.query.range_overlap = self.range_overlap.isChecked() self.query.range_subset = self.range_subset.isChecked() self.query.range_superset = self.range_superset.isChecked() # start processing self.busy.setLabelText("Processing query...") self.busy.show() self.raw_results.clear() self.thread.start() def update_complete(self, count): self.log.info("{0} initial SID statment(s) found.".format(count)) # update sizes/location of result displays if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's columns; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeColumnsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's rows; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeRowsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText("Moving the raw result to top; GUI may be unresponsive") self.busy.repaint() self.raw_results.moveCursor(QTextCursor.Start) self.busy.reset()
class NetworkHandler: def __init__(self, addr, port): self.addr = (addr, port) self.s = connector_client(addr, port) if self.s == "offline": logger.log("error", "No route to server") logger.log("info", "Starting internal server") self.offline = True self.server_thread = threading.Thread(target=self.server_listner) self.server_thread.start() time.sleep(0.8) self.s = connector_client(addr, port) else: self.offline = False self.netQueue = [] #self.listening_thread = threading.Thread(target=self.listening) #self.listening_thread.start() self.listening_thread = QThread() self.worker = AsyncThreadThing(self) self.worker.moveToThread(self.listening_thread) self.listening_thread.started.connect(self.worker.run) self.worker.update.connect(lambda n: self.textbox.setPlainText(n)) self.listening_thread.start() def recv_data(self): while len(self.netQueue) == 0: pass return self.netQueue.pop() def server_listner(self): s = connecter_server(self.addr[0], self.addr[1]) conn, addr = s.accept() self.con = Connection(conn) self.con.run() def send_data(self, data): #time.sleep(0.5) string = json.dumps(data) string = struct.pack('>I', len( string.encode("utf-8"))) + string.encode("utf-8") self.s.sendall(string) def recv_data_raw(self): raw_msglen = self.recvall(4) if not raw_msglen: return None msglen = struct.unpack('>I', raw_msglen)[0] s = self.recvall(msglen).decode("utf-8") return json.loads(s) def recvall(self, n): data = b'' while len(data) < n: packet = self.s.recv(n - len(data)) if not packet: return None data += packet return data def sendUpdate(self, text): self.send_data({"request": "update", "data": text}) def close(self): self.s.close() if self.offline: self.con.exit_()
class MDTGUISingleModel(QMainWindow, Ui_MainWindow): def __init__(self, shared_state, computations_thread): super().__init__() self.setupUi(self) self._shared_state = shared_state self._computations_thread = computations_thread self._computations_thread.signal_starting.connect( self.computations_started) self._computations_thread.signal_finished.connect( self.computations_finished) self._stdout_old = sys.stdout self._stderr_old = sys.stderr self._logging_update_queue = Queue() self._logging_update_thread = QThread() self._message_receiver = MessageReceiver(self._logging_update_queue) self._message_receiver.text_message_signal.connect(self.update_log) self._message_receiver.moveToThread(self._logging_update_thread) self._logging_update_thread.started.connect(self._message_receiver.run) self._logging_update_thread.start() sys.stdout = ForwardingListener(self._logging_update_queue) sys.stderr = ForwardingListener(self._logging_update_queue) LogDispatchHandler.add_listener( ForwardingListener(self._logging_update_queue)) print_welcome_message() self.actionExit.setShortcuts(['Ctrl+q', 'Ctrl+w']) self.action_RuntimeSettings.triggered.connect( lambda: RuntimeSettingsDialog(self).exec_()) self.action_MapsVisualizer.triggered.connect( lambda: mdt.gui.maps_visualizer.main.start_gui(app_exec=False)) self.actionAbout.triggered.connect(lambda: AboutDialog(self).exec_()) self.action_GetExampleData.triggered.connect( lambda: GetExampleDataDialog(self, shared_state).exec_()) self.executionStatusLabel.setText('Idle') self.executionStatusIcon.setPixmap( QtGui.QPixmap(":/main_gui/icon_status_red.png")) self.fit_model_tab = FitModelTab(shared_state, self._computations_thread) self.fit_model_tab.setupUi(self.fitModelTab) self.generate_mask_tab = GenerateBrainMaskTab( shared_state, self._computations_thread) self.generate_mask_tab.setupUi(self.generateBrainMaskTab) self.generate_roi_mask_tab = GenerateROIMaskTab( shared_state, self._computations_thread) self.generate_roi_mask_tab.setupUi(self.generateROIMaskTab) self.generate_protocol_tab = GenerateProtocolTab( shared_state, self._computations_thread) self.generate_protocol_tab.setupUi(self.generateProtocolTab) self.tabs = [ self.fit_model_tab, self.generate_mask_tab, self.generate_roi_mask_tab, self.generate_protocol_tab ] self.MainTabs.currentChanged.connect( lambda index: self.tabs[index].tab_opened()) def closeEvent(self, event): sys.stdout = self._stdout_old sys.stderr = self._stderr_old self._message_receiver.is_running = False self._logging_update_thread.quit() self._logging_update_thread.wait(10) super().closeEvent(event) def send_sigint(self, *args): self.close() @pyqtSlot() def computations_started(self): self.executionStatusLabel.setText('Computing') self.executionStatusIcon.setPixmap( QtGui.QPixmap(":/main_gui/icon_status_green.png")) @pyqtSlot() def computations_finished(self): self.executionStatusLabel.setText('Idle') self.executionStatusIcon.setPixmap( QtGui.QPixmap(":/main_gui/icon_status_red.png")) @pyqtSlot(str) def update_log(self, string): sb = self.loggingTextBox.verticalScrollBar() scrollbar_position = sb.value() autoscroll = scrollbar_position == sb.maximum() self.loggingTextBox.moveCursor(QtGui.QTextCursor.End) self.loggingTextBox.insertPlainText(string) if autoscroll: sb.setValue(sb.maximum()) else: sb.setValue(scrollbar_position)
class ClusteringView(QObject): """ A viewer for viewing clustering result of OPTICS-APT, showing ion maps, RD, RD histogram. """ def __init__(self, Clusterer, show_bg, point_size=3): super().__init__() self.clustering_view = QWidget() self.layout = QGridLayout() self.clustering_view.setLayout(self.layout) self.RD_plot = pg.PlotWidget() self.RD_hist = pg.PlotWidget() self.point_cloud_view = gl.GLViewWidget() self.RD_plot.sizeHint = pg.QtCore.QSize(800, 600) self.point_cloud_view.sizeHint = lambda: pg.QtCore.QSize(800, 600) self.point_cloud_view.setSizePolicy(self.RD_plot.sizePolicy()) self.layout.addWidget(self.point_cloud_view, 0, 0, 2, 1) self.layout.addWidget(self.RD_plot, 0, 1) self.layout.addWidget(self.RD_hist, 1, 1) self.point_cloud_view.setBackgroundColor('w') self.point_cloud_view.opts['distance'] = 2000 self.point_cloud_view.opts['fov'] = 1 self.thread_show_clustering = QThread() self.ShowClustering = ShowClustering(Clusterer, point_size, show_bg) self.ShowClustering.gl_finished.connect( self.thread_show_clustering.quit) self.ShowClustering.gl_ready.connect(self.point_cloud_ready) self.ShowClustering.moveToThread(self.thread_show_clustering) self.thread_show_clustering.started.connect( self.ShowClustering.show_clustering) self.thread_show_clustering.start() # start plot RD ordered_RD = Clusterer.RD[Clusterer.ordered_lst] self.RD_plot.setBackground('w') self.RD_plot.setDownsampling(auto=True) self.RD_plot.plot(ordered_RD, pen=pg.mkPen('k', width=2)) color_table = ColorTable.gen_color_table(style='int') color_table.pop(0) cycol = cycle(color_table) # num_leaves = len(leaves) leaves = Clusterer.obtain_leaf_nodes() count = 0 for node in leaves: if node.is_cluster: item = node.index_range ave_RD = node.average_RD(ordered_RD) self.RD_plot.plot(item, [ave_RD, ave_RD], pen=pg.mkPen(color=next(cycol), width=2)) count += 1 # start plot RD hist self.RD_hist.setBackground('w') hist, bins = np.histogram(Clusterer.RD, bins=50, density=True) curve = pg.PlotCurveItem(bins, hist, pen=pg.mkPen('b', width=2), stepMode=True) self.RD_hist.addItem(curve) # self.thread_show_RD = QThread() # self.thread_show_RD_hist = QThread() self.clustering_view.show() @pyqtSlot(float, name='processing_time') def processing_time(self, t): print('processing time is', t, 's') @pyqtSlot(object, name='point_cloud_ready') def point_cloud_ready(self, sp1): self.point_cloud_view.addItem(sp1) @pyqtSlot(object, name='RD_ready') def RD_ready(self, item): # self.point_cloud_view.addItem(sp1) pass @pyqtSlot(object, name='RD_hist_ready') def RD_hist_ready(self, item): # self.point_cloud_view.addItem(sp1) pass
class MainWindow(QMainWindow): """ Defines the mainWindow class for the neurodecode GUI. """ hide_recordTerminal = pyqtSignal(bool) signal_error = pyqtSignal(str) #---------------------------------------------------------------------- def __init__(self): """ Constructor. """ super(MainWindow, self).__init__() self.cfg_struct = None # loaded module containing all param possible values self.cfg_subject = None # loaded module containing subject specific values self.paramsWidgets = {} # dict of all the created parameters widgets self.load_ui_from_file() self.redirect_stdout() self.connect_signals_to_slots() # Define in which modality we are self.modality = None # Recording process self.record_terminal = None self.recordLogger = logging.getLogger('recorder') self.recordLogger.propagate = False init_logger(self.recordLogger) # To display errors self.error_dialog = QErrorMessage(self) self.error_dialog.setWindowTitle('Warning') # Mp sharing variables self.record_state = mp.Value('i', 0) self.protocol_state = mp.Value('i', 0) self.lsl_state = mp.Value('i', 0) self.viewer_state = mp.Value('i', 0) # Disable widgets self.ui.groupBox_Modality.setEnabled(False) self.ui.groupBox_Launch.setEnabled(False) # ---------------------------------------------------------------------- def redirect_stdout(self): """ Create Queue and redirect sys.stdout to this queue. Create thread that will listen on the other end of the queue, and send the text to the textedit_terminal. """ queue = mp.Queue() self.thread = QThread() self.my_receiver = MyReceiver(queue) self.my_receiver.mysignal.connect(self.on_terminal_append) self.my_receiver.moveToThread(self.thread) self.thread.started.connect(self.my_receiver.run) self.thread.start() redirect_stdout_to_queue(logger, self.my_receiver.queue, 'INFO') #---------------------------------------------------------------------- def load_ui_from_file(self): """ Loads the UI interface from file. """ self.ui = Ui_MainWindow() self.ui.setupUi(self) # Protocol terminal self.ui.textEdit_terminal.setReadOnly(1) font = QFont() font.setPointSize(10) self.ui.textEdit_terminal.setFont(font) # Viewer button self.ui.pushButton_Viewer.setEnabled(False) #---------------------------------------------------------------------- def clear_params(self): """ Clear all previously loaded params widgets. """ if self.ui.scrollAreaWidgetContents_Basics.layout() != None: QWidget().setLayout(self.ui.scrollAreaWidgetContents_Adv.layout()) QWidget().setLayout( self.ui.scrollAreaWidgetContents_Basics.layout()) # ---------------------------------------------------------------------- def extract_value_from_module(self, key, values): """ Extracts the subject's specific value associated with key. key = parameter name. values = list of all the parameters values. """ for v in values: if v[0] == key: return v[1] # ---------------------------------------------------------------------- def disp_params(self, cfg_template_module, cfg_module): """ Displays the parameters in the corresponding UI scrollArea. cfg = config module """ self.clear_params() # Extract the parameters and their possible values from the template modules. params = inspect.getmembers(cfg_template_module) # Extract the chosen values from the subject's specific module. all_chosen_values = inspect.getmembers(cfg_module) filePath = self.ui.lineEdit_pathSearch.text() # Load channels if self.modality == 'trainer': subjectDataPath = Path( '%s/%s/%s/fif' % (os.environ['NEUROD_DATA'], filePath.split('/')[-2], filePath.split('/')[-1])) self.channels = read_params_from_file(subjectDataPath, 'channelsList.txt') self.directions = () # Iterates over the classes for par in range(2): param = inspect.getmembers(params[par][1]) # Create layouts layout = QFormLayout() # Iterates over the list for p in param: # Remove useless attributes if '__' in p[0]: continue # Iterates over the dict for key, values in p[1].items(): chosen_value = self.extract_value_from_module( key, all_chosen_values) # For the feedback directions [offline and online]. if 'DIRECTIONS' in key: self.directions = values if self.modality is 'offline': nb_directions = 4 directions = Connect_Directions( key, chosen_value, values, nb_directions) elif self.modality is 'online': chosen_events = [ event[1] for event in chosen_value ] chosen_value = [val[0] for val in chosen_value] nb_directions = len(chosen_value) directions = Connect_Directions_Online( key, chosen_value, values, nb_directions, chosen_events, [None]) directions.signal_paramChanged[str, list].connect( self.on_guichanges) self.paramsWidgets.update({key: directions}) layout.addRow(key, directions.l) # For the special case of choosing the trigger classes to train on elif 'TRIGGER_DEF' in key: trigger_def = Connect_Directions( key, chosen_value, [None], 4) trigger_def.signal_paramChanged[str, list].connect( self.on_guichanges) self.paramsWidgets.update({key: trigger_def}) layout.addRow(key, trigger_def.l) # For providing a folder path. elif 'PATH' in key: pathfolderfinder = PathFolderFinder( key, os.environ['NEUROD_DATA'], chosen_value) pathfolderfinder.signal_pathChanged[str, str].connect( self.on_guichanges) pathfolderfinder.signal_error[str].connect( self.on_error) self.paramsWidgets.update({key: pathfolderfinder}) layout.addRow(key, pathfolderfinder.layout) if not chosen_value: self.signal_error[str].emit( key + ' is empty! Provide a path before starting.') continue # For providing a file path. elif 'FILE' in key: pathfilefinder = PathFileFinder(key, chosen_value) pathfilefinder.signal_pathChanged[str, str].connect( self.on_guichanges) pathfilefinder.signal_error[str].connect(self.on_error) self.paramsWidgets.update({key: pathfilefinder}) layout.addRow(key, pathfilefinder.layout) if not chosen_value: self.signal_error[str].emit( key + ' is empty! Provide a file before starting.') # To select specific electrodes elif '_CHANNELS' in key or 'CHANNELS_' in key: ch_select = Channel_Select(key, self.channels, chosen_value) ch_select.signal_paramChanged[str, list].connect( self.on_guichanges) self.paramsWidgets.update({key: ch_select}) layout.addRow(key, ch_select.layout) elif 'BIAS' in key: # Add None to the list in case of no bias wanted self.directions = tuple([None] + list(self.directions)) bias = Connect_Bias(key, self.directions, chosen_value) bias.signal_paramChanged[str, object].connect( self.on_guichanges) self.paramsWidgets.update({key: bias}) layout.addRow(key, bias.l) # For all the int values. elif values is int: spinBox = Connect_SpinBox(key, chosen_value) spinBox.signal_paramChanged[str, int].connect( self.on_guichanges) self.paramsWidgets.update({key: spinBox}) layout.addRow(key, spinBox) # For all the float values. elif values is float: doublespinBox = Connect_DoubleSpinBox( key, chosen_value) doublespinBox.signal_paramChanged[str, float].connect( self.on_guichanges) self.paramsWidgets.update({key: doublespinBox}) layout.addRow(key, doublespinBox) # For parameters with multiple non-fixed values in a list (user can modify them) elif values is list: modifiable_list = Connect_Modifiable_List( key, chosen_value) modifiable_list.signal_paramChanged[str, list].connect( self.on_guichanges) self.paramsWidgets.update({key: modifiable_list}) layout.addRow(key, modifiable_list) continue # For parameters containing a string to modify elif values is str: lineEdit = Connect_LineEdit(key, chosen_value) lineEdit.signal_paramChanged[str, str].connect( self.on_guichanges) lineEdit.signal_paramChanged[str, type(None)].connect( self.on_guichanges) self.paramsWidgets.update({key: lineEdit}) layout.addRow(key, lineEdit) # For parameters with multiple fixed values. elif type(values) is tuple: comboParams = Connect_ComboBox(key, chosen_value, values) comboParams.signal_paramChanged[str, object].connect( self.on_guichanges) comboParams.signal_additionalParamChanged[ str, dict].connect(self.on_guichanges) self.paramsWidgets.update({key: comboParams}) layout.addRow(key, comboParams.layout) continue # For parameters with multiple non-fixed values in a dict (user can modify them) elif type(values) is dict: try: selection = chosen_value['selected'] comboParams = Connect_ComboBox( key, chosen_value, values) comboParams.signal_paramChanged[ str, object].connect(self.on_guichanges) comboParams.signal_additionalParamChanged[ str, dict].connect(self.on_guichanges) self.paramsWidgets.update({key: comboParams}) layout.addRow(key, comboParams.layout) except: modifiable_dict = Connect_Modifiable_Dict( key, chosen_value, values) modifiable_dict.signal_paramChanged[ str, dict].connect(self.on_guichanges) self.paramsWidgets.update({key: modifiable_dict}) layout.addRow(key, modifiable_dict) # Add a horizontal line to separate parameters' type. if p != param[-1]: separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setFrameShadow(QFrame.Sunken) layout.addRow(separator) # Display the parameters according to their types. if params[par][0] == 'Basic': self.ui.scrollAreaWidgetContents_Basics.setLayout(layout) elif params[par][0] == 'Advanced': self.ui.scrollAreaWidgetContents_Adv.setLayout(layout) # Connect inter-widgets signals and slots if self.modality == 'trainer': self.paramsWidgets['TRIGGER_FILE'].signal_pathChanged[ str, str].connect(trigger_def.on_new_tdef_file) self.paramsWidgets['TRIGGER_FILE'].on_selected() if self.modality == 'online': self.paramsWidgets['TRIGGER_FILE'].signal_pathChanged[ str, str].connect(directions.on_new_tdef_file) self.paramsWidgets['TRIGGER_FILE'].on_selected() self.paramsWidgets['DECODER_FILE'].signal_pathChanged[ str, str].connect(directions.on_new_decoder_file) self.paramsWidgets['DECODER_FILE'].on_selected() # ---------------------------------------------------------------------- def load_config(self, cfg_file): """ Dynamic loading of a config file. Format the lib to fit the previous developed neurodecode code if subject specific file (not for the templates). cfg_file: tuple containing the path and the config file name. """ if self.cfg_subject == None or cfg_file[ 1] not in self.cfg_subject.__file__: # Dynamic loading sys.path.append(cfg_file[0]) cfg_module = import_module(cfg_file[1].split('.')[0]) else: cfg_module = reload(self.cfg_subject) return cfg_module #---------------------------------------------------------------------- def load_all_params(self, cfg_template, cfg_file): """ Loads the params structure and assign the subject/s specific value. It also checks the sanity of the loaded params according to the protocol. """ try: # Loads the subject's specific values self.cfg_subject = self.load_config(cfg_file) # Loads the template if self.cfg_struct == None or cfg_template[ 1] not in self.cfg_struct.__file__: self.cfg_struct = self.load_config(cfg_template) # Display parameters on the GUI self.disp_params(self.cfg_struct, self.cfg_subject) # Check the parameters integrity self.m.check_config(self.cfg_subject) except Exception as e: self.signal_error[str].emit(str(e)) @pyqtSlot(str, str) @pyqtSlot(str, bool) @pyqtSlot(str, list) @pyqtSlot(str, float) @pyqtSlot(str, int) @pyqtSlot(str, dict) @pyqtSlot(str, tuple) @pyqtSlot(str, type(None)) # ---------------------------------------------------------------------- def on_guichanges(self, name, new_Value): """ Apply the modification to the corresponding param of the cfg module name = parameter name new_value = new str value to to change in the module """ # In case of a dict containing several option (contains 'selected') try: tmp = getattr(self.cfg_subject, name) tmp['selected'] = new_Value['selected'] tmp[new_Value['selected']] = new_Value[new_Value['selected']] setattr(self.cfg_subject, name, tmp) # In case of simple data format except: setattr(self.cfg_subject, name, new_Value) print("The parameter %s has been changed to %s" % (name, getattr(self.cfg_subject, name))) # ---------------------------------------------------------------------- @pyqtSlot() def on_click_pathSearch(self): """ Opens the File dialog window when the search button is pressed. """ buttonIcon = self.ui.pushButton_Search.text() if buttonIcon == 'Search': path_name = QFileDialog.getExistingDirectory( caption="Choose the subject's directory", directory=os.environ['NEUROD_SCRIPTS']) if path_name: self.ui.lineEdit_pathSearch.clear() self.ui.lineEdit_pathSearch.insert(path_name) self.ui.pushButton_Search.setText('Accept') self.ui.pushButton_Search.setStyleSheet("color: red;") else: self.ui.pushButton_Search.setText('Search') self.ui.pushButton_Search.setStyleSheet("color: black;") self.on_enable_modality() # ---------------------------------------------------------------------- def look_for_subject_file(self, modality): ''' Look if the subject config file is contained in the subject folder modality = offline, trainer or online ''' is_found = False cfg_file = None cfg_path = Path(self.ui.lineEdit_pathSearch.text()) for f in glob(os.fspath(cfg_path / "*.py"), recursive=False): fileName = os.path.split(f)[-1] if modality in fileName and 'structure' not in fileName: is_found = True cfg_file = f break return is_found, cfg_file #---------------------------------------------------------------------- def find_structure_file(self, cfg_file, modality): """ Find the structure config file associated with the subject config file cfg_file: subject specific config file modality = offline, trainer or online """ # Find the config template tmp = cfg_file.split('.')[0] # Remove the .py self.protocol = tmp.split('-')[-1] # Extract the protocol name template_path = Path( os.environ['NEUROD_ROOT'] ) / 'neurodecode' / 'config_files' / self.protocol / 'structure_files' for f in glob(os.fspath(template_path / "*.py"), recursive=False): fileName = os.path.split(f)[-1] if modality in fileName and 'structure' in fileName: return f #---------------------------------------------------------------------- def prepare_config_files(self, modality): """ Find both the subject config file and the associated structure config file paths """ is_found, cfg_file = self.look_for_subject_file(modality) if is_found is False: self.error_dialog.showMessage( 'Config file missing: copy an ' + modality + ' config file to the subject folder or create a new subjet') return None, None else: cfg_template = self.find_structure_file(cfg_file, modality) cfg_file = os.path.split(cfg_file) cfg_template = os.path.split(cfg_template) return cfg_file, cfg_template # ---------------------------------------------------------------------- def load_protocol_module(self, module_name): """ Load or reload the protocol's module associated with the modality module_name = name of the module to load """ if module_name not in sys.modules: path2protocol = os.path.split( self.ui.lineEdit_pathSearch.text())[0] sys.path.append(path2protocol) self.m = import_module(module_name) else: self.m = reload(sys.modules[module_name]) # ---------------------------------------------------------------------- @pyqtSlot() def on_click_offline(self): """ Loads the Offline parameters. """ self.modality = 'offline' cfg_file, cfg_template = self.prepare_config_files(self.modality) module_name = 'offline_' + self.protocol self.load_protocol_module(module_name) self.ui.checkBox_Record.setChecked(True) self.ui.checkBox_Record.setEnabled(False) if cfg_file and cfg_template: self.load_all_params(cfg_template, cfg_file) self.ui.groupBox_Launch.setEnabled(True) # ---------------------------------------------------------------------- @pyqtSlot() def on_click_train(self): """ Loads the Training parameters. """ self.modality = 'trainer' cfg_file, cfg_template = self.prepare_config_files(self.modality) module_name = 'trainer_' + self.protocol self.load_protocol_module(module_name) self.ui.checkBox_Record.setChecked(False) self.ui.checkBox_Record.setEnabled(False) if cfg_file and cfg_template: self.load_all_params(cfg_template, cfg_file) self.ui.groupBox_Launch.setEnabled(True) #---------------------------------------------------------------------- @pyqtSlot() def on_click_online(self): """ Loads the Online parameters. """ self.modality = 'online' cfg_file, cfg_template = self.prepare_config_files(self.modality) module_name = 'online_' + self.protocol self.load_protocol_module(module_name) self.ui.checkBox_Record.setChecked(True) self.ui.checkBox_Record.setEnabled(True) if cfg_file and cfg_template: self.load_all_params(cfg_template, cfg_file) self.ui.groupBox_Launch.setEnabled(True) #----------------------------------------------------------------------v @pyqtSlot() def on_click_start(self): """ Launch the selected protocol. It can be Offline, Train or Online. """ self.record_dir = Path(self.cfg_subject.DATA_PATH) ccfg = cfg_class(self.cfg_subject) # because a module is not pickable with self.record_state.get_lock(): self.record_state.value = 0 if not self.protocol_state.value: self.ui.textEdit_terminal.clear() # Recording shared variable + recording terminal if self.ui.checkBox_Record.isChecked(): amp = self.ui.comboBox_LSL.currentData() if not amp: self.signal_error[str].emit('No LSL amplifier specified.') return if not self.record_terminal: self.record_terminal = GuiTerminal(self.recordLogger, 'INFO', self.width()) self.hide_recordTerminal[bool].connect( self.record_terminal.setHidden) else: self.record_terminal.textEdit.clear() self.record_terminal.textEdit.insertPlainText( 'Waiting for the recording to start...\n') self.hide_recordTerminal[bool].emit(False) # Protocol shared variable with self.protocol_state.get_lock(): self.protocol_state.value = 2 # 0=stop, 1=start, 2=wait processesToLaunch = [('recording', recorder.run_gui, [self.record_state, self.protocol_state, self.record_dir, self.recordLogger, amp['name'], amp['serial'], False, self.record_terminal.my_receiver.queue]), \ ('protocol', self.m.run, [ccfg, self.protocol_state, self.my_receiver.queue])] else: # Protocol shared variable with self.protocol_state.get_lock(): self.protocol_state.value = 1 # 0=stop, 1=start, 2=wait processesToLaunch = [ ('protocol', self.m.run, [ccfg, self.protocol_state, self.my_receiver.queue]) ] launchedProcess = Thread(target=self.launching_subprocesses, args=processesToLaunch) launchedProcess.start() logger.info(self.modality + ' protocol starting...') self.ui.pushButton_Start.setText('Stop') else: with self.protocol_state.get_lock(): self.protocol_state.value = 0 time.sleep(2) self.hide_recordTerminal[bool].emit(True) self.ui.pushButton_Start.setText('Start') #---------------------------------------------------------------------- @pyqtSlot(str) def on_terminal_append(self, text): """ Writes to the QtextEdit_terminal the redirected stdout. """ self.ui.textEdit_terminal.moveCursor(QTextCursor.End) self.ui.textEdit_terminal.insertPlainText(text) @pyqtSlot() #---------------------------------------------------------------------- def on_click_newSubject(self): """ Instance a Connect_NewSubject QDialog class """ buttonIcon = self.ui.pushButton_NewSubject.text() if buttonIcon == 'New': qdialog = Connect_NewSubject(self, self.ui.lineEdit_pathSearch) qdialog.signal_error[str].connect(self.on_error) self.ui.pushButton_NewSubject.setText('Accept') self.ui.pushButton_NewSubject.setStyleSheet("color: red;") else: self.ui.pushButton_NewSubject.setText('New') self.ui.pushButton_NewSubject.setStyleSheet("color: black;") self.on_enable_modality() #---------------------------------------------------------------------- def on_error(self, errorMsg): """ Display the error message into a QErrorMessage """ self.error_dialog.showMessage(errorMsg) #---------------------------------------------------------------------- def on_click_save_params_to_file(self): """ Save the params to a config_file """ filePath, fileName = os.path.split(self.cfg_subject.__file__) fileName = fileName.split('.')[0] # Remove the .py file = self.cfg_subject.__file__.split( '.')[0] + '_' + datetime.now().strftime('%m.%d.%d.%M') + '.py' filePath = QFileDialog.getSaveFileName(self, 'Save config file', file, 'python(*.py)') if filePath[0]: save_params_to_file(filePath[0], cfg_class(self.cfg_subject)) @pyqtSlot(list) #---------------------------------------------------------------------- def fill_comboBox_lsl(self, amp_list): """ Fill the comboBox with the available lsl streams """ # Clear the comboBox_lsl first self.ui.comboBox_LSL.clear() for amp in amp_list: amp_formated = '{} ({})'.format(amp[1], amp[2]) self.ui.comboBox_LSL.addItem(amp_formated, { 'name': amp[1], 'serial': amp[2] }) self.ui.pushButton_LSL.setText('Search') self.ui.pushButton_Viewer.setEnabled(True) #---------------------------------------------------------------------- def on_click_lsl_button(self): """ Find the available lsl streams and display them in the comboBox_LSL """ if self.lsl_state.value == 1: with self.lsl_state.get_lock(): self.lsl_state.value = 0 self.lsl_thread.terminate() self.lsl_thread.wait() self.ui.pushButton_LSL.setText('Search') else: self.ui.textEdit_terminal.clear() with self.lsl_state.get_lock(): self.lsl_state.value = 1 self.lsl_thread = search_lsl_streams_thread(self.lsl_state, logger) self.lsl_thread.signal_lsl_found[list].connect( self.fill_comboBox_lsl) self.lsl_thread.start() self.ui.pushButton_LSL.setText('Stop') #---------------------------------------------------------------------- def on_click_start_viewer(self): """ Launch the viewer to check the signals in a seperate process """ # Start Viewer if not self.viewer_state.value: self.ui.textEdit_terminal.clear() with self.viewer_state.get_lock(): self.viewer_state.value = 1 amp = self.ui.comboBox_LSL.currentData() viewerprocess = mp.Process( target=instantiate_scope, args=[amp, self.viewer_state, logger, self.my_receiver.queue]) viewerprocess.start() self.ui.pushButton_Viewer.setText('Stop') # Stop Viewer else: with self.viewer_state.get_lock(): self.viewer_state.value = 0 self.ui.pushButton_Viewer.setEnabled(True) self.ui.pushButton_Viewer.setText('Viewer') @pyqtSlot() #---------------------------------------------------------------------- def on_enable_modality(self): """ Enable the modalities groupBox if the provided path exists """ subjectFolder = self.ui.lineEdit_pathSearch.text() if subjectFolder: exist = os.path.isdir(subjectFolder) if not exist: self.signal_error[str].emit( 'The provided subject folder does not exists.') else: self.ui.groupBox_Modality.setEnabled(True) #---------------------------------------------------------------------- def connect_signals_to_slots(self): """Connects the signals to the slots""" # New subject button self.ui.pushButton_NewSubject.clicked.connect(self.on_click_newSubject) # Search Subject folder Search button self.ui.pushButton_Search.clicked.connect(self.on_click_pathSearch) # Enable modality when subject folder path is given self.ui.lineEdit_pathSearch.editingFinished.connect( self.on_enable_modality) # Offline button self.ui.pushButton_Offline.clicked.connect(self.on_click_offline) # Train button self.ui.pushButton_Train.clicked.connect(self.on_click_train) # Online button self.ui.pushButton_Online.clicked.connect(self.on_click_online) # Start button self.ui.pushButton_Start.clicked.connect(self.on_click_start) # Save conf file self.ui.actionSave_config_file.triggered.connect( self.on_click_save_params_to_file) # Error dialog self.signal_error[str].connect(self.on_error) # Start viewer button self.ui.pushButton_Viewer.clicked.connect(self.on_click_start_viewer) # LSL button self.ui.pushButton_LSL.clicked.connect(self.on_click_lsl_button) #---------------------------------------------------------------------- def launching_subprocesses(*args): """ Launch subprocesses processesToLaunch = list of tuple containing the functions to launch and their args """ launchedProcesses = dict() for p in args[1:]: launchedProcesses[p[0]] = mp.Process(target=p[1], args=p[2]) launchedProcesses[p[0]].start() # Wait that the protocol is finished to stop recording launchedProcesses['protocol'].join() try: launchedProcesses['recording'] recordState = args[1][2][0] # Sharing variable with recordState.get_lock(): recordState.value = 0 except: pass
class _POSIXUserscriptRunner(_BaseUserscriptRunner): """Userscript runner to be used on POSIX. Uses _BlockingFIFOReader. The OS must have support for named pipes and select(). Commands are executed immediately when they arrive in the FIFO. Attributes: _reader: The _BlockingFIFOReader instance. _thread: The QThread where reader runs. """ def __init__(self, win_id, parent=None): super().__init__(win_id, parent) self._reader = None self._thread = None def run(self, cmd, *args, env=None): rundir = standarddir.get(QStandardPaths.RuntimeLocation) # tempfile.mktemp is deprecated and discouraged, but we use it here to # create a FIFO since the only other alternative would be to create a # directory and place the FIFO there, which sucks. Since os.kfifo will # raise an exception anyways when the path doesn't exist, it shouldn't # be a big issue. self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir) os.mkfifo(self._filepath) # pylint: disable=no-member self._reader = _BlockingFIFOReader(self._filepath) self._thread = QThread(self) self._reader.moveToThread(self._thread) self._reader.got_line.connect(self.got_cmd) self._thread.started.connect(self._reader.read) self._reader.finished.connect(self.on_reader_finished) self._thread.finished.connect(self.on_thread_finished) self._run_process(cmd, *args, env=env) self._thread.start() def on_proc_finished(self): """Interrupt the reader when the process finished.""" log.procs.debug("proc finished") self._thread.requestInterruption() def on_proc_error(self, error): """Interrupt the reader when the process had an error.""" super().on_proc_error(error) self._thread.requestInterruption() def on_reader_finished(self): """Quit the thread and clean up when the reader finished.""" log.procs.debug("reader finished") self._thread.quit() self._reader.fifo.close() self._reader.deleteLater() super()._cleanup() self.finished.emit() def on_thread_finished(self): """Clean up the QThread object when the thread finished.""" log.procs.debug("thread finished") self._thread.deleteLater()
class APTDataViewer(QObject): """ A class to show ion maps and mass spectrum. """ # finished = pyqtSignal() def __init__(self, PosData, point_size=2, max_ion=1000): super().__init__() self.apt_data_viewer = QWidget() self.layout = QGridLayout() self.apt_data_viewer.setLayout(self.layout) self.apt_data_viewer.sizeHint = lambda: pg.QtCore.QSize(1920, 1200) self.point_cloud_view = gl.GLViewWidget() self.point_cloud_view.sizeHint = lambda: pg.QtCore.QSize(1920, 700) self.point_cloud_view.setBackgroundColor('w') self.point_cloud_view.opts['distance'] = 2000 self.point_cloud_view.opts['fov'] = 1 vb = CustomViewBox( ) # A custom view box added to mass spec view, this way the visualization performance is greatly enhanced. self.mass_spec = pg.PlotWidget(viewBox=vb) self.mass_spec.setBackground('w') self.mass_spec.sizeHint = lambda: pg.QtCore.QSize(1920, 500) self.mass_spec.setDownsampling( auto=True, mode='mean' ) # because there are some werid issue using downsampling mode when there are 0s in mass spec # self.mass_spec.setLogMode(y=True) self.mass_spec.setLabel('bottom', text='Mass-to-Charge (Dalton)') self.mass_spec.setLabel('left', text='Counts') self.layout.addWidget(self.point_cloud_view, 0, 0) self.layout.addWidget(self.mass_spec, 1, 0) self.MassWorker = APTMassWorker(PosData.m2z, PosData.intensity) self.thread_mass = QThread() self.MassWorker.plot_ready.connect(self.mass_plot_ready) self.MassWorker.finished.connect(self.thread_mass.quit) self.MassWorker.moveToThread(self.thread_mass) self.thread_mass.started.connect(self.MassWorker.plot_mass_spec) self.thread_mass.start() self.PosWorker = APTPosWorker(PosData.pos[:, 0:3], PosData.identity, PosData.ions, point_size, max_ion) self.thread_pos = QThread() self.PosWorker.gl_ready.connect(self.point_cloud_ready) self.PosWorker.finished.connect(self.thread_pos.quit) self.PosWorker.moveToThread(self.thread_pos) self.thread_pos.started.connect(self.PosWorker.visualize_pos) self.thread_pos.start() self.apt_data_viewer.show() @pyqtSlot(object, name='mass_plot_ready') def mass_plot_ready(self, item): self.mass_spec.addItem(item) @pyqtSlot(object, name='point_cloud_ready') def point_cloud_ready(self, sp1): self.point_cloud_view.addItem(sp1)
class ProcessDialog(QDialog): def __init__(self, port, **kwargs): super().__init__() self.setWindowTitle('Tasmotizing...') self.setFixedWidth(400) self.exception = None esptool.sw.progress.connect(self.update_progress) self.nam = QNetworkAccessManager() self.nrBinFile = QNetworkRequest() self.bin_data = b'' self.setLayout(VLayout(5, 5)) self.actions_layout = QFormLayout() self.actions_layout.setSpacing(5) self.layout().addLayout(self.actions_layout) self._actions = [] self._action_widgets = {} self.port = port self.auto_reset = kwargs.get('auto_reset', False) self.file_path = kwargs.get('file_path') if self.file_path and self.file_path.startswith('http'): self._actions.append('download') self.backup = kwargs.get('backup') if self.backup: self._actions.append('backup') self.backup_size = kwargs.get('backup_size') self.erase = kwargs.get('erase') if self.erase: self._actions.append('erase') if self.file_path: self._actions.append('write') self.create_ui() self.start_process() def create_ui(self): for action in self._actions: pb = QProgressBar() pb.setFixedHeight(35) self._action_widgets[action] = pb self.actions_layout.addRow(action.capitalize(), pb) self.btns = QDialogButtonBox(QDialogButtonBox.Abort) self.btns.rejected.connect(self.abort) self.layout().addWidget(self.btns) self.sb = QStatusBar() self.layout().addWidget(self.sb) def appendBinFile(self): self.bin_data += self.bin_reply.readAll() def saveBinFile(self): if self.bin_reply.error() == QNetworkReply.NoError: self.file_path = self.file_path.split('/')[-1] with open(self.file_path, 'wb') as f: f.write(self.bin_data) self.run_esp() else: raise NetworkError def updateBinProgress(self, recv, total): self._action_widgets['download'].setValue(recv // total * 100) def download_bin(self): self.nrBinFile.setUrl(QUrl(self.file_path)) self.bin_reply = self.nam.get(self.nrBinFile) self.bin_reply.readyRead.connect(self.appendBinFile) self.bin_reply.downloadProgress.connect(self.updateBinProgress) self.bin_reply.finished.connect(self.saveBinFile) def show_connection_state(self, state): self.sb.showMessage(state, 0) def run_esp(self): params = { 'file_path': self.file_path, 'auto_reset': self.auto_reset, 'erase': self.erase } if self.backup: backup_size = f'0x{2 ** self.backup_size}00000' params['backup_size'] = backup_size self.esp_thread = QThread() self.esp = ESPWorker(self.port, self._actions, **params) esptool.sw.connection_state.connect(self.show_connection_state) self.esp.waiting.connect(self.wait_for_user) self.esp.done.connect(self.accept) self.esp.error.connect(self.error) self.esp.moveToThread(self.esp_thread) self.esp_thread.started.connect(self.esp.run) self.esp_thread.start() def start_process(self): if 'download' in self._actions: self.download_bin() self._actions = self._actions[1:] else: self.run_esp() def update_progress(self, action, value): self._action_widgets[action].setValue(value) @pyqtSlot() def wait_for_user(self): dlg = QMessageBox.information( self, 'User action required', 'Please power cycle the device, wait a moment and press OK', QMessageBox.Ok | QMessageBox.Cancel) if dlg == QMessageBox.Ok: self.esp.continue_ok() elif dlg == QMessageBox.Cancel: self.esp.abort() self.esp.continue_ok() self.abort() def stop_thread(self): self.esp_thread.wait(2000) self.esp_thread.exit() def accept(self): self.stop_thread() self.done(QDialog.Accepted) def abort(self): self.sb.showMessage('Aborting...', 0) QApplication.processEvents() self.esp.abort() self.stop_thread() self.reject() def error(self, e): self.exception = e self.abort() def closeEvent(self, e): self.stop_thread()
class ExampleApp(QtWidgets.QWidget, mouseEvent.Ui_Form): def __init__(self): # Это здесь нужно для доступа к переменным, методам # и т.д. в файле design.py super().__init__() self.stopEvent = threading.Event() self.check = True self.thread = QThread() self.thread.start() self.setWindowFlags(Qt.FramelessWindowHint) self.setupUi(self) # Это нужно для инициализации нашего дизайна self.robot = xmlrpc.client.ServerProxy('http://%s:%d' % (IP_ROBOT, CONTROL_PORT)) self.spawer = Spawer(self.on_recive, self.stopEvent) self.spawer.start() def on_recive(self, img): pixmap = QtGui.QPixmap(img) self.label.setPixmap(pixmap) def closeEvent(self, event): reply = QtWidgets.QMessageBox.question( self, 'Message', "Are you sure to quit?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: _ = self.robot.stopStreaming() self.stopEvent.wait() event.accept() else: event.ignore() def keyPressEvent(self, e): if self.check: if e.type() == QEvent.KeyPress: if e.text() == 'w': _ = self.robot.comRecv('cam/down') #self.check = False e.accept() elif e.text() == 'd': _ = self.robot.comRecv('cam/RIGHT') #self.check = False e.accept() elif e.text() == 'a': _ = self.robot.comRecv('cam/LEFT') #self.check = False e.accept() elif e.text() == 's': _ = self.robot.comRecv('cam/up') #self.check = False e.accept() elif e.key() == Qt.Key_Up: _ = self.robot.comRecv('drive/ahead/200') self.check = False e.accept() elif e.key() == Qt.Key_Down: _ = self.robot.comRecv('drive/backward/200') self.check = False e.accept() elif e.key() == Qt.Key_Left: _ = self.robot.comRecv('drive/left/200') self.check = False e.accept() elif e.key() == Qt.Key_Right: _ = self.robot.comRecv('drive/right/200') self.check = False e.accept() if e.key() == Qt.Key_Escape: self.close() def keyReleaseEvent(self, e): if e.type() == QEvent.KeyRelease: if e.key() in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right ] and not e.isAutoRepeat(): _ = self.robot.comRecv('drive/ahead/0') self.check = True e.accept()
class Ui_Window(QtWidgets.QDialog, UI.UICoordTransform.Ui_Dialog): def __init__(self, parent): super(Ui_Window, self).__init__(parent=parent) self.setupUi(self) font = QtGui.QFont() font.setFamily("微软雅黑") font.setPointSize(9) self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.button(QDialogButtonBox.Ok).setFont(font) self.buttonBox.button(QDialogButtonBox.Ok).setText("确定") self.buttonBox.button(QDialogButtonBox.Cancel).setFont(font) self.buttonBox.button(QDialogButtonBox.Cancel).setText("取消") vlayout = QtWidgets.QVBoxLayout(self) vlayout.addWidget(self.splitter) vlayout.setContentsMargins(0, 0, 10, 10) self.splitter.setGeometry(0, 0, self.width(), self.height()) self.splitter.setOrientation(Qt.Horizontal) self.splitter.setProperty("Stretch", SplitterState.collapsed) self.splitter.setProperty("Dock", Dock.right) self.splitter.setProperty("WidgetToHide", self.txt_log) self.splitter.setProperty("ExpandParentForm", True) self.splitter.setSizes([600, self.splitter.width() - 590]) self.resize(self.splitter.width(), self.splitter.height()) self.paras = {} # 存储参数信息 self.selIndex = QModelIndex() self.table_init() log.setLogViewer(parent=self, logViewer=self.txt_log) self.txt_log.setReadOnly(True) self.btn_addressFile.clicked.connect(self.open_addressFile) self.btn_addRow.clicked.connect(self.btn_addRow_clicked) self.btn_removeRow.clicked.connect(self.btn_removeBtn_clicked) self.btn_saveMetaFile.clicked.connect(self.btn_saveMetaFile_clicked) self.buttonBox.clicked.connect(self.buttonBox_clicked) self.splitter.splitterMoved.connect(self.splitterMoved) self.splitter.handle(1).handleClicked.connect(self.handleClicked) self.rbtn_file.clicked.connect(self.rbtn_toggled) self.rbtn_filedb.clicked.connect(self.rbtn_toggled) self.rbtn_table.clicked.connect(self.rbtn_toggled) self.thread = QThread(self) self.coordTransformThread = coordTransformWorker() self.coordTransformThread.moveToThread(self.thread) self.coordTransformThread.transform.connect( self.coordTransformThread.coordTransform) self.coordTransformThread.transform_tbl.connect( self.coordTransformThread.tableTransform) self.coordTransformThread.finished.connect(self.threadStop) def showEvent(self, a0: QtGui.QShowEvent) -> None: log.setLogViewer(parent=self, logViewer=self.txt_log) self.rbtn_file.click() # self.table_layout() def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: self.table_layout() def threadStop(self): self.thread.quit() def splitterMoved(self): self.table_layout() def handleClicked(self): self.table_layout() def table_layout(self): if self.rbtn_table.isChecked(): self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.1) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.1) self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(4, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(5, self.tbl_address.width() * 0.2) else: self.tbl_address.setColumnWidth(0, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(1, self.tbl_address.width() * 0.15) self.tbl_address.setColumnWidth(2, self.tbl_address.width() * 0.15) self.tbl_address.setColumnWidth(3, self.tbl_address.width() * 0.15) self.tbl_address.setColumnWidth(4, self.tbl_address.width() * 0.2) self.tbl_address.setColumnWidth(5, self.tbl_address.width() * 0.15) @Slot() def btn_addRow_clicked(self): try: save_srs_list = list(srs_dict.values()) if self.rbtn_file.isChecked(): fileNames, types = QFileDialog.getOpenFileNames( self, "选择需要转换的图形文件", os.getcwd(), "图形文件(*.shp *.geojson *.dwg);;ESRI Shapefile(*.shp);;GeoJson(*.geojson);;CAD drawing(*.dwg)" ) if len(fileNames) == 0: return for fileName in fileNames: fileType = get_suffix(fileName) if fileType == DataType.shapefile: wks = workspaceFactory().get_factory( DataType.shapefile) elif fileType == DataType.geojson: wks = workspaceFactory().get_factory(DataType.geojson) elif fileType == DataType.cad_dwg: wks = workspaceFactory().get_factory(DataType.cad_dwg) else: log.error("不识别的图形文件格式!", dialog=True) return None datasource = wks.openFromFile(fileName) if datasource is not None: layer_name = wks.getLayerNames()[0] in_layer = datasource.GetLayer() row = self.add_layer_to_row(in_layer, fileName, layer_name) # self.add_delegate_to_row(row, fileName, [layer_name], save_srs_list) levelData = { 'layer_names': [layer_name], 'srs_list': save_srs_list } self.model.setLevelData(fileName, levelData) datasource.Release() datasource = None in_layer = None else: layer_name, suffix = os.path.splitext( os.path.basename(fileName)) row = self.add_layer_to_row(None, fileName, layer_name) levelData = { 'layer_names': [layer_name], 'srs_list': save_srs_list } self.model.setLevelData(fileName, levelData) # self.add_delegate_to_row(row, fileName, [layer_name], save_srs_list) elif self.rbtn_filedb.isChecked(): fileName = QtWidgets.QFileDialog.getExistingDirectory( self, "选择需要转换的GDB数据库", os.getcwd(), QFileDialog.ShowDirsOnly) wks = workspaceFactory().get_factory(DataType.fileGDB) datasource = wks.openFromFile(fileName) if datasource is not None: lst_names = wks.getLayerNames() selected_names = None if len(lst_names) > 1: selected_names = nameListDialog().openListDialog( "请选择要转换的图层", lst_names) elif len(lst_names) == 1: selected_names = [lst_names[0]] rows = [] if selected_names is not None: for selected_name in selected_names: layer = wks.openLayer(selected_name) row = self.add_layer_to_row( layer, fileName, selected_name) rows.append(row) for row in rows: levelData = { 'layer_names': lst_names, 'srs_list': save_srs_list } self.model.setLevelData(fileName, levelData) # self.add_delegate_to_row(row, fileName, lst_names, save_srs_list) elif self.rbtn_table.isChecked(): fileNames, types = QFileDialog.getOpenFileNames( self, "选择需要转换的表格文件", os.getcwd(), "表格文件(*.csv *.xlsx *.dbf);;csv文件(*.csv);;excel文件(*.xlsx);;dbf文件(*.dbf)" ) if len(fileNames) == 0: return for fileName in fileNames: fileType = get_suffix(fileName) row = self.add_table_to_row(fileName) if fileType == DataType.xlsx: # selected_sheet = [] # wb = load_workbook(fileName, read_only=True) # wb.close() # lst_names = wb.sheetnames # if len(lst_names) > 1: # selected_sheet = nameListDialog().openListDialog( # "请选择工作表(sheet)", lst_names, QAbstractItemView.SingleSelection) # elif len(lst_names) == 1: # selected_sheet = lst_names[0] header, bheader = read_table_header( fileName, fileType, None) levelData = { 'is_header': bheader, # 'sheet': selected_sheet[0], 'field_list': header, 'srs_list': save_srs_list } elif fileType == DataType.csv: header, encoding, bheader = read_table_header( fileName, fileType) levelData = { 'is_header': bheader, 'encoding': encoding, 'field_list': header, 'srs_list': save_srs_list } elif fileType == DataType.dbf: header = read_table_header(fileName, fileType) levelData = { 'is_header': True, 'field_list': header, 'srs_list': save_srs_list } else: log.error("不识别的图形文件格式!", dialog=True) return None field_delegate = xyfieldDelegate(self, [ None, { 'type': 'xy' }, { 'type': 'xy' }, { 'type': 'srs' }, { 'type': 'srs' }, { 'type': 'f', 'text': '请选择需要保存的文件' } ]) self.tbl_address.setItemDelegateForRow(row, field_delegate) self.model.setLevelData(fileName, levelData) except: log.error(traceback.format_exc()) def add_table_to_row(self, fileName): row = self.model.rowCount(QModelIndex()) self.model.addEmptyRow(row, 1, 0) in_path_index = self.tbl_address.model().index(row, self.in_path_no) self.tbl_address.model().setData(in_path_index, fileName) return row def add_layer_to_row(self, in_layer, fileName, layer_name): if in_layer is not None: in_srs = in_layer.GetSpatialRef() if in_srs is not None: in_srs = osr.SpatialReference(in_srs.ExportToWkt()) srs_epsg = in_srs.GetAttrValue("AUTHORITY", 1) srs_desc = srs_dict[int(srs_epsg)] else: srs_desc = None else: srs_desc = None row = self.model.rowCount(QModelIndex()) self.model.addEmptyRow(row, 1, 0) in_path_index = self.tbl_address.model().index(row, self.in_path_no) in_layername_index = self.tbl_address.model().index( row, self.in_layername_no) in_srs_index = self.tbl_address.model().index(row, self.in_srs_no) self.tbl_address.model().setData(in_path_index, fileName) self.tbl_address.model().setData(in_layername_index, layer_name) self.tbl_address.model().setData(in_srs_index, srs_desc) return row @Slot() def open_addressFile(self): fileName, fileType = QtWidgets.QFileDialog.getOpenFileName( self, "选择坐标转换参数文件", os.getcwd(), "All Files(*)") self.txt_addressFile.setText(fileName) if fileName == "": return try: with open(fileName, 'r', encoding='utf-8') as f: self.paras = json.load(f) self.add_address_rows_from_paras() except: if self.model.rowCount(QModelIndex()) > 0: self.model.removeRows( self.model.rowCount(QModelIndex()) - 1, 1, QModelIndex()) log.error("读取参数文件失败!", dialog=True) def add_address_rows_from_paras(self): imps = self.paras['exports'] for imp in imps: row = self.model.rowCount(QModelIndex()) self.model.addEmptyRow(row, 1, 0) if self.rbtn_table.isChecked(): fileType = get_suffix(imp['in_path']) header = imp['field_list'] if fileType == DataType.csv: header, encoding, bheader = read_table_header( imp['in_path'], fileType) self.model.setLevelData( imp['in_path'], { 'is_header': bheader, 'encoding': encoding, 'field_list': header, 'srs_list': imp['srs_list'] }) elif fileType == DataType.xlsx: header, bheader = read_table_header( imp['in_path'], fileType, None) self.model.setLevelData( imp['in_path'], { 'is_header': bheader, 'field_list': header, 'srs_list': imp['srs_list'] }) elif fileType == DataType.dbf: self.model.setLevelData( imp['in_path'], { 'is_header': True, 'field_list': header, 'srs_list': imp['srs_list'] }) x_field_index = self.tbl_address.model().index(row, 1) y_field_index = self.tbl_address.model().index(row, 2) in_srs_index = self.tbl_address.model().index(row, 3) out_srs_index = self.tbl_address.model().index(row, 4) x_field_delegate = self.tbl_address.itemDelegate(x_field_index) y_field_delegate = self.tbl_address.itemDelegate(y_field_index) in_srs_delegate = self.tbl_address.itemDelegate(in_srs_index) out_srs_delegate = self.tbl_address.itemDelegate(out_srs_index) if isinstance(x_field_delegate, xyfieldDelegate): x_field_delegate.set_field_list(header) if isinstance(y_field_delegate, xyfieldDelegate): y_field_delegate.set_field_list(header) if isinstance(in_srs_delegate, srsDelegate): in_srs_delegate.set_srs_list(imp['srs_list']) if isinstance(out_srs_delegate, srsDelegate): out_srs_delegate.set_srs_list(imp['srs_list']) field_delegate = xyfieldDelegate(self, [ None, { 'type': 'xy' }, { 'type': 'xy' }, { 'type': 'srs' }, { 'type': 'srs' }, { 'type': 'f', 'text': '请选择需要保存的文件' } ]) self.tbl_address.setItemDelegateForRow(row, field_delegate) self.tbl_address.model().setData( self.tbl_address.model().index(row, 0), imp['in_path']) if imp['x_field'] in header: self.tbl_address.model().setData( self.tbl_address.model().index(row, 1), imp['x_field']) if imp['y_field'] in header: self.tbl_address.model().setData( self.tbl_address.model().index(row, 2), imp['y_field']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 3), imp['in_srs']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 4), imp['out_srs']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 5), imp['out_path']) else: in_srs_index = self.tbl_address.model().index( row, self.in_srs_no) out_srs_index = self.tbl_address.model().index( row, self.out_srs_no) in_srs_delegate = self.tbl_address.itemDelegate(in_srs_index) out_srs_delegate = self.tbl_address.itemDelegate(out_srs_index) if isinstance(in_srs_delegate, srsDelegate): in_srs_delegate.set_srs_list(imp['srs_list']) if isinstance(out_srs_delegate, srsDelegate): out_srs_delegate.set_srs_list(imp['srs_list']) self.model.setLevelData(imp['in_path'], { 'layer_names': imp['layer_names'], 'srs_list': imp['srs_list'] }) self.tbl_address.model().setData( self.tbl_address.model().index(row, 0), imp['in_path']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 1), imp['in_layer']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 2), imp['in_srs']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 3), imp['out_srs']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 4), imp['out_path']) self.tbl_address.model().setData( self.tbl_address.model().index(row, 5), imp['out_layer']) @Slot(QAbstractButton) def buttonBox_clicked(self, button: QAbstractButton): if button == self.buttonBox.button(QDialogButtonBox.Ok): if not self.check_paras(): return self.thread.start() self.run_process() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): self.close() def check_paras(self): rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) if self.rbtn_table.isChecked(): for row in rows: in_path_index = self.tbl_address.model().index( row, 0, QModelIndex()) x_field_index = self.tbl_address.model().index( row, 1, QModelIndex()) y_field_index = self.tbl_address.model().index( row, 2, QModelIndex()) in_srs_index = self.tbl_address.model().index( row, 3, QModelIndex()) out_srs_index = self.tbl_address.model().index( row, 4, QModelIndex()) out_path_index = self.tbl_address.model().index( row, 5, QModelIndex()) in_path = str(self.tbl_address.model().data( in_path_index, Qt.DisplayRole)).strip() x_field = str(self.tbl_address.model().data( x_field_index, Qt.DisplayRole)).strip() y_field = str(self.tbl_address.model().data( y_field_index, Qt.DisplayRole)).strip() in_srs = str(self.tbl_address.model().data( in_srs_index, Qt.DisplayRole)).strip() out_srs = str(self.tbl_address.model().data( out_srs_index, Qt.DisplayRole)).strip() out_path = str(self.tbl_address.model().data( out_path_index, Qt.DisplayRole)).strip() if in_path == "": log.error('第{}行缺失必要参数"输入路径",请补全!'.format(row), dialog=True) return False in_format = get_suffix(in_path) if in_format is None: log.error('第{}行的输入数据格式不支持!目前只支持csv, excel和dbf'.format(row), dialog=True) return False if x_field == "": log.error('第{}行缺失必要参数"x坐标",请补全!'.format(row)) return False if y_field == "": log.error('第{}行缺失必要参数"y坐标",请补全!'.format(row)) return False if in_srs == "": log.error('第{}行缺失必要参数"输入坐标系",请补全!'.format(row)) return False if out_srs == "": log.error('第{}行缺失必要参数"输出坐标系",请补全!'.format(row)) return False if out_path == "": in_filename, in_suffix = os.path.splitext( os.path.basename(in_path)) out_file = self.default_outfile(in_path, in_format, in_filename, out_srs) if not os.path.exists("res"): os.makedirs("res") out_path = os.path.join(os.path.abspath("res"), out_file) if is_already_opened_in_write_mode(out_path): out_path = launderName(out_path) self.tbl_address.model().setData(out_path_index, out_path) log.warning('第{}行参数"输出路径"缺失数据源,自动补全为默认值{}'.format( row, out_path)) else: for row in rows: in_path_index = self.tbl_address.model().index( row, 0, QModelIndex()) in_layername_index = self.tbl_address.model().index( row, 1, QModelIndex()) in_srs_index = self.tbl_address.model().index( row, 2, QModelIndex()) out_srs_index = self.tbl_address.model().index( row, 3, QModelIndex()) out_path_index = self.tbl_address.model().index( row, 4, QModelIndex()) out_layername_index = self.tbl_address.model().index( row, 5, QModelIndex()) in_path = str(self.tbl_address.model().data( in_path_index, Qt.DisplayRole)).strip() in_layername = str(self.tbl_address.model().data( in_layername_index, Qt.DisplayRole)).strip() in_srs = str(self.tbl_address.model().data( in_srs_index, Qt.DisplayRole)).strip() out_srs = str(self.tbl_address.model().data( out_srs_index, Qt.DisplayRole)).strip() out_path = str(self.tbl_address.model().data( out_path_index, Qt.DisplayRole)).strip() out_layername = str(self.tbl_address.model().data( out_layername_index, Qt.DisplayRole)).strip() if in_path == "": log.error('第{}行缺失必要参数"输入路径",请补全!'.format(row), dialog=True) return False in_format = get_suffix(in_path) if in_format is None: log.error( '第{}行的输入数据格式不支持!目前只支持shapefile, fileGDB, geojson和cad dwg' .format(row), dialog=True) return False else: in_wks = workspaceFactory().get_factory(in_format) if in_wks is None: log.error("缺失图形文件引擎!") return False if in_wks.driver is None: log.error("缺失图形文件引擎{}!".format(in_wks.driverName)) return False if in_layername == "": if in_format == DataType.fileGDB: log.error('第{}行参数缺失必要参数"输入图层"!') return False else: in_layername, suffix = os.path.splitext( os.path.basename(in_path)) self.tbl_address.model().setData( in_layername_index, in_layername) log.warning('第{}行参数缺失参数"输入图层",已自动补全为{}'.format( row, in_layername)) if in_srs == "": log.error('第{}行缺失必要参数"输入坐标系",请补全!'.format(row), dialog=True) return False if out_srs == "": log.error('第{}行缺失必要参数"输出坐标系",请补全!'.format(row), dialog=True) return False if out_layername == "": out_layername = in_layername + "_" + str(out_srs) self.tbl_address.model().setData(out_layername_index, out_layername) log.warning('第{}行参数缺失参数"输出图层",自动补全为默认值{}'.format( row, out_layername)) self.autofill_outpath(row, in_path, out_path, in_layername, out_srs, in_format, out_path_index) return True def default_outfile(self, in_path, in_format, in_layername, out_srs): out_file = "" if in_format == DataType.fileGDB: in_filename, in_suffix = os.path.splitext( os.path.basename(in_path)) out_file = "{}_converted.gdb".format(in_filename) elif in_format == DataType.geojson: out_file = "{}_{}_{}.geojson".format(in_layername, out_srs, encodeCurrentTime()) elif in_format == DataType.shapefile: out_file = "{}_{}_{}.shp".format(in_layername, out_srs, encodeCurrentTime()) elif in_format == DataType.cad_dwg: out_file = "{}_{}_{}.dwg".format(in_layername, out_srs, encodeCurrentTime()) elif in_format == DataType.csv or in_format == DataType.xlsx or in_format == DataType.dbf: out_file = "{}_{}_{}.csv".format(in_layername, out_srs, encodeCurrentTime()) return out_file def autofill_outpath(self, row, in_path, out_path, in_layername, out_srs, in_format, out_path_index): if out_path == "": out_file = self.default_outfile(in_path, in_format, in_layername, out_srs) if not os.path.exists("res"): os.makedirs("res") out_path = os.path.join(os.path.abspath("res"), out_file) self.tbl_address.model().setData(out_path_index, out_path) log.warning('第{}行参数缺失参数"输出路径",自动补全为默认值{}'.format(row, out_path)) else: out_format = get_suffix(out_path) if out_format is None: out_file = self.default_outfile(in_path, in_format, in_layername, out_srs) out_path = os.path.join(out_path, out_file) self.tbl_address.model().setData(out_path_index, out_path) log.warning('第{}行参数"输出路径"缺失数据源,自动补全为默认值{}'.format( row, out_path)) def run_process(self): rows = range(0, self.tbl_address.model().rowCount(QModelIndex())) if self.rbtn_table.isChecked(): for row in rows: in_path_index = self.tbl_address.model().index( row, 0, QModelIndex()) x_field_index = self.tbl_address.model().index( row, 1, QModelIndex()) y_field_index = self.tbl_address.model().index( row, 2, QModelIndex()) in_srs_index = self.tbl_address.model().index( row, 3, QModelIndex()) out_srs_index = self.tbl_address.model().index( row, 4, QModelIndex()) out_path_index = self.tbl_address.model().index( row, 5, QModelIndex()) in_path = str(self.tbl_address.model().data( in_path_index, Qt.DisplayRole)).strip() x_field = str(self.tbl_address.model().data( x_field_index, Qt.DisplayRole)).strip() y_field = str(self.tbl_address.model().data( y_field_index, Qt.DisplayRole)).strip() in_srs = str(self.tbl_address.model().data( in_srs_index, Qt.DisplayRole)).strip() out_srs = str(self.tbl_address.model().data( out_srs_index, Qt.DisplayRole)).strip() out_path = str(self.tbl_address.model().data( out_path_index, Qt.DisplayRole)).strip() in_srs = list(srs_dict.keys())[list( srs_dict.values()).index(in_srs)] out_srs = list(srs_dict.keys())[list( srs_dict.values()).index(out_srs)] x = self.model.levels[in_path]['field_list'].index(x_field) y = self.model.levels[in_path]['field_list'].index(y_field) # inencode = check_encoding(in_path) fileType = get_suffix(in_path) bheader = self.model.levels[in_path]['is_header'] if fileType == DataType.csv: inencode = self.model.levels[in_path]['encoding'] # UICore.coordTransform_table.coordTransform(in_path, inencode, bheader, x, y, in_srs, out_srs, out_path, "gbk") self.coordTransformThread.transform_tbl.emit( in_path, inencode, bheader, x, y, in_srs, out_srs, out_path, "gb18030") else: self.coordTransformThread.transform_tbl.emit( in_path, "gbk", bheader, x, y, in_srs, out_srs, out_path, "gb18030") else: for row in rows: in_path_index = self.tbl_address.model().index( row, 0, QModelIndex()) in_layername_index = self.tbl_address.model().index( row, 1, QModelIndex()) in_srs_index = self.tbl_address.model().index( row, 2, QModelIndex()) out_srs_index = self.tbl_address.model().index( row, 3, QModelIndex()) out_path_index = self.tbl_address.model().index( row, 4, QModelIndex()) out_layername_index = self.tbl_address.model().index( row, 5, QModelIndex()) in_path = str(self.tbl_address.model().data( in_path_index, Qt.DisplayRole)).strip() in_layername = str(self.tbl_address.model().data( in_layername_index, Qt.DisplayRole)).strip() in_srs = str(self.tbl_address.model().data( in_srs_index, Qt.DisplayRole)).strip() out_srs = str(self.tbl_address.model().data( out_srs_index, Qt.DisplayRole)).strip() out_path = str(self.tbl_address.model().data( out_path_index, Qt.DisplayRole)).strip() out_layername = str(self.tbl_address.model().data( out_layername_index, Qt.DisplayRole)).strip() in_srs = list(srs_dict.keys())[list( srs_dict.values()).index(in_srs)] out_srs = list(srs_dict.keys())[list( srs_dict.values()).index(out_srs)] self.coordTransformThread.transform.emit( in_path, in_layername, in_srs, out_path, out_layername, out_srs) @Slot() def btn_saveMetaFile_clicked(self): fileName, fileType = QFileDialog.getSaveFileName( self, "请选择保存的参数文件", os.getcwd(), "json file(*.json)") if fileName == "": return datas = self.tbl_address.model().datas levels = self.tbl_address.model().levelData() logicRows = range(0, len(datas)) results = [] if self.rbtn_table.isChecked(): for logicRow in logicRows: key = datas[logicRow][0] row_data = { 'in_path': datas[logicRow][0], 'x_field': datas[logicRow][1], 'y_field': datas[logicRow][2], 'in_srs': datas[logicRow][3], 'out_srs': datas[logicRow][4], 'out_path': datas[logicRow][5], 'field_list': levels[key]['field_list'], 'srs_list': levels[key]['srs_list'] } results.append(row_data) else: for logicRow in logicRows: key = datas[logicRow][0] row_data = { 'in_path': datas[logicRow][0], 'in_layer': datas[logicRow][1], 'in_srs': datas[logicRow][2], 'out_srs': datas[logicRow][3], 'out_path': datas[logicRow][4], 'out_layer': datas[logicRow][5], 'layer_names': levels[key]['layer_names'], 'srs_list': levels[key]['srs_list'] } results.append(row_data) res = {'exports': results} try: if fileName != '': with open(fileName, 'w', encoding='UTF-8') as f: json.dump(res, f, ensure_ascii=False) except: log.error("文件存储路径错误,无法保存!", parent=self, dialog=True) @Slot() def rbtn_toggled(self): if self.rbtn_table.isChecked(): self.table_init_table_data() else: self.table_init_spatial_data() def table_init(self): self.tbl_address.setStyle(mTableStyle()) self.tbl_address.horizontalHeader().setStretchLastSection(True) self.tbl_address.verticalHeader().setDefaultSectionSize(20) self.tbl_address.verticalHeader().setSectionResizeMode( QHeaderView.Fixed) # 行高固定 color = self.palette().color(QPalette.Button) self.tbl_address.horizontalHeader().setStyleSheet( "QHeaderView::section {{ background-color: {}}}".format( color.name())) self.tbl_address.verticalHeader().setStyleSheet( "QHeaderView::section {{ background-color: {}}}".format( color.name())) self.tbl_address.setStyleSheet( "QTableCornerButton::section {{ color: {}; border: 1px solid; border-color: {}}}" .format(color.name(), color.name())) self.tbl_address.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tbl_address.setEditTriggers(QAbstractItemView.SelectedClicked | QAbstractItemView.DoubleClicked) # self.tbl_address.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tbl_address.DragDropMode(QAbstractItemView.InternalMove) self.tbl_address.setSelectionBehavior(QAbstractItemView.SelectRows | QAbstractItemView.SelectItems) self.tbl_address.setDefaultDropAction(Qt.MoveAction) self.tbl_address.horizontalHeader().setSectionsMovable(False) self.tbl_address.setDragEnabled(True) self.tbl_address.setAcceptDrops(True) self.in_path_no = 0 # 输入路径字段的序号 self.in_layername_no = 1 # 输入图层的序号 self.out_layername_no = 5 # 输出图层的序号 self.in_srs_no = 2 self.out_srs_no = 3 # 输出坐标系的序号 def table_init_spatial_data(self): self.model = TableModel() self.model.setHeaderData(0, Qt.Horizontal, "输入路径", Qt.DisplayRole) self.model.setHeaderData(1, Qt.Horizontal, "输入图层", Qt.DisplayRole) self.model.setHeaderData(2, Qt.Horizontal, "输入坐标系", Qt.DisplayRole) self.model.setHeaderData(3, Qt.Horizontal, "输出坐标系", Qt.DisplayRole) self.model.setHeaderData(4, Qt.Horizontal, "输出路径", Qt.DisplayRole) self.model.setHeaderData(5, Qt.Horizontal, "输出图层", Qt.DisplayRole) self.tbl_address.setModel(self.model) self.table_layout() layername_delegate = layernameDelegate(self, {'type': 'c'}) self.tbl_address.setItemDelegateForColumn(1, layername_delegate) srs_delegate = srsDelegate(self, srs_dict.values()) self.tbl_address.setItemDelegateForColumn(2, srs_delegate) self.tbl_address.setItemDelegateForColumn(3, srs_delegate) outputpath_delegate = outputPathDelegate(self, {'type': 'd'}) self.tbl_address.setItemDelegateForColumn(4, outputpath_delegate) def table_init_table_data(self): self.model = TableModel() self.model.setHeaderData(0, Qt.Horizontal, "输入文件", Qt.DisplayRole) self.model.setHeaderData(1, Qt.Horizontal, "x坐标", Qt.DisplayRole) self.model.setHeaderData(2, Qt.Horizontal, "y坐标", Qt.DisplayRole) self.model.setHeaderData(3, Qt.Horizontal, "输入坐标系", Qt.DisplayRole) self.model.setHeaderData(4, Qt.Horizontal, "输出坐标系", Qt.DisplayRole) self.model.setHeaderData(5, Qt.Horizontal, "输出文件", Qt.DisplayRole) self.tbl_address.setModel(self.model) self.table_layout() # layername_delegate = layernameDelegate(self, {'type': 'c'}) # self.tbl_address.setItemDelegateForColumn(1, layername_delegate) # srs_delegate = srsDelegate(self, srs_dict.values()) # self.tbl_address.setItemDelegateForColumn(2, srs_delegate) # self.tbl_address.setItemDelegateForColumn(3, srs_delegate) # outputpath_delegate = outputPathDelegate(self, {'type': 'd'}) # self.tbl_address.setItemDelegateForColumn(4, outputpath_delegate) @Slot() def btn_removeBtn_clicked(self): index_list = [] selModel = self.tbl_address.selectionModel() if selModel is None: return if len(selModel.selectedRows()) < 1: return for model_index in selModel.selectedRows(): index = QPersistentModelIndex(model_index) index_list.append(index) oldrow = index.row() for index in index_list: self.model.removeRows(index.row(), 1, 0) if self.model.rowCount(QModelIndex()) == 1: next_index = self.model.index(0, 0) else: next_index = self.model.index(oldrow, 0) # next_index = self.model.index(self.model.rowCount(QModelIndex()) - 1, 0) selModel.select( next_index, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self.tbl_address.setFocus() def return_url_and_layername(self, logicRow): in_path_index = self.tbl_address.model().index(logicRow, self.in_path_no) layername_index = self.tbl_address.model().index( logicRow, self.in_layername_no) url = self.tbl_address.model().data(in_path_index, Qt.DisplayRole) layername = self.tbl_address.model().data(layername_index, Qt.DisplayRole) return in_path_index, layername_index, url, layername def update_outlayername(self, index: QModelIndex, text): if index.column() == self.out_srs_no: in_layername_index = self.tbl_address.model().index( index.row(), self.in_layername_no) out_srs_index = self.tbl_address.model().index( index.row(), self.out_srs_no) out_layername_index = self.tbl_address.model().index( index.row(), self.out_layername_no) layer_name = self.tbl_address.model().data(in_layername_index, Qt.DisplayRole) # self.tbl_address.model().setData(out_srs_index, text) self.tbl_address.model().setData(out_layername_index, "{}_{}".format(layer_name, text))
class ESPMode(MicroPythonMode): """ Represents the functionality required for running MicroPython on ESP8266 """ name = _("ESP MicroPython") short_name = "esp" description = _("Write MicroPython on ESP8266/ESP32 boards.") icon = "esp" fs = None # The below list defines the supported devices, however, many # devices are using the exact same FTDI USB-interface, with vendor # ID 0x403 without reporting their own VID/PID # In some instances we can recognize the device not on VID/PID, # but on manufacturer ID, that's what the third column is for. # These more specific device specifications, should be listed # before the generic FTDI VID/PID's valid_boards = [ # VID , PID, Manufacturer string, Device name (0x1A86, 0x7523, None, "HL-340"), (0x10C4, 0xEA60, None, "CP210x"), (0x0403, 0x6001, "M5STACK Inc.", "M5Stack ESP32 device"), (0x0403, 0x6001, None, None), # FT232/FT245 (XinaBox CW01, CW02) (0x0403, 0x6010, None, None), # FT2232C/D/L/HL/Q (ESP-WROVER-KIT) (0x0403, 0x6011, None, None), # FT4232 (0x0403, 0x6014, None, None), # FT232H (0x0403, 0x6015, None, None), # FT X-Series (Sparkfun ESP32) (0x0403, 0x601C, None, None), # FT4222H ] def actions(self): """ Return an ordered list of actions provided by this module. An action is a name (also used to identify the icon) , description, and handler. """ buttons = [ { "name": "run", "display_name": _("Run"), "description": _( "Run your code directly on the ESP8266/ESP32" " via the REPL." ), "handler": self.run, "shortcut": "F5", }, { "name": "files", "display_name": _("Files"), "description": _("Access the file system on ESP8266/ESP32."), "handler": self.toggle_files, "shortcut": "F4", }, { "name": "repl", "display_name": _("REPL"), "description": _( "Use the REPL to live-code on the " "ESP8266/ESP32." ), "handler": self.toggle_repl, "shortcut": "Ctrl+Shift+I", }, ] if CHARTS: buttons.append( { "name": "plotter", "display_name": _("Plotter"), "description": _("Plot incoming REPL data."), "handler": self.toggle_plotter, "shortcut": "CTRL+Shift+P", } ) return buttons def api(self): """ Return a list of API specifications to be used by auto-suggest and call tips. """ return SHARED_APIS + ESP_APIS def toggle_repl(self, event): if self.fs is None: if self.repl: # Remove REPL super().toggle_repl(event) self.set_buttons(files=True) elif not (self.repl): # Add REPL super().toggle_repl(event) if self.repl: self.set_buttons(files=False) else: message = _("REPL and file system cannot work at the same time.") information = _( "The REPL and file system both use the same USB " "serial connection. Only one can be active " "at any time. Toggle the file system off and " "try again." ) self.view.show_message(message, information) def toggle_plotter(self, event): """ Check for the existence of the file pane before toggling plotter. """ if self.fs is None: super().toggle_plotter(event) if self.plotter: self.set_buttons(files=False) elif not (self.repl or self.plotter): self.set_buttons(files=True) else: message = _( "The plotter and file system cannot work at the same " "time." ) information = _( "The plotter and file system both use the same " "USB serial connection. Only one can be active " "at any time. Toggle the file system off and " "try again." ) self.view.show_message(message, information) def run(self): """ Takes the currently active tab, compiles the Python script therein into a hex file and flashes it all onto the connected device. """ """ if self.repl: message = _("Flashing cannot be performed at the same time as the " "REPL is active.") information = _("File transfers use the same " "USB serial connection as the REPL. Toggle the " "REPL off and try again.") self.view.show_message(message, information) return """ logger.info("Running script.") # Grab the Python script. tab = self.view.current_tab if tab is None: # There is no active text editor. message = _("Cannot run anything without any active editor tabs.") information = _( "Running transfers the content of the current tab" " onto the device. It seems like you don't have " " any tabs open." ) self.view.show_message(message, information) return python_script = tab.text().split("\n") if not self.repl: self.toggle_repl(None) if self.repl and self.connection: self.connection.send_commands(python_script) def toggle_files(self, event): """ Check for the existence of the REPL or plotter before toggling the file system navigator for the MicroPython device on or off. """ if self.repl: message = _( "File system cannot work at the same time as the " "REPL or plotter." ) information = _( "The file system and the REPL and plotter " "use the same USB serial connection. Toggle the " "REPL and plotter off and try again." ) self.view.show_message(message, information) else: if self.fs is None: self.add_fs() if self.fs: logger.info("Toggle filesystem on.") self.set_buttons(run=False, repl=False, plotter=False) else: self.remove_fs() logger.info("Toggle filesystem off.") self.set_buttons(run=True, repl=True, plotter=True) def add_fs(self): """ Add the file system navigator to the UI. """ # Find serial port the ESP8266/ESP32 is connected to device = self.editor.current_device # Check for MicroPython device if not device: message = _("Could not find an attached ESP8266/ESP32.") information = _( "Please make sure the device is plugged " "into this computer.\n\nThe device must " "have MicroPython flashed onto it before " "the file system will work.\n\n" "Finally, press the device's reset button " "and wait a few seconds before trying " "again." ) self.view.show_message(message, information) return self.file_manager_thread = QThread(self) self.file_manager = FileManager(device.port) self.file_manager.moveToThread(self.file_manager_thread) self.file_manager_thread.started.connect(self.file_manager.on_start) # Show directory of the current file in the left pane, if any, # otherwise show the default workspace_dir if self.view.current_tab and self.view.current_tab.path: path = os.path.dirname(os.path.abspath(self.view.current_tab.path)) else: path = self.workspace_dir() self.fs = self.view.add_filesystem( path, self.file_manager, _("ESP board") ) self.fs.set_message.connect(self.editor.show_status_message) self.fs.set_warning.connect(self.view.show_message) self.file_manager_thread.start() def remove_fs(self): """ Remove the file system navigator from the UI. """ self.view.remove_filesystem() self.file_manager = None self.file_manager_thread = None self.fs = None def on_data_flood(self): """ Ensure the Files button is active before the REPL is killed off when a data flood of the plotter is detected. """ self.set_buttons(files=True) super().on_data_flood() def deactivate(self): """ Invoked whenever the mode is deactivated. """ super().deactivate() if self.fs: self.remove_fs() def device_changed(self, new_device): """ Invoked when the user changes device. """ super().device_changed(new_device) if self.fs: self.remove_fs() self.add_fs()
def start(self): """Ensure thread is stopped, and start it """ self.stop() self._exit = False QThread.start(self)
class ApiJobQueue(QObject): """ ApiJobQueue is the queue manager of two FIFO priority queues that process jobs of type ApiJob. The queue manager starts the queues when a new auth token is provided to ensure jobs are able to make their requests. It stops the queues whenever a MetadataSyncJob, which runs in a continuous loop outside of the queue manager, encounters an ApiInaccessibleError and forces a logout from the Controller. """ # Signal that is emitted after a queue is paused. paused = pyqtSignal() def __init__(self, api_client: API, session_maker: scoped_session) -> None: super().__init__(None) self.main_thread = QThread() self.download_file_thread = QThread() self.main_queue = RunnableQueue(api_client, session_maker) self.download_file_queue = RunnableQueue(api_client, session_maker) self.main_queue.moveToThread(self.main_thread) self.download_file_queue.moveToThread(self.download_file_thread) self.main_thread.started.connect(self.main_queue.process) self.download_file_thread.started.connect(self.download_file_queue.process) self.main_queue.paused.connect(self.on_main_queue_paused) self.download_file_queue.paused.connect(self.on_file_download_queue_paused) def start(self, api_client: API) -> None: """ Start the queues whenever a new api token is provided. """ self.main_queue.api_client = api_client self.download_file_queue.api_client = api_client if not self.main_thread.isRunning(): self.main_thread.start() logger.debug("Started main queue") if not self.download_file_thread.isRunning(): self.download_file_thread.start() logger.debug("Started file download queue") def stop(self) -> None: """ Stop the queues. """ if self.main_thread.isRunning(): self.main_thread.quit() logger.debug("Stopped main queue") if self.download_file_thread.isRunning(): self.download_file_thread.quit() logger.debug("Stopped file download queue") @pyqtSlot() def on_main_queue_paused(self) -> None: """ Emit the paused signal if the main queue has been paused. """ logger.debug("Paused main queue") self.paused.emit() @pyqtSlot() def on_file_download_queue_paused(self) -> None: """ Emit the paused signal if the file download queue has been paused. """ logger.debug("Paused file download queue") self.paused.emit() def resume_queues(self) -> None: """ Emit the resume signal to the queues if they are running. """ if self.main_thread.isRunning(): logger.debug("Resuming main queue") self.main_queue.resume.emit() if self.download_file_thread.isRunning(): logger.debug("Resuming download queue") self.download_file_queue.resume.emit() @pyqtSlot(object) def enqueue(self, job: ApiJob) -> None: """ Enqueue the supplied job if the queues are running. """ if not self.main_thread.isRunning() or not self.download_file_thread.isRunning(): logger.debug("Not adding job before queues have been started.") return if isinstance(job, FileDownloadJob): self.download_file_queue.add_job(job) else: self.main_queue.add_job(job)
class MainWindow(QWidget): def __init__(self, parent, masternode_list, imgDir): super(QWidget, self).__init__(parent) self.parent = parent self.imgDir = imgDir self.runInThread = ThreadFuns.runInThread ###-- Masternode list self.masternode_list = masternode_list ###-- Create clients and statuses self.hwdevice = None self.hwStatus = 0 self.hwStatusMess = "Not Connected" self.rpcClient = None self.rpcConnected = False self.rpcStatusMess = "Not Connected" self.isBlockchainSynced = False ###-- Load icons & images self.loadIcons() ###-- Create main layout self.layout = QVBoxLayout() self.header = GuiHeader(self) self.initConsole() self.layout.addWidget(self.header) ###-- Create RPC Whatchdog self.rpc_watchdogThread = QThread() self.myRpcWd = RpcWatchdog(self) self.myRpcWd.moveToThread(self.rpc_watchdogThread) self.rpc_watchdogThread.started.connect(self.myRpcWd.run) ###-- Create Queues and redirect stdout and stderr self.queue = Queue() self.queue2 = Queue() sys.stdout = WriteStream(self.queue) sys.stderr = WriteStream(self.queue2) ###-- Init last logs logFile = open(log_File, 'w+') timestamp = strftime('%Y-%m-%d %H:%M:%S', gmtime(now())) log_line = '<b style="color: blue">{}</b><br>'.format( 'STARTING SPMT at ' + timestamp) logFile.write(log_line) logFile.close() ###-- Create the thread to update console log for stdout self.consoleLogThread = QThread() self.myWSReceiver = WriteStreamReceiver(self.queue) self.myWSReceiver.mysignal.connect(self.append_to_console) self.myWSReceiver.moveToThread(self.consoleLogThread) self.consoleLogThread.started.connect(self.myWSReceiver.run) self.consoleLogThread.start() printDbg("Console Log thread started") ###-- Create the thread to update console log for stderr self.consoleLogThread2 = QThread() self.myWSReceiver2 = WriteStreamReceiver(self.queue2) self.myWSReceiver2.mysignal.connect(self.append_to_console) self.myWSReceiver2.moveToThread(self.consoleLogThread2) self.consoleLogThread2.started.connect(self.myWSReceiver2.run) self.consoleLogThread2.start() printDbg("Console Log thread 2 started") ###-- Initialize tabs self.tabs = QTabWidget() self.t_main = TabMain(self) self.t_mnconf = TabMNConf(self) self.t_rewards = TabRewards(self) self.t_governance = TabGovernance(self) ###-- Add tabs self.tabs.addTab(self.tabMain, "Masternode Control") #self.tabs.addTab(self.tabMNConf, "MN Configuration") self.tabs.addTab(self.tabRewards, "Transfer Rewards") self.tabs.addTab(self.tabGovernance, "Governance") ###-- Connect change action self.tabs.currentChanged.connect(lambda: self.onTabChange()) ###-- Draw Tabs self.splitter = QSplitter(Qt.Vertical) ###-- Add tabs and console to Layout self.splitter.addWidget(self.tabs) self.splitter.addWidget(self.console) self.splitter.setStretchFactor(0, 0) self.splitter.setStretchFactor(1, 1) self.splitter.setSizes(self.parent.cache.get("splitter_sizes")) self.layout.addWidget(self.splitter) ###-- Set Layout self.setLayout(self.layout) ###-- Let's go self.mnode_to_change = None printOK("Hello! Welcome to " + parent.title) ###-- Hide console if it was previously hidden if self.parent.cache.get("console_hidden"): self.onToggleConsole() ##-- Check version self.onCheckVersion() ##-- init Api Client self.apiClient = ApiClient() @pyqtSlot(str) def append_to_console(self, text): self.consoleArea.moveCursor(QTextCursor.End) self.consoleArea.insertHtml(text) def initConsole(self): self.console = QGroupBox() self.console.setTitle("Console Log") layout = QVBoxLayout() self.btn_consoleToggle = QPushButton('Hide') self.btn_consoleToggle.setToolTip('Show/Hide console') self.btn_consoleToggle.clicked.connect(lambda: self.onToggleConsole()) consoleHeader = QHBoxLayout() consoleHeader.addWidget(self.btn_consoleToggle) self.consoleSaveButton = QPushButton('Save') self.consoleSaveButton.clicked.connect(lambda: self.onSaveConsole()) consoleHeader.addWidget(self.consoleSaveButton) self.btn_consoleClean = QPushButton('Clean') self.btn_consoleClean.setToolTip('Clean console log area') self.btn_consoleClean.clicked.connect(lambda: self.onCleanConsole()) consoleHeader.addWidget(self.btn_consoleClean) consoleHeader.addStretch(1) self.versionLabel = QLabel("--") self.versionLabel.setOpenExternalLinks(True) consoleHeader.addWidget(self.versionLabel) self.btn_checkVersion = QPushButton("Check SPMT version") self.btn_checkVersion.setToolTip("Check latest stable release of SPMT") self.btn_checkVersion.clicked.connect(lambda: self.onCheckVersion()) consoleHeader.addWidget(self.btn_checkVersion) layout.addLayout(consoleHeader) self.consoleArea = QTextEdit() almostBlack = QColor(40, 40, 40) palette = QPalette() palette.setColor(QPalette.Base, almostBlack) green = QColor(0, 255, 0) palette.setColor(QPalette.Text, green) self.consoleArea.setPalette(palette) layout.addWidget(self.consoleArea) self.console.setLayout(layout) def isMasternodeInList(self, mn_alias): return (mn_alias in [x['name'] for x in self.masternode_list]) def loadIcons(self): # Load Icons self.ledPurpleH_icon = QPixmap( os.path.join(self.imgDir, 'icon_purpleLedH.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledGrayH_icon = QPixmap( os.path.join(self.imgDir, 'icon_grayLedH.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledHalfPurpleH_icon = QPixmap( os.path.join(self.imgDir, 'icon_halfPurpleLedH.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledRedV_icon = QPixmap( os.path.join(self.imgDir, 'icon_redLedV.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledGrayV_icon = QPixmap( os.path.join(self.imgDir, 'icon_grayLedV.png')).scaledToHeight( 17, Qt.SmoothTransformation) self.ledGreenV_icon = QPixmap( os.path.join(self.imgDir, 'icon_greenLedV.png')).scaledToHeight( 17, Qt.SmoothTransformation) def loadMNConf(self, fileName): hot_masternodes = loadMNConfFile(fileName) if hot_masternodes == None: messText = "Unable to load data from file '%s'" % fileName self.myPopUp2(QMessageBox.Warning, "SPMT - warning", messText) else: # Append new masternodes to list new_masternodes = [] skip_masternodes = [] for x in hot_masternodes: if not self.isMasternodeInList(x['name']): self.masternode_list.append(x) new_masternodes.append(x) else: skip_masternodes.append(x) # Show new list for new_masternode in new_masternodes: name = new_masternode['name'] self.tabMain.insert_mn_list(name, new_masternode['ip'], new_masternode['port'], None, isHardware=False) self.tabMain.btn_remove[name].clicked.connect( lambda: self.t_main.onRemoveMN()) # print number of nodes added new_nodes = len(new_masternodes) final_message = "" if new_nodes == 0: final_message = "No External Masternode " elif new_nodes == 1: final_message = "1 External Masternode " else: final_message = "%d External Masternodes " % new_nodes final_message += "added to the list. " if new_nodes > 0: final_message += str([x['name'] for x in new_masternodes]) + ". " if len(skip_masternodes) > 0: final_message += "Following entries skipped due to duplicate names:" final_message += str([x['name'] for x in skip_masternodes]) + ". " printDbg(final_message) if new_nodes > 0: # update files printDbg("saving MN configuration file") writeToFile(self.masternode_list, masternodes_File) printDbg("saved") # Clear voting masternodes configuration and update cache self.t_governance.clear() def myPopUp(self, messType, messTitle, messText, defaultButton=QMessageBox.No): mess = QMessageBox(messType, messTitle, messText, defaultButton, parent=self) mess.setStandardButtons(QMessageBox.Yes | QMessageBox.No) mess.setDefaultButton(defaultButton) return mess.exec_() def myPopUp2(self, messType, messTitle, messText, singleButton=QMessageBox.Ok): mess = QMessageBox(messType, messTitle, messText, singleButton, parent=self) mess.setStandardButtons(singleButton | singleButton) return mess.exec_() @pyqtSlot() def onCheckHw(self): printDbg("Checking for HW device...") self.updateHWstatus(None) self.showHWstatus() @pyqtSlot() def onCheckRpc(self): printDbg("Checking RPC server...") self.runInThread(self.updateRPCstatus, (), self.showRPCstatus) @pyqtSlot() def onCheckVersion(self): printDbg("Checking SPMT version...") self.versionLabel.setText("--") self.runInThread(self.checkVersion, (), self.updateVersion) def checkVersion(self, ctrl): local_version = self.parent.version['number'].split('.') remote_version = getRemoteSPMTversion().split('.') if (remote_version[0] > local_version[0]) or \ (remote_version[0] == local_version[0] and remote_version[1] > local_version[1]) or \ (remote_version[0] == local_version[0] and remote_version[1] == local_version[1] and remote_version[2] > local_version[2]): self.versionMess = '<b style="color:red">New Version Available:</b> %s.%s.%s ' % ( remote_version[0], remote_version[1], remote_version[2]) self.versionMess += '(<a href="https://github.com/PIVX-Project/PIVX-SPMT/releases/">download</a>)' else: self.versionMess = "You have the latest version of SPMT" def updateVersion(self): if self.versionMess is not None: self.versionLabel.setText(self.versionMess) @pyqtSlot() def onCleanConsole(self): self.consoleArea.clear() @pyqtSlot() def onSaveConsole(self): timestamp = strftime('%Y-%m-%d_%H-%M-%S', gmtime(now())) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName( self, "Save Logs to file", "SPMT_Logs_%s.txt" % timestamp, "All Files (*);; Text Files (*.txt)", options=options) try: if fileName: printOK("Saving logs to %s" % fileName) log_file = open(fileName, 'w+') log_text = self.consoleArea.toPlainText() log_file.write(log_text) log_file.close() except Exception as e: err_msg = "error writing Log file" printException(getCallerName(), getFunctionName(), err_msg, e.args) @pyqtSlot() def onTabChange(self): # reload (and re-sort)masternode list in tabs if self.tabs.currentWidget() == self.tabRewards: # reload last used address self.tabRewards.destinationLine.setText( self.parent.cache.get("lastAddress")) # get new order mnOrder = {} mnList = self.tabMain.myList for i in range(mnList.count()): mnName = mnList.itemWidget(mnList.item(i)).alias mnOrder[mnName] = i self.parent.cache['mnList_order'] = mnOrder # Sort masternode list (by alias if no previous order set) if self.parent.cache.get('mnList_order') != {}: self.masternode_list.sort(key=self.parent.extract_order) self.t_rewards.loadMnSelect() self.t_rewards.selectedRewards = None # reload proposal and voting masternode list if self.tabs.currentWidget() == self.tabGovernance: self.t_governance.onRefreshProposals() self.t_governance.updateSelectedMNlabel() @pyqtSlot() def onToggleConsole(self): if self.btn_consoleToggle.text() == 'Hide': self.btn_consoleToggle.setText('Show') self.consoleArea.hide() self.console.setMinimumHeight(70) self.console.setMaximumHeight(70) else: self.console.setMinimumHeight(70) self.console.setMaximumHeight(starting_height) self.btn_consoleToggle.setText('Hide') self.consoleArea.show() def showHWstatus(self): self.updateHWleds() self.myPopUp2(QMessageBox.Information, 'SPMT - hw check', "%s" % self.hwStatusMess, QMessageBox.Ok) def showRPCstatus(self): self.updateRPCled() self.myPopUp2(QMessageBox.Information, 'SPMT - rpc check', "%s" % self.rpcStatusMess, QMessageBox.Ok) def updateHWleds(self): if self.hwStatus == 1: self.header.hwLed.setPixmap(self.ledHalfPurpleH_icon) elif self.hwStatus == 2: self.header.hwLed.setPixmap(self.ledPurpleH_icon) else: self.header.hwLed.setPixmap(self.ledGrayH_icon) self.header.hwLed.setToolTip(self.hwStatusMess) def updateHWstatus(self, ctrl): if self.hwdevice is not None: if hasattr(self.hwdevice, 'dongle'): self.hwdevice.dongle.close() self.hwdevice = HWdevice() statusCode, statusMess = self.hwdevice.getStatus() printDbg("mess: %s" % statusMess) if statusCode != 2: # If is not connected try again try: if hasattr(self.hwdevice, 'dongle'): self.hwdevice.dongle.close() self.hwdevice = HWdevice() self.hwdevice.initDevice() statusCode, statusMess = self.hwdevice.getStatus() except Exception as e: err_msg = "error in checkHw" printException(getCallerName(), getFunctionName(), err_msg, e.args) self.hwStatus = statusCode self.hwStatusMess = statusMess # if all is good connect the signals if statusCode == 2: self.hwdevice.sigTxdone.connect(self.t_rewards.FinishSend) self.hwdevice.sigTxabort.connect(self.t_rewards.onCancel) self.hwdevice.tx_progress.connect( self.t_rewards.updateProgressPercent) def updateLastBlockLabel(self): text = '--' if self.rpcLastBlock == 1: text = "Loading block index..." elif self.rpcLastBlock > 0 and self.rpcConnected: text = str(self.rpcLastBlock) text += " (" if not self.isBlockchainSynced: text += "Synchronizing" else: text += "Synced" text += ")" self.header.lastBlockLabel.setText(text) def updateRPCled(self): if self.rpcConnected: self.header.rpcLed.setPixmap(self.ledPurpleH_icon) else: if self.rpcLastBlock == 1: self.header.rpcLed.setPixmap(self.ledHalfPurpleH_icon) else: self.header.rpcLed.setPixmap(self.ledGrayH_icon) self.header.rpcLed.setToolTip(self.rpcStatusMess) self.updateLastBlockLabel() def updateRPCstatus(self, ctrl): if self.rpcClient is None: self.rpcClient = RpcClient() status, statusMess, lastBlock = self.rpcClient.getStatus() self.rpcConnected = status self.rpcLastBlock = lastBlock self.rpcStatusMess = statusMess self.isBlockchainSynced = self.rpcClient.isBlockchainSynced() # If is not connected try again if not status: self.rpcClient = RpcClient()
class Ui_MainWindow(object): def __init__(self, parent = None): self.parent = parent self.pressEnterTwiceLineEdit = False self.uiDialog = Ui_Dialog(self) # từ dialog self.word_buttons = [] # tham chiếu parent để truyền qua lại dữ liệu #self.uiDialog.setupUi(self.window) # kế thừa từ QDialog # na ná như bên lớp applycation trong learnenglish.py self.translated_word = '' self.scraping = Scraping(0) self.st2_waiting = False self.st2_status = True def newKeyPressEvent(self, event): if event.key() == Qt.Key_Q: self.google_trans(self.lineEdit.text(), 'e2v') self.getImageForWord(self.lineEdit.text()) if event.key() == Qt.Key_A: self.google_trans(self.lineEdit.text(), 'v2e') self.getImageForWord(self.lineEdit.text()) if event.key() == Qt.Key_W and self.toolButton_trans2.isEnabled(): self.google_trans(self.parent.GetLang('ENG'), 'e2v') self.getImageForWord(self.parent.GetLang('ENG')) if event.key() == Qt.Key_S: if self.pressEnterTwiceLineEdit or self.parent.listen: self.parent.TextToSpeech(self.parent.GetLang('ENG')) if event.key() == Qt.Key_D and self.parent.s2t and not self.st2_waiting: self.speak() def OpenVideo(self): self.uiVideo = VideoShow(self) self.uiVideo.show() def OpenCongrat(self): self.congratwindow = Congrat_Window(self) self.congratwindow.show() def OpenWindow(self, lang): def open(): if self.parent.listen: self.parent.TextToSpeech(self.parent.GetLang('ENG')) else: if lang == 'VIE': self.uiDialog.lineEdit.setText(self.parent.GetLang('VIE')) self.uiDialog.lang_selected = 'VIE' else: self.uiDialog.lineEdit.setText(self.parent.GetLang('ENG')) self.uiDialog.lang_selected = 'ENG' # gán text của lable vào text của textEdit để chỉnh sửa !!! # Giữ label hiện tại để thay đổi trong dialog self.uiDialog.show() # sau khi khởi tạo đối tượng QMainWindow() là một dialog, ta sẽ show nó return open # dùng hàm open lồng trong OpenWindow để mục đích cuối cùng là trả về địa chỉ hàm # hợp lệ cho event # Nếu không dùng hàm lồng nhau trả về địa chỉ hàm nó sẽ ngầm hiểu label là kiểu bool và ko có thuộc tính text !!! def addWordButtonToLayoutEng(self, text): text = re.sub(r"[^\w\-']", ' ', text).strip() text = re.sub(r'\s+', ' ', text) layout = self.hz_vocabulary_eng # del widgets from hz_vocabulary_eng self.parent.deleteWidgetsInLayout(layout) self.word_buttons = [] # add widgets to hz_vocabulary_eng list_word = text.split(' ') for i, word in enumerate(list_word): word_button = QtWidgets.QPushButton(self.centralwidget) word_button.setObjectName('word_button%s'%i) # fix callable objects in loop slot = functools.partial(self.translate, word) word_button.clicked.connect(slot) self.word_buttons.append(word_button) word_button.setText(word) font = QtGui.QFont() font.setPointSize(12) font.setFamily("Time News Roman") word_button.setFont(font) layout.addWidget(word_button) def google_trans(self, sentence, mode): self.translated_word = '' if sentence.replace(' ', '') == '': return trsl, spelling, word_type_and_content, hint = self.scraping.google_translate(sentence, mode) self.text_browser.clear() self.text_browser.show() self.text_browser.append(self.parent.setStyleTextHTML(trsl, color= '#005500', size = '14')) if hint: self.text_browser.append(self.parent.setStyleTextHTML(hint, color= '#ff0000', size = '10')) if spelling: self.text_browser.append(self.parent.setStyleTextHTML(spelling, color= '#ffaa00', size = '10')) if word_type_and_content[0] and word_type_and_content[1]: viet_word = True for line in word_type_and_content[1]: if line in word_type_and_content[0]: self.text_browser.append(self.parent.setStyleTextHTML(line, color= '#005500', size = '12', weight= '500')) elif viet_word: self.text_browser.append(self.parent.setStyleTextHTML(line, color= '#5500ff', size = '10', style= 'italic')) viet_word = False else: self.text_browser.append(self.parent.setStyleTextHTML(line, color= '#000000', size = '10')) viet_word = True self.text_browser.moveCursor(QtGui.QTextCursor.Start) def speak(self): self.lineEdit.setText('') self.lineEdit.setPlaceholderText('Speech to text >>> Listening ... <<<') self.scraping.speech_to_text(self) def translate(self, word): # show text_browser and hz_image_eng if self.translated_word == word: self.getImageForWord(word) return self.translated_word = word self.text_browser.show() self.parent.EnableWidgetsInLayout(self.hz_image_eng, True) self.getImageForWord(word) self.text_browser.clear() word = re.sub(r"[^-'\w]", ' ', word).lower() word = word.replace('_', '-') if word == '': return page = requests.get("https://dict.laban.vn/find?type=1&query=%s"%(word)) soup = BeautifulSoup(page.content, 'html.parser') spelling = soup.find('h2', {'class':'fl'}).text content = soup.find('div',{'id':'content_selectable', 'class':'content'}) if content == None: return word_type = content.find_all('div', {'class':'bg-grey bold font-large m-top20'}) word_type = [wt.find('span').text for wt in word_type] content = [w.replace('\xa0', ' ') for w in content.text.split('\n')][1:len(content)-1] self.text_browser.append(self.parent.setStyleTextHTML(word, color= '#005500', size = '17')) self.text_browser.append(self.parent.setStyleTextHTML(spelling)) for i, wt in enumerate(word_type): self.text_browser.append(self.parent.addTagScroll(wt, 'href', '#', i, 8, 'underline', '0000ff')) text = '' count = 0 for line in content: if line in word_type: self.text_browser.append(self.parent.addTagScroll('\n' + line, 'name', '', count, 14, 'none', 'ffaa00')) count+=1 else: temp = word[:int(len(word)/2)] temp = word if len(temp)<=2 else temp if line.lower().find(temp)!=-1: text = self.parent.setStyleTextHTML(line, color= '#aa55ff', style='italic', weight = '600') else: text = self.parent.setStyleTextHTML(line) self.text_browser.append(text) self.text_browser.moveCursor(QtGui.QTextCursor.Start) def getImageForWord(self, word): try: self.parent.deleteWidgetsInLayout(self.hz_image_eng) page = requests.get("https://vn.images.search.yahoo.com/search/images?fr=sfp&p=" + word + "&fr2=p%3As%2Cv%3Ai&.bcrumb=uKIyJ5aGvwE&save=0") soup = BeautifulSoup(page.content, 'html.parser') images = soup.find_all('img') images = [img.attrs['src'] for img in images if 'src' in img.attrs] if len(images)<4: max = len(images) else: max = 4 for _ in range(max): i = random.randint(0,len(images)-1) data = urllib.request.urlopen(images[i]).read() image = QtGui.QImage() image.loadFromData(data) lbl = QtWidgets.QLabel(self.centralwidget) lbl.setPixmap(QtGui.QPixmap(image).scaled(200,200)) #lbl.setFixedSize(QtCore.QSize(200,200)) self.hz_image_eng.addWidget(lbl) del images[i] except: pass def show(self, result): self.toolButton_3.setEnabled(True) self.toolButton_4.setEnabled(True) self.toolButton_trans2.setEnabled(True) self.pressEnterTwiceLineEdit = True # so khớp 2 kết quả # sửa sai lần hai cho các từ giống như như 's ~ is, 're ~ are ... if self.parent.checkSentenceEncore: s1 = self.parent.ConvertAcronyms(result) s2 = self.parent.ConvertAcronyms(self.lineEdit.text()) s1 = self.parent.FillCharectInSentence(s1) s2 = self.parent.FillCharectInSentence(s2) else: s1 = self.parent.FillCharectInSentence(result) s2 = self.parent.FillCharectInSentence(self.lineEdit.text()) if s1 == s2: self.parent.BellRing(1) if self.numTrueSentence >= 0: self.progressBar.setProperty('value', self.progressBar.value() + 5) self.parent.DictDB.update_score(self.parent.DictDB.get_score(result) + 1, result) self.numTrueSentence+=1 self.progressBar.setFormat('%s/20'%(str(self.numTrueSentence))) self.addWordButtonToLayoutEng(result) if self.numTrueSentence == 20: self.OpenCongrat() self.parent.BellRing(3) else: if not self.parent.checkSentenceEncore: self.parent.checkSentenceEncore = True self.show(result) return self.parent.BellRing(2) if not self.parent.s2t: self.progressBar.setProperty('value', self.progressBar.value() - 5) score = self.parent.DictDB.get_score(result) score = score - 1 if score >-5 else score self.parent.DictDB.update_score(score, result) self.numTrueSentence-=1 self.progressBar.setFormat('%s/20'%(str(self.numTrueSentence))) s1 = self.lineEdit.text().split(' ') s2 = self.parent.FillCharectInSentence(result).split(' ') pos = [-1,-1] if len(s1) <= len(s2): pos = self.parent.FindPosErrorWords(s1, s2) else: pos = self.parent.FindPosErrorWords(s2, s1) self.addWordButtonToLayoutEng(result) self.parent.PaintColorWordButtons(self.word_buttons, pos[0], pos[1]) self.parent.checkSentenceEncore = False def showResult(self): # Không show result nếu chưa nhập gì cả hoặc chỉ toàn là khoảng trắng string = self.lineEdit.text().replace(' ', '') if not self.pressEnterTwiceLineEdit: # lần enter thứ nhất if string == '': # chuỗi rỗng QtWidgets.QMessageBox.information(None, 'WARNING', 'Please type corectly english translation !!!') else: if self.parent.listen: self.label.setText(self.parent.GetLang('VIE')) self.parent.SetButtonIcon(self.toolButton, 'edit') self.parent.listen = False self.toolButton_3.show() result = self.parent.GetLang('ENG') # Thread(target= self.show(result)).start() # Thread(target= self.parent.TextToSpeech(result)).start() self.show(result) self.parent.TextToSpeech(result) else: # lần enter thứ 2 if self.parent.s2t: self.lineEdit.setPlaceholderText('Type english translation') self.parent.s2t = False self.pressEnterTwiceLineEdit = False self.lineEdit.setText('') self.parent.EnableWidgetsInLayout(self.hz_image_eng, False) self.text_browser.hide() self.parent.LoadEngSentence() if self.st2_status: self.parallel_s2t_threading() # self.label_2.setStyleSheet('color: black;') def parallel_s2t_threading(self): # self.worker = Worker() self.thread = QThread(self.parent) self.thread.started.connect(self.speech2text_shot) # <--new line, make sure work starts. self.thread.start() def speech2text_shot(self): if np.random.randint(3) == 0 and self.parent.firstStart: self.lineEdit.setReadOnly(True) self.parent.s2t = True self.speak() else: self.lineEdit.setReadOnly(False) def DeleteCouple(self, MainWindow): def delete(): reply = QtWidgets.QMessageBox.question(MainWindow, 'Message', "Do you want to delete this couple?", QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.parent.DictDB.deleteById(self.parent.rowid) self.parent.LoadEngSentence() return delete def setStatusS2T(self): icon = QtGui.QIcon() if self.st2_status: icon.addPixmap(QtGui.QPixmap("assets/speech_to_text_off.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_6.setIcon(icon) self.st2_status = False self.lineEdit.setReadOnly(False) else: icon.addPixmap(QtGui.QPixmap("assets/speech_to_text.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_6.setIcon(icon) self.st2_status = True if self.parent.s2t: self.lineEdit.setReadOnly(True) def setupUi(self, MainWindow): MainWindow.keyPressEvent = self.newKeyPressEvent MainWindow.setObjectName("MainWindow") MainWindow.resize(603, 318) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") # tạo layout chứa label và button self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName('horizontalLayout') self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName('horizontalLayout_2') self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName('horizontalLayout_3') self.horizontalLayout_4 = QtWidgets.QHBoxLayout() self.horizontalLayout_4.setObjectName('horizontalLayout_3') self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setObjectName('verticalLayout_2') self.label = QtWidgets.QLabel(self.centralwidget) self.label.setWordWrap(True) self.horizontalLayout.addWidget(self.label) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout.addLayout(self.horizontalLayout_2) self.verticalLayout.addLayout(self.horizontalLayout_3) self.verticalLayout.addLayout(self.horizontalLayout_4) self.text_browser = QtWidgets.QTextBrowser(self.centralwidget) self.text_browser.hide() self.verticalLayout.addWidget(self.text_browser) # self.text_browser.setText('ok baby\nyou are the one for me\n') # self.text_browser.setHtml("""My image :<br /><p style="text-align:center;"><img src="assets/enter.png" height="100" width="100"/> # <img src="assets/speak.png" height="100" width="100"/> # """) # self.text_browser.setFixedHeight(250) fontLabel1 = QtGui.QFont() fontLabel1.setPointSize(14) fontLabel1.setBold(True) fontLabel2 = QtGui.QFont() fontLabel2.setPointSize(14) fontLabel2.setBold(True) fontLineEdit = QtGui.QFont() fontLineEdit.setFamily("Times New Roman") fontLineEdit.setPointSize(16) fontLineEdit.setBold(True) fontLineEdit.setItalic(False) fontLineEdit.setWeight(75) self.label.setFont(fontLabel1) self.label.setObjectName("label") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("assets/edit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton = QtWidgets.QToolButton(self.centralwidget) self.toolButton.setIcon(icon) self.toolButton.setIconSize(QtCore.QSize(20,20)) self.toolButton.setObjectName("toolButton") self.horizontalLayout.addWidget(self.toolButton) # Truyền tham số vào hàm sự kiện !!! # Vì sự kiện cần một địa chỉ hàm ko phải là hàm thực thi # nên sẽ dùng hàm lồng nhau self.toolButton.clicked.connect(self.OpenWindow('VIE')) self.lineEdit = QtWidgets.QLineEdit(self.centralwidget) # self.lineEdit.keyPressEvent = self.newKeyPressEvent # self.lineEdit.keyReleaseEvent = self.newKeyReleaseEvent self.horizontalLayout_2.addWidget(self.lineEdit) self.lineEdit.setFont(fontLineEdit) self.lineEdit.setAutoFillBackground(False) self.lineEdit.setText('') self.lineEdit.setDragEnabled(False) self.lineEdit.setClearButtonEnabled(False) self.lineEdit.setStyleSheet("color: green;") self.lineEdit.setObjectName("lineEdit") # ẩn chữ trong lineEdit self.lineEdit.setPlaceholderText('Type english translation') # Event lineEdit self.lineEdit.returnPressed.connect(self.showResult) # Hiện con trỏ văn bản khi mở app self.lineEdit.setFocus(True) icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap("assets/enter.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_2 = QtWidgets.QToolButton(self.centralwidget) self.toolButton_2.setIcon(icon2) self.toolButton_2.setIconSize(QtCore.QSize(20,20)) self.toolButton_2.setObjectName("toolButton_2") icon_trans = QtGui.QIcon() icon_trans.addPixmap(QtGui.QPixmap("assets/google_translate_icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_trans = QtWidgets.QToolButton(self.centralwidget) self.toolButton_trans.setIcon(icon_trans) self.toolButton_trans.setIconSize(QtCore.QSize(20,20)) self.toolButton_trans.setObjectName('toolButton_trans') self.toolButton_trans.clicked.connect(lambda: self.google_trans(self.lineEdit.text(), 'e2v')) self.verLayA = QtWidgets.QVBoxLayout() self.verLayA.setObjectName('verLayA') self.verLayA.addWidget(self.toolButton_2) self.verLayA.addWidget(self.toolButton_trans) self.horizontalLayout_2.addLayout(self.verLayA) #Event toolButton_2 self.toolButton_2.clicked.connect(self.showResult) # ProgressBar self.progressBar = QtWidgets.QProgressBar(self.centralwidget) self.progressBar.setProperty("value", 0) self.progressBar.setObjectName("progressBar") # set text progressbar self.numTrueSentence = 0 self.progressBar.setTextVisible(True) self.progressBar.setFormat('0/20') icon3 = QtGui.QIcon() icon3.addPixmap(QtGui.QPixmap("assets/speak.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_3 = QtWidgets.QToolButton(self.centralwidget) self.toolButton_3.setIcon(icon3) self.toolButton_3.setIconSize(QtCore.QSize(20,20)) self.toolButton_3.setObjectName("toolButton_3") self.verticalLayout_2.addWidget(self.toolButton_3) icon4 = QtGui.QIcon() icon4.addPixmap(QtGui.QPixmap("assets/edit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_4 = QtWidgets.QToolButton(self.centralwidget) self.toolButton_4.setIcon(icon4) self.toolButton_4.setIconSize(QtCore.QSize(20,20)) self.toolButton_4.setObjectName('toolButton_4') self.toolButton_4.clicked.connect(self.OpenWindow('ENG')) self.toolButton_trans2 = QtWidgets.QToolButton(self.centralwidget) self.toolButton_trans2.setIcon(icon_trans) self.toolButton_trans2.setIconSize(QtCore.QSize(20,20)) self.toolButton_trans2.setObjectName('toolButton_trans2') self.toolButton_trans2.clicked.connect(lambda: self.google_trans(self.parent.GetLang('ENG'), 'e2v')) self.verticalLayout_2.addWidget(self.toolButton_4) self.verticalLayout_2.addWidget(self.toolButton_trans2) self.verticalLayout_2.setAlignment(Qt.AlignRight) self.hz_vocabulary_eng = QtWidgets.QHBoxLayout() self.hz_vocabulary_eng.setObjectName('horizontalLayout_eng') self.hz_vocabulary_eng.setAlignment(Qt.AlignLeft) self.horizontalLayout_3.addLayout(self.hz_vocabulary_eng) self.horizontalLayout_3.addLayout(self.verticalLayout_2) icon5 = QtGui.QIcon() icon5.addPixmap(QtGui.QPixmap("assets/delete.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_5 = QtWidgets.QToolButton(self.centralwidget) self.toolButton_5.setIcon(icon5) self.toolButton_5.setIconSize(QtCore.QSize(16,16)) self.toolButton_5.setObjectName('toolButton_5') self.toolButton_5.clicked.connect(self.DeleteCouple(MainWindow)) icon6 = QtGui.QIcon() icon6.addPixmap(QtGui.QPixmap("assets/speech_to_text.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.toolButton_6 = QtWidgets.QToolButton(self.centralwidget) self.toolButton_6.setIcon(icon6) self.toolButton_6.setIconSize(QtCore.QSize(16,16)) self.toolButton_6.setObjectName('toolButton_6') self.toolButton_6.clicked.connect(self.setStatusS2T) self.horizontalLayout_4.addWidget(self.toolButton_5) self.horizontalLayout_4.addWidget(self.toolButton_6) self.horizontalLayout_4.setAlignment(Qt.AlignLeft) self.hz_image_eng = QtWidgets.QHBoxLayout() self.hz_image_eng.setObjectName('horizontalLayoutImageEng') self.verticalLayout.addLayout(self.hz_image_eng) self.verticalLayout.addWidget(self.progressBar) self.toolButton_3.clicked.connect(lambda: self.parent.TextToSpeech(self.parent.GetLang('ENG'))) MainWindow.setCentralWidget(self.centralwidget) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 603, 21)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") MainWindow.setMenuBar(self.menubar) self.actionReload = QtWidgets.QAction(MainWindow) self.actionReload.setObjectName("actionReload") self.actionSave_sentence_source = QtWidgets.QAction(MainWindow) self.actionSave_sentence_source.setObjectName("actionSave_sentence_source") self.menuFile.addAction(self.actionReload) # event actionReload self.actionReload.triggered.connect(self.parent.Reload) self.menuFile.addAction(self.actionSave_sentence_source) self.menubar.addAction(self.menuFile.menuAction()) # Menu video self.actionVideo = QtWidgets.QAction(MainWindow) self.actionVideo.setObjectName('actionVideo') self.menubar.addAction(self.actionVideo) self.actionVideo.triggered.connect(self.OpenVideo) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "STUDY ENGLISH")) self.label.setText(_translate("MainWindow", "Vietnamese Sentence")) self.toolButton.setText(_translate("MainWindow", "Edit Sentence")) self.toolButton_2.setText(_translate("MainWindow", "Check")) self.toolButton_3.setText(_translate("MainWindow", "Edit result")) self.menuFile.setTitle(_translate("MainWindow", "Option")) self.actionReload.setText(_translate("MainWindow", "Reload")) self.actionSave_sentence_source.setText(_translate("MainWindow", "Save sentence source")) self.actionVideo.setText(_translate("MainWindow", "Learn with Video"))
class UserWindow(QMainWindow): def __init__(self, login, password, addr, port, parent=None): super().__init__(parent) self.login = login self.password = password self.user = client.User(login=login, password=password, addr=addr, port=port) self.user.connect() self.w = uic.loadUi('GUI\client.ui', self) self.w.setWindowTitle(self.login) # создаём рессивер для перехвата self.listener = GuiReciever(self.user.socket, self.user.request_queue) # создаём поток self.listener.gotData.connect(self.update_chat) self.th = QThread() # ???? self.listener.moveToThread(self.th) # связываем поток с рессивером при приеме данных self.th.started.connect(self.listener.poll) # запускаем поток self.th.start() self.initUT() def set_contacts(self): self.w.listWidgetContacts.clear() for contact in self.contacts: self.w.listWidgetContacts.addItem(str(contact)) @pyqtSlot(str) def update_chat(self, data): ''' Отображение сообщения в истории ''' try: msg = data self.w.listWidgetMsg.addItem(msg) except Exception as e: print(e) def refresh_contacts(self): self.contacts = self.user.get_contacts()[1] self.set_contacts() def add_contact(self): contact = self.w.textEditAddContact.toPlainText() answer = self.user.add_contact(contact) if answer['response'] == 201: self.refresh_contacts() self.print_info(answer) def print_info(self, msg): alert = msg['alert'] time_point = msg['time'] t = time_point[11:] text = f'[{t} сервер] {alert}' self.w.info.setText(text) def open_chat(self): try: self.wc = Chat(socket=self.user.socket, login=self.login, user=self.user) except Exception as e: print(e) def del_contact(self): contact = self.w.textEditAddContact.toPlainText() answer = self.user.del_contact(contact) if answer['response'] == 203: self.refresh_contacts() self.print_info(answer) def set_item(self): name = self.w.listWidgetContacts.currentItem().text() self.w.textEditAddContact.setText(name) def initUT(self): self.refresh_contacts() # получаем контакты self.w.pushButtonAddContact.clicked.connect(self.add_contact) self.w.pushButtonOpenChat.clicked.connect(self.open_chat) self.w.pushButtonDelContact.clicked.connect(self.del_contact) self.w.listWidgetContacts.itemClicked.connect(self.set_item) self.show()
class MainWindow(QWidget): """ Main window for uploading Template and Variables files. """ def __init__(self): super(MainWindow, self).__init__() self.automation_thread = None self.threads = [] self.setWindowTitle('AutoReport') self.setWindowIcon( QIcon(os.path.join(sys.path[1], 'assets\\small_logo.ico'))) self.setMaximumSize(600, 400) self.TemplateDrop = FileDrop(self, '.docx') self.TemplateDrop.clicked.connect(partial(self.upload, '.docx')) self.VarsDrop = FileDrop(self, '.xls') self.VarsDrop.clicked.connect(partial(self.upload, '.xls')) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.TemplateDrop, 0, Qt.AlignHCenter) hbox.addWidget(self.VarsDrop, 0, Qt.AlignHCenter) vbox.addLayout(hbox) self.submitBtn = QPushButton('Submit') self.submitBtn.clicked.connect(self.submit) vbox.addWidget(self.submitBtn) self.setLayout(vbox) self.setAcceptDrops(True) def upload(self, which_file): """ Handles the upload of a file from a file-upload dialog. This function is ran after a user clicks 'Open' after selecting a template or vars file. Parameters ---------- which_file : str Which file (Template or Variables) is being uploaded. """ if which_file == '.xls': file_path = QFileDialog().getOpenFileName(filter='*.xls')[0] icon_name = os.path.join(sys.path[1], 'assets\\excel_icon.png') listbox = self.VarsDrop else: file_path = QFileDialog().getOpenFileName(filter='*.docx')[0] icon_name = os.path.join(sys.path[1], 'assets\\word_icon.png') listbox = self.TemplateDrop if not file_path: listbox.setDefaultContents() else: file_name = file_path.split('/')[-1] listbox.cur_file = file_path listbox.clear() item = QListWidgetItem(QIcon(icon_name), file_name) item.setFont(QFont(None, 12)) item.setSizeHint(QSize(256, 256)) item.setTextAlignment(Qt.AlignCenter) listbox.addItem(item) def submit(self): """ Submit the inputted template and vars file to the automation thread. This code is ran upon press of the submit button. """ if not self.TemplateDrop.cur_file or not self.VarsDrop.cur_file: return self.dialog = ProgressDialog() self.automation_thread = QThread() self.threads.append(self.automation_thread) automation = Automate() automation.moveToThread(self.automation_thread) automation.guiUpdater.moveToThread(self.automation_thread) automation.guiUpdater.logging_signal.connect(self.log) # When the thread starts, the run method of the Automation object will be ran with the template and vars files self.automation_thread.started.connect( partial(automation.run, self.TemplateDrop.cur_file, self.VarsDrop.cur_file)) self.automation_thread.start() self.dialog.exec() @pyqtSlot(int, str, int) def log(self, level, message, progress=None): """ Add a log message to the progress dialog box. Parameters ---------- level : int The level (i.e. logging level, info, warning, or critical) of the message. message : str The contents of the message. progress : int, optional The progress level from 0 to 100 of the message. """ if progress is not None: self.dialog.progressBar.setValue(progress) if level in {logging.CRITICAL, logging.ERROR}: self.dialog.setWindowTitle('Error!') log = QListWidgetItem(message) color = { logging.WARNING: QColor(255, 204, 0), # Yellow logging.ERROR: QColor(166, 68, 82), # Red logging.CRITICAL: QColor(166, 68, 82) # Red }.get( level ) # If level is not Warning, Error, or Critical, there will be a default (white) background. if color: log.setBackground(color) self.dialog.logsList.addItem(log) self.dialog.logsList.scrollToBottom()
class MainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent, Qt.Window) self.setObjectName('main_window') self.params = PARAMS self.load_config() self.menubar = self.menuBar() self.root = QTabWidget(self) self.configurator = LoggerConfigurator(self.root) self.console = LoggerConsole(self.root) self.worker = None self.worker_thread = None self.init_ui() self.update_start_button() def init_ui(self): # Setting window geometry self.setWindowTitle('JIRA work logger') self.setWindowIcon(QIcon('gui/misc/clock-icon.ico')) # Setting menu bar app_menu = self.menubar.addMenu('Help') exit_action = QAction('Exit', self) exit_action.setShortcut('Ctrl+Q') exit_action.triggered.connect(qApp.quit) app_menu.addAction(exit_action) # Setting root frame self.root.addTab(self.configurator, 'Logger Setup') self.root.addTab(self.console, 'Logger Output') self.setCentralWidget(self.root) def setup_worker_thread(self): self.worker = LogWorker(self.params) self.worker_thread = QThread() self.worker.moveToThread(self.worker_thread) # Assign signals to slots self.worker.msg.connect(self.console.print_msg) self.worker.warn.connect(self.console.print_warn) self.worker.err.connect(self.console.print_err) self.worker_thread.started.connect(self.worker.execute_logging) self.worker_thread.finished.connect(self.stop_worker_thread) def execute_autologging(self): get_main_window().findChild( QWidget, 'main_buttons', Qt.FindChildrenRecursively).start_btn.setDisabled(True) self.read_params() self.setup_worker_thread() self.root.setCurrentIndex(1) qApp.processEvents() self.worker_thread.start() def stop_worker_thread(self): self.console.print_msg('Worker thread has been stopped') self.worker_thread.deleteLater() get_main_window().findChild( QWidget, 'main_buttons', Qt.FindChildrenRecursively).start_btn.setEnabled(True) qApp.processEvents() def update_start_button(self): self.read_params() start_btn = self.findChild(QWidget, 'main_buttons', Qt.FindChildrenRecursively).start_btn if not [param for param in MANDATORY_PARAMS if not self.params[param]]: if True in list(self.params['tasks_filter'].values()): start_btn.setEnabled(True) return start_btn.setDisabled(True) def read_params(self): """Reading params from widgets across Configurator""" # JIRA settings jira_widget = self.findChild(QWidget, 'jira_settings', Qt.FindChildrenRecursively) self.params['jira_host'] = jira_widget.host_ln.text() self.params['jira_user'] = jira_widget.user_ln.text() self.params['jira_pass'] = jira_widget.pass_ln.text() # Tasks filter settings tasks_filter_widget = self.findChild(QWidget, 'tasks_filter', Qt.FindChildrenRecursively) self.params['tasks_filter'][ 'user_assignee'] = tasks_filter_widget.is_assignee.isChecked() self.params['tasks_filter'][ 'user_validator'] = tasks_filter_widget.is_validator.isChecked() self.params['tasks_filter'][ 'user_creator'] = tasks_filter_widget.is_creator.isChecked() # Working days settings days_widget = self.findChild(QWidget, 'days_config', Qt.FindChildrenRecursively) self.params['work_days'] = days_widget.weekdays self.params['target_hrs'] = days_widget.target_hrs.value() self.params['daily_tasks'] = tasks_string_to_dict( days_widget.daily_tasks.text()) self.params['tasks_comment'] = days_widget.tasks_comment.text() self.params['daily_only'] = days_widget.daily_only.isChecked() self.params['ignore_tasks'] = tasks_string_to_list( days_widget.ignore_tasks.text()) # Date settings date_widget = self.findChild(QWidget, 'dates_selector', Qt.FindChildrenRecursively) self.params['from_date'] = date_widget.from_cal.selectedDate( ).toString(Qt.ISODate) self.params['to_date'] = date_widget.to_cal.selectedDate().toString( Qt.ISODate) def load_config(self): config_path = Path(CONFIG_FILE) if config_path.exists(): self.params.update( yaml.load(config_path.read_text(), Loader=yaml.FullLoader)) return
class FlashingDialog(QDialog): def __init__(self, parent): super().__init__() self.setWindowTitle("Flashing...") esptool.sw.read_start.connect(self.read_start) esptool.sw.read_progress.connect(self.read_progress) esptool.sw.read_finished.connect(self.read_finished) esptool.sw.erase_start.connect(self.erase_start) esptool.sw.erase_finished.connect(self.erase_finished) esptool.sw.write_start.connect(self.write_start) esptool.sw.write_progress.connect(self.write_progress) esptool.sw.write_finished.connect(self.write_finished) self.setFixedWidth(400) self.nrBinFile = QNetworkRequest() self.parent = parent vl = VLayout(10, 10) self.setLayout(vl) self.bin_data = b"" self.error_msg = None self.progress_task = QProgressBar() self.progress_task.setFixedHeight(45) self.task = QLabel() self.erase_timer = QTimer() self.erase_timer.setSingleShot(False) self.erase_timer.timeout.connect(self.erase_progress) self.btns = QDialogButtonBox(QDialogButtonBox.Abort) self.dlgText = QLabel( "Press the Boot button for a few seconds to start the flashing process" ) vl.addWidgets([self.dlgText, self.task, self.progress_task, self.btns]) self.btns.rejected.connect(self.abort) # process starts self.bin_file = parent.bin_file self.run_esptool() def updateBinProgress(self, recv, total): self.progress_task.setValue(recv // total * 100) def read_start(self): self.progress_task.setValue(0) self.task.setText("Saving image backup...") def read_progress(self, value): self.progress_task.setValue(value) def read_finished(self): self.progress_task.setValue(100) self.task.setText("Writing done.") def erase_start(self): self.btns.setEnabled(False) self.progress_task.setValue(0) self.task.setText("Erasing flash... (this may take a while)") self.erase_timer.start(1000) def erase_progress(self): self.progress_task.setValue(self.progress_task.value() + 5) def erase_finished(self): self.progress_task.setValue(100) self.task.setText("Erasing done.") self.erase_timer.stop() self.btns.setEnabled(True) def write_start(self): self.dlgText.setText("Flashing in progress...") self.progress_task.setValue(0) self.task.setText("Writing image...") def write_progress(self, value): self.progress_task.setValue(value) def write_finished(self): self.progress_task.setValue(100) self.task.setText("Writing done.") self.accept() def run_esptool(self): self.espthread = QThread() self.espworker = ESPWorker(self.parent.cbxPort.currentData(), self.bin_file) self.espworker.port_error.connect(self.error) self.espworker.moveToThread(self.espthread) self.espthread.started.connect(self.espworker.execute) self.espthread.start() def abort(self): self.espworker.stop() self.espthread.quit() self.espthread.wait(2000) self.reject() def error(self, e): self.error_msg = e self.reject() def accept(self): self.espworker.stop() self.espthread.quit() self.espthread.wait(2000) self.done(QDialog.Accepted)
class ObserverState(QObject): """ Handles Observer state (current trip info, current haul info, Gear Type, etc) and related database interactions """ modelChanged = pyqtSignal() currentHaulChanged = pyqtSignal(QVariant) currentTripChanged = pyqtSignal(QVariant) tripsChanged = pyqtSignal(QVariant) haulsChanged = pyqtSignal(QVariant) setsChanged = pyqtSignal(QVariant) catchesChanged = pyqtSignal(QVariant) trawlMaxDepthFathomsChanged = pyqtSignal(QVariant) trawlMaxBasketWeightLbsChanged = pyqtSignal(QVariant) trawlConfirmBasketWeightLbsChanged = pyqtSignal(QVariant) isGearTypeTrawlChanged = pyqtSignal(bool) currentObserverChanged = pyqtSignal(QVariant) driveLettersChanged = pyqtSignal(QVariant) lastBackupTimeChanged = pyqtSignal() backupStatusChanged = pyqtSignal(bool, QVariant, arguments=["success", "message"]) unusedSignal = pyqtSignal( name='unusedSignal' ) # For properties without a signal; use to avoid QML warning. currentCatchCatChanged = pyqtSignal(QVariant) catchCatNameChanged = pyqtSignal(str) speciesNameChanged = pyqtSignal(str) commentsChanged = pyqtSignal(str) wm5WeightChanged = pyqtSignal( int, QVariant, arguments=["catchId", "new_wt"]) # tell CC page to update tableView # If we are in these appstates, always save comments to Trips.notes # (instead of haul level: FishingActivities.notes) trip_comment_states = ('home_state', 'start_trawl_state', 'end_trawl_state', 'start_fg_state', 'end_fg_state', 'hauls_state', 'sets_state', 'trip_errors_state', 'logbook_state', 'backup_state', 'select_trip_state') def __init__(self, db): super().__init__() self._logger = logging.getLogger(__name__) self._db = db # These data will also get saved into (new) Trips cs_query = Settings.select().where(Settings.parameter == 'catch_share') if not cs_query.exists(): Settings.create(parameter='catch_share', value='TRUE') self._catchshare = cs_query.get() gt_query = Settings.select().where(Settings.parameter == 'gear_type') if not gt_query.exists(): Settings.create(parameter='gear_type', value='TRUE') self._geartype_trawl_default = gt_query.get() self._users = ObserverUsers() self._trips = ObserverTrip() self._hauls = Hauls(db=db) self._sets = Sets(db=db) self._catches = ObserverCatches(db=db) self._current_cc = None self._current_cc_name = "" # for display self._current_spec_name = "" # for display fr_query = Settings.select().where(Settings.parameter == 'first_run') if not fr_query.exists(): Settings.create(parameter='first_run', value='TRUE') self._firstrun = fr_query.get() cu_query = Settings.select().where( Settings.parameter == 'current_user') if not cu_query.exists(): Settings.create(parameter='current_user') self._current_user = cu_query.get() cu_query = Settings.select().where( Settings.parameter == 'current_user_id') if not cu_query.exists(): Settings.create(parameter='current_user_id') self._current_user_id = cu_query.get() # Type max depth as integer self._trawl_max_depth_fathoms = int( self.getset_setting( 'trawl_max_depth_fathoms', DefaultDefaultSettings.trawl_max_depth_fathoms)) self.trawlMaxDepthFathomsChanged.emit(self._trawl_max_depth_fathoms) # Confirmation-required basket weight, typed as integer self._trawl_confirm_basket_weight_lbs = int( self.getset_setting( 'trawl_confirm_basket_weight_lbs', DefaultDefaultSettings.trawl_confirm_basket_weight_lbs)) self.trawlConfirmBasketWeightLbsChanged.emit( self._trawl_confirm_basket_weight_lbs) # Max basket weight as integer, typed as integer self._trawl_max_basket_weight_lbs = int( self.getset_setting( 'trawl_max_basket_weight_lbs', DefaultDefaultSettings.trawl_max_basket_weight_lbs)) self.trawlMaxBasketWeightLbsChanged.emit( self._trawl_max_basket_weight_lbs) # Minimum and maximum degrees latitude as integer. # No emits necessary: this value will not change during a run. self._trawl_min_latitude_degrees = int( self.getset_setting( 'trawl_minimum_latitude_degrees', DefaultDefaultSettings.trawl_minimum_latitude_degrees)) self._trawl_max_latitude_degrees = int( self.getset_setting( 'trawl_maximum_latitude_degrees', DefaultDefaultSettings.trawl_maximum_latitude_degrees)) # DB Backup Thread self._backup_thread = QThread() self._backup_worker = None self._comments_all = '' self._comments_trip = '' self._comments_haul = dict() self._db_formatted_comments_trip = '' self._db_formatted_comments_haul = dict() self.currentTripId = ObserverDBUtil.db_load_setting( 'trip_number') # Current Trip ID if set self.update_comments() self._catches.retainedCatchWeightChanged.connect( self.update_wm5_catch_weights ) # ret. catch changes --> WM5 updates self._hauls.otcWeightChanged.connect( self.update_wm5_catch_weights) # otc changes --> WM5 updates @staticmethod def getset_setting(parm_name, default_val): """ Get parm_name setting from SETTINGS table. If setting not in SETTINGS, use default_val. Side-effect: also add default_val to SETTINGS table. """ fr_query = Settings.select().where(Settings.parameter == parm_name) if not fr_query.exists(): Settings.create(parameter=parm_name, value=default_val) return fr_query.get().value @pyqtSlot() def reset(self): # self._logger.warn('RESETTING STATE - TODO') self._current_cc = None self._current_cc_name = "" # for display self._current_spec_name = "" # for display @staticmethod def pad_trip_id(id_value): """ Returns padded string from int :param id_value: int ID :return: """ return '{0:05d}'.format(id_value) @pyqtProperty( str, notify=unusedSignal ) # Specify notify to avoid "depends on non-NOTIFYable properties" warning def optecsVersion(self): return optecs_version @pyqtProperty( str, notify=unusedSignal ) # Specify notify to avoid "depends on non-NOTIFYable properties" warning def dbVersion(self): return ObserverDBUtil.get_setting('database_revision', 'unknown') @pyqtProperty( int, notify=unusedSignal ) # Specify notify to avoid "depends on non-NOTIFYable properties" warning def displayDecimalPlaces(self): """ :return: Number of floating point decimal places to use in display text boxes. """ return display_decimal_places @pyqtProperty(bool) def firstRun(self): return self._firstrun.value.lower() == 'true' @firstRun.setter def firstRun(self, value): self._firstrun.value = 'TRUE' if value else 'FALSE' self._firstrun.save() @pyqtProperty(QVariant, notify=currentObserverChanged) def currentObserver(self): return self._current_user.value @pyqtProperty(QVariant, notify=trawlMaxDepthFathomsChanged) def trawlMaxDepthFathoms(self): return self._trawl_max_depth_fathoms @pyqtProperty(QVariant, notify=trawlMaxBasketWeightLbsChanged) def trawlMaxBasketWeightLbs(self): """ Used in Catch Counts/Weights. A basket can't weigh more than this amount. """ return self._trawl_max_basket_weight_lbs @pyqtProperty(QVariant, notify=trawlConfirmBasketWeightLbsChanged) def trawlConfirmBasketWeightLbs(self): """ Used in Catch Counts/Weights. A basket that weighs no more than trawlMaxBasketWeightLbs yet weighs more than this amount should prompt a confirmation window. """ return self._trawl_confirm_basket_weight_lbs @pyqtProperty(QVariant, notify=unusedSignal) def trawlMinLatitudeDegrees(self): return self._trawl_min_latitude_degrees @pyqtProperty(QVariant, notify=unusedSignal) def trawlMaxLatitudeDegrees(self): return self._trawl_max_latitude_degrees @currentObserver.setter def currentObserver(self, value): """ Set username for convenience, ties to appstate.users.currentUserName @param value: username @return: """ self._logger.info('Current observer set to {0}'.format(value)) self._current_user.value = value self._current_user.save() self._current_user_id.value = ObserverUsers.get_user_id(value) self._current_user_id.save() @pyqtProperty(bool) def defaultCatchShare(self): return self._catchshare.value.lower() == 'true' @defaultCatchShare.setter def defaultCatchShare(self, value): self._catchshare.value = 'TRUE' if value else 'FALSE' self._catchshare.save() @pyqtProperty(bool, notify=isGearTypeTrawlChanged) def isGearTypeTrawl(self): return self._geartype_trawl_default.value.lower() == 'true' @isGearTypeTrawl.setter def isGearTypeTrawl(self, value): self._geartype_trawl_default.value = 'TRUE' if value else 'FALSE' self._geartype_trawl_default.save() self.isGearTypeTrawlChanged.emit(value) @pyqtProperty(bool, notify=isGearTypeTrawlChanged) def isFixedGear(self): return self._geartype_trawl_default.value.lower() != 'true' @pyqtProperty(QVariant, notify=haulsChanged) def hauls(self): return self._hauls @pyqtProperty(QVariant, notify=setsChanged) def sets(self): return self._sets @pyqtProperty(str, notify=tripsChanged) def currentTripId(self): return self._trips.tripId @pyqtProperty(QVariant, notify=currentObserverChanged) def users(self): return self._users @currentTripId.setter def currentTripId(self, trip_id): """ Set Trip ID (currently same as db pk) @param trip_id: DB PK ID """ if trip_id is None: self._logger.info('Current trip ID is not set.') return # this is also being set in ObserverTrips.tripId.setter, but fails if in debriefer mode (user mismatch). # adding here to ensure trip_number param is definitely set when selecting trip # TODO: consolidate logic for setting trip_number here, or rework ObserverTrip.tripId setter ObserverDBUtil.db_save_setting('trip_number', trip_id) self._logger.debug( f"setting SETTINGS parameter trip_number to {trip_id}") trip_id = int(trip_id) # ensure integer key self._trips.tripId = trip_id self._hauls.reset() self._trips.load_current_trip(trip_id) if self.isFixedGear: self._sets.load_sets(trip_id=trip_id) else: self._hauls.load_hauls(trip_id=trip_id) @pyqtSlot(str, result='QVariant') def create_trip(self, vessel_name): """ Create trip, looks up vessel_name by ID @param vessel_name: vessel name as seen in DB @return: new trip """ if self.currentObserver is None or vessel_name is None: self._logger.error( 'No Observer/ Vessel Selected, abort create trip') return None vessel_id = self.get_vessel_id(vessel_name) observer_id = self.users.currentUserID program_id = self.users.currentProgramID self._logger.info( 'Creating trip with observer ID {}, vessel ID {} TODO PROGRAM ID'. format(observer_id, vessel_id)) newtrip = self._trips.create_trip(vessel_id, observer_id, program_id) self.modelChanged.emit() return newtrip @pyqtSlot(str, result='QVariant') def create_catch(self, catch_category_id): """ Create catch @param catch_category_id: @return: new catch model (dict) """ if catch_category_id is None: self._logger.error( 'Bad catch category ID passed to create_catch, abort.') return None current_haul_set_id = self.sets.current_set.fishing_activity if self.isFixedGear \ else self.hauls.current_haul.fishing_activity if current_haul_set_id is None: self._logger.error('No current haul/set ID, abort create_catch') return None try: catch_category_id = int(catch_category_id) except ValueError as ve: self._logger.error( f'catch_category_id "{catch_category_id}" is not int, aborting create_catch ({ve}).' ) return None self._logger.info( 'Creating catch with catch category ID {}, haul id {}'.format( catch_category_id, current_haul_set_id)) newcatch_model = self._catches.create_catch( catch_category_id=catch_category_id, fishing_activity_pk=current_haul_set_id) self.modelChanged.emit() return newcatch_model @pyqtSlot() def end_trip(self): self._trips.end_trip() self.hauls.reset() self.sets.reset() self.modelChanged.emit() @pyqtProperty(QVariant, notify=catchesChanged) def catches(self): return self._catches @pyqtProperty(QVariant, notify=modelChanged) def TripsModel(self): return self._trips.TripsModel @pyqtProperty(QVariant, notify=tripsChanged) def trips(self): return self._trips # Properties for display (in header, etc) @pyqtProperty(str, notify=catchCatNameChanged) def catchCatName(self): return self._current_cc_name @catchCatName.setter def catchCatName(self, value): self._current_cc_name = value self.catchCatNameChanged.emit(value) @pyqtProperty(str, notify=speciesNameChanged) def speciesName(self): return self._current_spec_name @speciesName.setter def speciesName(self, value): self._current_spec_name = value self.speciesNameChanged.emit(value) @pyqtProperty(str, notify=commentsChanged) def comments(self): return self._comments_all @pyqtProperty(str, notify=commentsChanged) def db_formatted_comments(self): return self._db_formatted_comments_trip @pyqtSlot(name='updateComments') def update_comments(self): if not self.currentTripId or not self.currentObserver: # self._logger.info(f'No trip, skip saving comments: {self.currentTripId}') return trip_id = int(self.currentTripId) self._logger.info(f'Updating comments for TRIP ID {trip_id}') self._comments_all = '' self._comments_trip = '' self._comments_haul = dict() # { haul_id: "comment" } self._db_formatted_comments_trip = '' self._db_formatted_comments_haul = dict() comments_q = Comment.select().where(Comment.trip == trip_id).order_by( Comment.comment_id) if not comments_q.count(): self._logger.debug(f'No comments found for trip: {trip_id}') return for comment in comments_q: # Removed starting dash comment delimiter to adhere to IFQ TRIP_CHECK convention that # notes start with an alphanumeric. But continuing to use trailing dash separator. new_comment_string = f'{comment.username} ({comment.appstateinfo}) ' \ f'{comment.comment_date} ---\n{comment.comment}\n\n' self._comments_all += new_comment_string try: # Only want first half of appstateinfo, that variable now also holds the page title text if comment.fishing_activity is None or comment.appstateinfo.split( "::")[0] in self.trip_comment_states: self._comments_trip += new_comment_string self._db_formatted_comments_trip += self._db_format_one_comment( comment) else: haul_id = comment.fishing_activity.fishing_activity if haul_id not in self._comments_haul.keys(): self._comments_haul[haul_id] = new_comment_string self._db_formatted_comments_haul[ haul_id] = self._db_format_one_comment(comment) else: # append self._comments_haul[haul_id] += new_comment_string self._db_formatted_comments_haul[ haul_id] += self._db_format_one_comment(comment) except Exception as e: # Handle load of bad previous comment weirdness self._logger.error(e) # now save to NOTES for Trip try: trip_q = Trips.get(Trips.trip == trip_id) trip_q.notes = ObserverDBUtil.escape_linefeeds( self._db_formatted_comments_trip) trip_q.save() self._logger.info( f"Wrote {len(trip_q.notes)} characters to Trips.notes.") for haul_id in self._comments_haul.keys(): haul_q = FishingActivities.get( (FishingActivities.trip == trip_id) & (FishingActivities.fishing_activity == haul_id)) haul_q.notes = ObserverDBUtil.escape_linefeeds( self._db_formatted_comments_haul[haul_id]) haul_q.save() self._logger.info( f"Wrote {len(haul_q.notes)} characters to FishingActivities.notes." ) except Trips.DoesNotExist as e: self._logger.warning(f'Cannot save comment, {e}') except FishingActivities.DoesNotExist as e: self._logger.warning(f'Cannot save comment, {e}') self.commentsChanged.emit(self._comments_trip) # logging.debug('Comments now {}'.format(self._comments)) @staticmethod def strip_db_comment(comment): remove_text = ['\r', '\n'] replace_text = [(',', '|')] for r in remove_text: comment = comment.replace(r, '') for r in replace_text: comment = comment.replace(r[0], r[1]) return comment def _db_format_one_comment(self, comment): db_format_comment = self.strip_db_comment(comment.comment) # Removed starting dash comment delimiter to adhere to IFQ TRIP_CHECK convention that # notes start with an alphanumeric. But continuing to use trailing dash separators (but only 4). # also need some smarts for the deleting the first half of the app state string if it's not # blank newappstate = comment.appstateinfo if len(comment.appstateinfo.split("::")) < 2 else \ comment.appstateinfo.split("::", maxsplit=1)[1] db_formatted_comment = f'{comment.username} ' \ f'({newappstate}) {comment.comment_date} ' \ f': {db_format_comment} -- ' return db_formatted_comment def _get_size_of_existing_comments(self): """ How many characters are currently in Trips.note for this trip? Ignores haul-level comments""" if not self.currentTripId or not self.currentObserver: self._logger.warning( f'Invalid trip or observer to save comments: {self.currentTripId}' ) return 0 else: trip_id = int(self.currentTripId) self._logger.info( f'Calculating size of existing comments for TRIP ID {trip_id}') db_formatted_comments = '' # Ignore haul-level comments comments_q = Comment. \ select(). \ where((Comment.trip == trip_id) & (Comment.fishing_activity.is_null(True))). \ order_by(Comment.comment_id) if not comments_q.count(): self._logger.info(f'No comments found for trip: {trip_id}') return 0 for comment in comments_q: db_formatted_comments += self._db_format_one_comment(comment) return len(db_formatted_comments) @pyqtProperty( int, notify=unusedSignal ) # Specify notify to avoid "depends on non-NOTIFYable properties" warning def maxTextSizeOfObserverComments(self): return max_text_size_observer_comments @pyqtSlot(str, str, result=int, name='getFreeCommentSpaceAfterProposedAdd') def get_free_observer_comment_space_after_proposed_add( self, proposed_comment, proposed_appstate): total_size_after, _ = self.get_free_comment_space_after_proposed_add( proposed_comment, proposed_appstate, max_text_size_observer_comments) return total_size_after def get_free_comment_space_after_proposed_add(self, proposed_comment, proposed_appstate, max_text_allowed): size_before = self._get_size_of_existing_comments() # Instantiate a new model instance in memory, but don't add to database (no create, no save) newcomment = Comment(username=self.currentObserver, comment_date=ObserverDBUtil.get_arrow_datestr(), appstateinfo=proposed_appstate, comment=proposed_comment, trip=self.currentTripId) size_of_new_comment = 0 if newcomment is None else len( self._db_format_one_comment(newcomment)) size_after = size_before + size_of_new_comment return max_text_allowed - size_after, size_of_new_comment def update_wm5_catch_weights(self): """ Go to DB directly and find catches w. WM5. Update catch_weight with OTC-RET., then EMIT to signal QML func lives here so it can interact with both _hauls and _catches signals NOTE: catch_weight cant be negative in DB, so if negative set to null / None :return: None """ if self.isFixedGear: # wm5 doesn't exist w. FG return wm5_catches = Catches.select(Catches.catch, Catches.catch_num).where( (Catches.fishing_activity == self._hauls.currentHaulDBId) & (Catches.catch_weight_method == '5')).execute() new_wt = self._hauls.getData( 'observer_total_catch') - self._hauls.retainedHaulWeight new_wt = new_wt if new_wt > 0 else None # wt can't be negative in DB, set to None/Null for c in wm5_catches: # there shouldn't be more than one, but just in case Catches.update(catch_weight=new_wt).where( (Catches.catch == c.catch)).execute() logging.info( f"CatchNum {c.catch_num} (ID: {c.catch}) WM5 weight updated to {new_wt}" ) self.wm5WeightChanged.emit( c.catch, new_wt) # tell CC QML page to update too @pyqtSlot(str, str, str, name='upsertComment') def upsert_comment(self, comment_prefix, comment, appstate): """ Use for comment update/insert oustide of Comment dialog box. Find comment with prefix, if exists, replace, else insert :param comment_prefix: str, e.g. CollectionMethod= :param comment: str, e.g. string after prefix :param appstate: str, e.g. state of app + title of current screen :return: None """ new_comment_date = ObserverDBUtil.get_arrow_datestr() new_comment = f"{comment_prefix}{comment}" # try to get comment model and update try: c = Comment.get(Comment.comment.contains(comment_prefix), Comment.trip == self.currentTripId) Comment.update(comment=new_comment, comment_date=new_comment_date, username=self.currentObserver).where( Comment.comment_id == c.comment_id).execute() except ValueError: # trip id is not defined yet return # if existing comment not found, create a new one except Comment.DoesNotExist: Comment.create(comment=new_comment, comment_date=new_comment_date, username=self.currentObserver, trip=self.currentTripId, appstateinfo=appstate) # parse comments to trips/fishing_activities self.update_comments() @pyqtSlot(str, str, name='addComment') def add_comment(self, comment, appstate): """ Adds date, username, and comment to Comments :return: """ if not self.currentTripId: self._logger.error( 'No trip selected, comment NOT saved: {}'.format(comment)) return self._logger.info( f'Adding comment "{comment}" to current trip {self.currentTripId}') # TODO Add to trips, not Comment if self.isFixedGear: haul_db_id = self.sets.currentSetDBId if self.sets else None else: haul_db_id = self.hauls.currentHaulDBId if self.hauls else None newcomment = Comment.create( username=self.currentObserver, comment_date=ObserverDBUtil.get_arrow_datestr(), appstateinfo=appstate, comment=comment, trip=self.currentTripId, fishing_activity=haul_db_id) newcomment.save() self.update_comments() @pyqtSlot(str, result=int) def get_vessel_id(self, vessel_name): """ Check for vessel name @param vessel_name: vessel to check for in DB @return: Returns ID if vessel is in the DB, otherwise 0 """ try: vessel_name = vessel_name.lower() try: vessel = Vessels.get( fn.Lower(Vessels.vessel_name) == vessel_name) logging.info('ID {} found for vessel {}'.format( vessel.vessel, vessel_name)) return vessel.vessel except DoesNotExist: logging.warning('Vessel not found: {}'.format(vessel_name)) return 0 except ValueError: logging.warning('Invalid vessel name {}'.format(vessel_name)) return 0 @pyqtSlot(name='raiseException') def raise_exception(self): """ QML code can call this to raise an exception in order to test the unhandled exception handler. Should be only called by a developer or product proxy (Newport Team). Should be triggered only by an obscure keystroke sequence in the UI (currently 10 mouse clicks in a row on the label (not text box) of Visual OTC of the Haul Details screen). :return: """ msgStart = "Exception intentionally raised for testing. " msgEnd = "End of intentionally long exception msg" filler = "abcdefghijklmnopqrstuvwxyz " * 200 raise OptecsTestException(msgStart + filler + msgEnd) @pyqtProperty(bool, notify=unusedSignal) def isTestMode(self): """ if DB is pointing at IFQADMIN or IFQDEV, True else False (production!) @return: True if Test mode """ mode = ObserverDBUtil.get_setting('optecs_mode') return False if mode == 'ifq' else True @pyqtProperty(bool, notify=unusedSignal) def isTrainingMode(self): """ @return: True if training mode """ mode = ObserverDBUtil.get_setting('training') return True if mode == 'TRUE' else False @pyqtProperty(QVariant, notify=unusedSignal) def optecsMode(self): """ @return: optecs mode, e.g. ifqadmin """ return ObserverDBUtil.get_setting('optecs_mode', 'ifqadmin').upper() @pyqtSlot(str, name='backupToPath', result=str) def backup_db_to_path(self, path): try: if not self._backup_thread.isRunning(): self._backup_worker = BackupDBWorker(dest_path=path) self._backup_worker.moveToThread(self._backup_thread) self._backup_worker.backupStatus.connect( self._backup_status_received) self._backup_thread.started.connect(self._backup_worker.run) self._backup_thread.start() return f'Backup started to\n{path}...' except Exception as e: return textwrap.fill(f'FAIL:\nCould not back up DB:\n{e}', 60) def _backup_status_received(self, success, message): """ Method to catch the backup results @param success: True/False if succeeded @param message: Description of status @return: """ if success: self._update_backup_time() self.backupStatusChanged.emit(success, message) self._backup_thread.quit() @pyqtProperty(QVariant, notify=lastBackupTimeChanged) def lastBackupTime(self): """ Returns string representation of last DB backup. @return: string value """ try: last_sync = Settings.get(Settings.parameter == 'last_backup_time') last_time = arrow.get(last_sync.value) return last_time.humanize() except Settings.DoesNotExist: return 'Never' def _update_backup_time(self): """ Set most recent backup time to now. @return: """ try: last_sync = Settings.get(Settings.parameter == 'last_backup_time') last_sync.value = arrow.now() last_sync.save() except Settings.DoesNotExist: new_setting = Settings.create(parameter='last_backup_time', value=arrow.now()) new_setting.save() self.lastBackupTimeChanged.emit() @pyqtSlot(name='updateDriveLetters') def update_drive_letters(self): """ Send new drive letters signal @return: """ self.driveLettersChanged.emit( ObserverDBUtil.get_external_drive_letters()) @pyqtProperty(QVariant, notify=driveLettersChanged) def driveLetters(self): return ObserverDBUtil.get_external_drive_letters()
class TERuleQueryTab(SEToolsWidget, QScrollArea): """A Type Enforcement rule query.""" def __init__(self, parent, policy, perm_map): super(TERuleQueryTab, self).__init__(parent) self.log = logging.getLogger(__name__) self.policy = policy self.query = TERuleQuery(policy) self.setupUi() def __del__(self): self.thread.quit() self.thread.wait(5000) logging.getLogger("setools.terulequery").removeHandler(self.handler) def setupUi(self): self.load_ui("terulequery.ui") # set up source/target autocompletion typeattr_completion_list = [str(t) for t in self.policy.types()] typeattr_completion_list.extend(str(a) for a in self.policy.typeattributes()) typeattr_completer_model = QStringListModel(self) typeattr_completer_model.setStringList(sorted(typeattr_completion_list)) self.typeattr_completion = QCompleter() self.typeattr_completion.setModel(typeattr_completer_model) self.source.setCompleter(self.typeattr_completion) self.target.setCompleter(self.typeattr_completion) # set up default autocompletion type_completion_list = [str(t) for t in self.policy.types()] type_completer_model = QStringListModel(self) type_completer_model.setStringList(sorted(type_completion_list)) self.type_completion = QCompleter() self.type_completion.setModel(type_completer_model) self.default_type.setCompleter(self.type_completion) # setup indications of errors on source/target/default self.orig_palette = self.source.palette() self.error_palette = self.source.palette() self.error_palette.setColor(QPalette.Base, Qt.red) self.clear_source_error() self.clear_target_error() self.clear_default_error() self.clear_xperm_error() # populate class list self.class_model = SEToolsListModel(self) self.class_model.item_list = sorted(self.policy.classes()) self.tclass.setModel(self.class_model) # populate perm list self.perms_model = PermListModel(self, self.policy) self.perms.setModel(self.perms_model) # populate bool list self.bool_model = SEToolsListModel(self) self.bool_model.item_list = sorted(self.policy.bools()) self.bool_criteria.setModel(self.bool_model) # set up results self.table_results_model = TERuleTableModel(self) self.sort_proxy = QSortFilterProxyModel(self) self.sort_proxy.setSourceModel(self.table_results_model) self.table_results.setModel(self.sort_proxy) self.table_results.sortByColumn(0, Qt.AscendingOrder) # set up processing thread self.thread = QThread() self.worker = QueryResultsUpdater(self.query, self.table_results_model) self.worker.moveToThread(self.thread) self.worker.raw_line.connect(self.raw_results.appendPlainText) self.worker.finished.connect(self.update_complete) self.worker.finished.connect(self.thread.quit) self.thread.started.connect(self.worker.update) # create a "busy, please wait" dialog self.busy = QProgressDialog(self) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) self.busy.canceled.connect(self.thread.requestInterruption) self.busy.reset() # update busy dialog from query INFO logs self.handler = LogHandlerToSignal() self.handler.message.connect(self.busy.setLabelText) logging.getLogger("setools.terulequery").addHandler(self.handler) # Ensure settings are consistent with the initial .ui state self.set_source_regex(self.source_regex.isChecked()) self.set_target_regex(self.target_regex.isChecked()) self.set_default_regex(self.default_regex.isChecked()) self.toggle_xperm_criteria() self.criteria_frame.setHidden(not self.criteria_expander.isChecked()) self.notes.setHidden(not self.notes_expander.isChecked()) # connect signals self.buttonBox.clicked.connect(self.run) self.allowxperm.toggled.connect(self.toggle_xperm_criteria) self.auditallowxperm.toggled.connect(self.toggle_xperm_criteria) self.neverallowxperm.toggled.connect(self.toggle_xperm_criteria) self.dontauditxperm.toggled.connect(self.toggle_xperm_criteria) self.clear_ruletypes.clicked.connect(self.clear_all_ruletypes) self.all_ruletypes.clicked.connect(self.set_all_ruletypes) self.source.textEdited.connect(self.clear_source_error) self.source.editingFinished.connect(self.set_source) self.source_regex.toggled.connect(self.set_source_regex) self.target.textEdited.connect(self.clear_target_error) self.target.editingFinished.connect(self.set_target) self.target_regex.toggled.connect(self.set_target_regex) self.tclass.selectionModel().selectionChanged.connect(self.set_tclass) self.invert_class.clicked.connect(self.invert_tclass_selection) self.perms.selectionModel().selectionChanged.connect(self.set_perms) self.invert_perms.clicked.connect(self.invert_perms_selection) self.xperms.textEdited.connect(self.clear_xperm_error) self.xperms.editingFinished.connect(self.set_xperm) self.default_type.textEdited.connect(self.clear_default_error) self.default_type.editingFinished.connect(self.set_default_type) self.default_regex.toggled.connect(self.set_default_regex) self.bool_criteria.selectionModel().selectionChanged.connect(self.set_bools) # # Ruletype criteria # def _set_ruletypes(self, value): self.allow.setChecked(value) self.allowxperm.setChecked(value) self.auditallow.setChecked(value) self.auditallowxperm.setChecked(value) self.neverallow.setChecked(value) self.neverallowxperm.setChecked(value) self.dontaudit.setChecked(value) self.dontauditxperm.setChecked(value) self.type_transition.setChecked(value) self.type_member.setChecked(value) self.type_change.setChecked(value) def set_all_ruletypes(self): self._set_ruletypes(True) def clear_all_ruletypes(self): self._set_ruletypes(False) # # Source criteria # def clear_source_error(self): self.source.setToolTip("Match the source type/attribute of the rule.") self.source.setPalette(self.orig_palette) def set_source(self): try: self.query.source = self.source.text() except Exception as ex: self.log.error("Source type/attribute error: {0}".format(ex)) self.source.setToolTip("Error: " + str(ex)) self.source.setPalette(self.error_palette) def set_source_regex(self, state): self.log.debug("Setting source_regex {0}".format(state)) self.query.source_regex = state self.clear_source_error() self.set_source() # # Target criteria # def clear_target_error(self): self.target.setToolTip("Match the target type/attribute of the rule.") self.target.setPalette(self.orig_palette) def set_target(self): try: self.query.target = self.target.text() except Exception as ex: self.log.error("Target type/attribute error: {0}".format(ex)) self.target.setToolTip("Error: " + str(ex)) self.target.setPalette(self.error_palette) def set_target_regex(self, state): self.log.debug("Setting target_regex {0}".format(state)) self.query.target_regex = state self.clear_target_error() self.set_target() # # Class criteria # def set_tclass(self): selected_classes = [] for index in self.tclass.selectionModel().selectedIndexes(): selected_classes.append(self.class_model.data(index, Qt.UserRole)) self.query.tclass = selected_classes self.perms_model.set_classes(selected_classes) def invert_tclass_selection(self): invert_list_selection(self.tclass.selectionModel()) # # Permissions criteria # def set_perms(self): selected_perms = [] for index in self.perms.selectionModel().selectedIndexes(): selected_perms.append(self.perms_model.data(index, Qt.UserRole)) self.query.perms = selected_perms def invert_perms_selection(self): invert_list_selection(self.perms.selectionModel()) # # Extended permission criteria # def toggle_xperm_criteria(self): mode = any((self.allowxperm.isChecked(), self.auditallowxperm.isChecked(), self.neverallowxperm.isChecked(), self.dontauditxperm.isChecked())) self.xperms.setEnabled(mode) self.xperms_equal.setEnabled(mode) def clear_xperm_error(self): self.xperms.setToolTip("Match the extended permissions of the rule. Comma-separated " "permissions or ranges of permissions.") self.xperms.setPalette(self.orig_palette) def set_xperm(self): xperms = [] try: text = self.xperms.text() if text: for item in self.xperms.text().split(","): rng = item.split("-") if len(rng) == 2: xperms.append((int(rng[0], base=16), int(rng[1], base=16))) elif len(rng) == 1: xperms.append((int(rng[0], base=16), int(rng[0], base=16))) else: raise ValueError("Enter an extended permission or extended permission " "range, e.g. 0x5411 or 0x8800-0x88ff.") self.query.xperms = xperms else: self.query.xperms = None except Exception as ex: self.log.error("Extended permissions error: {0}".format(ex)) self.xperms.setToolTip("Error: " + str(ex)) self.xperms.setPalette(self.error_palette) # # Default criteria # def clear_default_error(self): self.default_type.setToolTip("Match the default type the rule.") self.default_type.setPalette(self.orig_palette) def set_default_type(self): self.query.default_regex = self.default_regex.isChecked() try: self.query.default = self.default_type.text() except Exception as ex: self.log.error("Default type error: {0}".format(ex)) self.default_type.setToolTip("Error: " + str(ex)) self.default_type.setPalette(self.error_palette) def set_default_regex(self, state): self.log.debug("Setting default_regex {0}".format(state)) self.query.default_regex = state self.clear_default_error() self.set_default_type() # # Boolean criteria # def set_bools(self): selected_bools = [] for index in self.bool_criteria.selectionModel().selectedIndexes(): selected_bools.append(self.bool_model.data(index, Qt.UserRole)) self.query.boolean = selected_bools # # Save/Load tab # def save(self): """Return a dictionary of settings.""" settings = {} save_checkboxes(self, settings, ["criteria_expander", "notes_expander", "allow", "allowxperm", "auditallow", "auditallowxperm", "neverallow", "neverallowxperm", "dontaudit", "dontauditxperm", "type_transition", "type_change", "type_member", "source_indirect", "source_regex", "target_indirect", "target_regex", "perms_subset", "xperms_equal", "default_regex", "bools_equal"]) save_lineedits(self, settings, ["source", "target", "xperms", "default_type"]) save_listviews(self, settings, ["tclass", "perms", "bool_criteria"]) save_textedits(self, settings, ["notes"]) return settings def load(self, settings): load_checkboxes(self, settings, ["allow", "allowxperm", "auditallow", "auditallowxperm", "neverallow", "neverallowxperm", "dontaudit", "dontauditxperm", "type_transition", "type_change", "type_member", "criteria_expander", "notes_expander", "source_indirect", "source_regex", "target_indirect", "target_regex", "perms_subset", "xperms_equal", "default_regex", "bools_equal"]) load_lineedits(self, settings, ["source", "target", "xperms", "default_type"]) load_listviews(self, settings, ["tclass", "perms", "bool_criteria"]) load_textedits(self, settings, ["notes"]) # # Results runner # def run(self, button): # right now there is only one button. rule_types = [] max_results = 0 if self.allow.isChecked(): rule_types.append("allow") max_results += self.policy.allow_count if self.allowxperm.isChecked(): rule_types.append("allowxperm") max_results += self.policy.allowxperm_count if self.auditallow.isChecked(): rule_types.append("auditallow") max_results += self.policy.auditallow_count if self.auditallowxperm.isChecked(): rule_types.append("auditallowxperm") max_results += self.policy.auditallowxperm_count if self.neverallow.isChecked(): rule_types.append("neverallow") max_results += self.policy.neverallow_count if self.neverallowxperm.isChecked(): rule_types.append("neverallowxperm") max_results += self.policy.neverallowxperm_count if self.dontaudit.isChecked(): rule_types.append("dontaudit") max_results += self.policy.dontaudit_count if self.dontauditxperm.isChecked(): rule_types.append("dontauditxperm") max_results += self.policy.dontauditxperm_count if self.type_transition.isChecked(): rule_types.append("type_transition") max_results += self.policy.type_transition_count if self.type_member.isChecked(): rule_types.append("type_member") max_results += self.policy.type_member_count if self.type_change.isChecked(): rule_types.append("type_change") max_results += self.policy.type_change_count self.query.ruletype = rule_types self.query.source_indirect = self.source_indirect.isChecked() self.query.target_indirect = self.target_indirect.isChecked() self.query.perms_subset = self.perms_subset.isChecked() self.query.boolean_equal = self.bools_equal.isChecked() # if query is broad, show warning. if not any((self.query.source, self.query.target, self.query.tclass, self.query.perms, self.query.xperms, self.query.default, self.query.boolean)) \ and max_results > 1000: reply = QMessageBox.question( self, "Continue?", "This is a broad query, estimated to return {0} results. Continue?". format(max_results), QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.No: return # start processing self.busy.setLabelText("Processing query...") self.busy.show() self.raw_results.clear() self.thread.start() def update_complete(self, count): self.log.info("{0} type enforcement rule(s) found.".format(count)) # update sizes/location of result displays if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's columns; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeColumnsToContents() # If the permissions column width is too long, pull back # to a reasonable size header = self.table_results.horizontalHeader() if header.sectionSize(4) > 400: header.resizeSection(4, 400) if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's rows; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeRowsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText("Moving the raw result to top; GUI may be unresponsive") self.busy.repaint() self.raw_results.moveCursor(QTextCursor.Start) self.busy.reset()
class MLSRuleQueryTab(SEToolsWidget, QScrollArea): """An MLS rule query.""" def __init__(self, parent, policy, perm_map): super(MLSRuleQueryTab, self).__init__(parent) self.log = logging.getLogger(__name__) self.policy = policy self.query = MLSRuleQuery(policy) self.setupUi() def __del__(self): self.thread.quit() self.thread.wait(5000) logging.getLogger("setools.mlsrulequery").removeHandler(self.handler) def setupUi(self): self.load_ui("mlsrulequery.ui") # set up source/target autocompletion typeattr_completion_list = [str(t) for t in self.policy.types()] typeattr_completion_list.extend( str(a) for a in self.policy.typeattributes()) typeattr_completer_model = QStringListModel(self) typeattr_completer_model.setStringList( sorted(typeattr_completion_list)) self.typeattr_completion = QCompleter() self.typeattr_completion.setModel(typeattr_completer_model) self.source.setCompleter(self.typeattr_completion) self.target.setCompleter(self.typeattr_completion) # setup indications of errors on source/target/default self.orig_palette = self.source.palette() self.error_palette = self.source.palette() self.error_palette.setColor(QPalette.Base, Qt.red) self.clear_source_error() self.clear_target_error() self.clear_default_error() # populate class list self.class_model = SEToolsListModel(self) self.class_model.item_list = sorted(self.policy.classes()) self.tclass.setModel(self.class_model) # set up results self.table_results_model = MLSRuleTableModel(self) self.sort_proxy = QSortFilterProxyModel(self) self.sort_proxy.setSourceModel(self.table_results_model) self.table_results.setModel(self.sort_proxy) self.table_results.sortByColumn(1, Qt.AscendingOrder) # set up processing thread self.thread = QThread() self.worker = QueryResultsUpdater(self.query, self.table_results_model) self.worker.moveToThread(self.thread) self.worker.raw_line.connect(self.raw_results.appendPlainText) self.worker.finished.connect(self.update_complete) self.worker.finished.connect(self.thread.quit) self.thread.started.connect(self.worker.update) # create a "busy, please wait" dialog self.busy = QProgressDialog(self) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) self.busy.canceled.connect(self.thread.requestInterruption) self.busy.reset() # update busy dialog from query INFO logs self.handler = LogHandlerToSignal() self.handler.message.connect(self.busy.setLabelText) logging.getLogger("setools.mlsrulequery").addHandler(self.handler) # Ensure settings are consistent with the initial .ui state self.set_source_regex(self.source_regex.isChecked()) self.set_target_regex(self.target_regex.isChecked()) self.criteria_frame.setHidden(not self.criteria_expander.isChecked()) self.notes.setHidden(not self.notes_expander.isChecked()) # connect signals self.buttonBox.clicked.connect(self.run) self.clear_ruletypes.clicked.connect(self.clear_all_ruletypes) self.all_ruletypes.clicked.connect(self.set_all_ruletypes) self.source.textEdited.connect(self.clear_source_error) self.source.editingFinished.connect(self.set_source) self.source_regex.toggled.connect(self.set_source_regex) self.target.textEdited.connect(self.clear_target_error) self.target.editingFinished.connect(self.set_target) self.target_regex.toggled.connect(self.set_target_regex) self.tclass.selectionModel().selectionChanged.connect(self.set_tclass) self.invert_class.clicked.connect(self.invert_tclass_selection) self.default_range.textEdited.connect(self.clear_default_error) self.default_range.editingFinished.connect(self.set_default_range) # # Ruletype criteria # def _set_ruletypes(self, value): self.range_transition.setChecked(value) def set_all_ruletypes(self): self._set_ruletypes(True) def clear_all_ruletypes(self): self._set_ruletypes(False) # # Source criteria # def clear_source_error(self): self.source.setToolTip("Match the source type/attribute of the rule.") self.source.setPalette(self.orig_palette) def set_source(self): try: self.query.source = self.source.text() except Exception as ex: self.log.error("Source type/attribute error: {0}".format(ex)) self.source.setToolTip("Error: {0}".format(ex)) self.source.setPalette(self.error_palette) def set_source_regex(self, state): self.log.debug("Setting source_regex {0}".format(state)) self.query.source_regex = state self.clear_source_error() self.set_source() # # Target criteria # def clear_target_error(self): self.target.setToolTip("Match the target type/attribute of the rule.") self.target.setPalette(self.orig_palette) def set_target(self): try: self.query.target = self.target.text() except Exception as ex: self.log.error("Target type/attribute error: {0}".format(ex)) self.target.setToolTip("Error: {0}".format(ex)) self.target.setPalette(self.error_palette) def set_target_regex(self, state): self.log.debug("Setting target_regex {0}".format(state)) self.query.target_regex = state self.clear_target_error() self.set_target() # # Class criteria # def set_tclass(self): selected_classes = [] for index in self.tclass.selectionModel().selectedIndexes(): selected_classes.append(self.class_model.data(index, Qt.UserRole)) self.query.tclass = selected_classes def invert_tclass_selection(self): invert_list_selection(self.tclass.selectionModel()) # # Default criteria # def clear_default_error(self): self.default_range.setToolTip("Match the default type the rule.") self.default_range.setPalette(self.orig_palette) def set_default_range(self): try: self.query.default = self.default_range.text() except Exception as ex: self.log.error("Default range error: {0}".format(ex)) self.default_range.setToolTip("Error: {0}".format(ex)) self.default_range.setPalette(self.error_palette) # # Results runner # def run(self, button): # right now there is only one button. self.query.ruletype = ['range_transition'] self.query.source_indirect = self.source_indirect.isChecked() self.query.target_indirect = self.target_indirect.isChecked() # start processing self.busy.setLabelText("Processing query...") self.busy.show() self.raw_results.clear() self.thread.start() def update_complete(self, count): self.log.info("{0} MLS rule(s) found.".format(count)) # update sizes/location of result displays if not self.busy.wasCanceled(): self.busy.setLabelText( "Resizing the result table's columns; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeColumnsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText( "Resizing the result table's rows; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeRowsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText( "Moving the raw result to top; GUI may be unresponsive") self.busy.repaint() self.raw_results.moveCursor(QTextCursor.Start) self.busy.reset()
def start(self): QThread.start(self) StenoEngine.start(self)
class MainWindow(QMainWindow, Ui_MainWindow): """QT main window""" def __init__(self, *args, obj=None, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) # Logging self.log = logging.getLogger("SpecChecker Main") self.log.setLevel("NOTSET") self.log.info("Starting Main Application") # Configuration self.config = configparser.ConfigParser() self.load_configuration() self.load_logging_configuration() # GUI Code self.setupUi(self) self.progressBar.setValue(0) self.btnStart.pressed.connect(self.runAllTests) self.btnExit.pressed.connect(self.doExit) self.actionExit.triggered.connect(self.doExit) self.actionAbout.triggered.connect(self.showAbout) self.statusText = "" # Specifications Code self.specs = SpecRecord() self.audioInfo = {} self.list_gpus = [] self.cpuInfo = {} self.networkInfo = {} self.hard_drive_list = [] self.locationInfo = {} self.memoryInfo = {} self.systemInfo = {} self.webcamList = [] self.timer = QTimer() self.speed_check_timeout = (1000 * 1) def finished(self, spec_record): self.btnStart.setEnabled(True) self.btnStart.setDisabled(False) self.btnStart.setText("Start") self.specs = spec_record # Setup email submission from configuration self.specs.email.client_name = self.txtName.text() self.specs.email.client_email_address = self.txtEmail.text() self.specs.email.fields = json.loads(self.config.get("email_submission", "fields")) self.specs.email.email_provider = self.config.get("email_submission", "email_provider") self.specs.email.send_address = self.config.get("email_submission", "send_address") self.specs.email.subject = self.config.get("email_submission", "subject") self.specs.email.template = self.config.get("email_submission", "template") self.specs.email.autoresponse = self.config.get("email_submission", "autoresponse") self.specs.email.cc_addresses = self.config.get("email_submission", "cc_addresses") self.specs.email.webhook = self.config.get("email_submission", "webhook") self.specs.email.url = self.config.get("email_submission", "api_url") self.log.error(f"url: {self.specs.email.url}") self.specs.email.submit(data=self.specs, email_provider=self.specs.email.email_provider, send_address=self.specs.email.send_address, subject=self.specs.email.subject, template=self.specs.email.template, autoresponse=self.specs.email.autoresponse, cc_addresses=self.specs.email.cc_addresses, webhook=self.specs.email.webhook, url=self.specs.email.url, client_name=self.specs.email.client_name, client_email_address=self.specs.email.client_email_address) # self.specs.write_to_file() def runAllTests(self): self.thread = QThread() self.worker = MainTestWorker(obj=self) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.test) self.worker.finished.connect(self.thread.quit) self.worker.finished.connect(self.worker.deleteLater) self.thread.finished.connect(self.thread.deleteLater) self.worker.progress.connect(self.reportProgress) # self.worker.progress.connect(self.speed_progress) self.thread.start() self.timer.start(self.speed_check_timeout) self.timer.timeout.connect(self.check_stage) self.btnStart.setDisabled(True) self.btnStart.setEnabled(False) self.btnStart.setText("Running Tests...") self.worker.finished.connect(self.finished) self.thread.finished.connect( lambda: self.updateStatus("\n\n All Tests Complete!") ) def check_stage(self): global speed_stage if speed_stage == 1: self.reportProgress(60, "dot") if speed_stage == 2: self.reportProgress(70, "dot") if speed_stage == 3: self.reportProgress(80, "dot") if speed_stage == 4: self.reportProgress(90, "dot") if speed_stage == 5: self.progressBar.setValue(100) if speed_stage <= 0 or speed_stage >= 6: self.reportProgress(-99, "dot") def reportProgress(self, percentage, module_name=None, first_pass=True): if not first_pass: self.updateStatus("Complete\n") if module_name is not None and module_name != "" and module_name != "dot": self.updateStatus(f"Scanning {module_name}..........") if module_name == "dot": self.updateStatus(".") if 100 >= percentage >= 0 and isinstance(percentage, int): self.progressBar.setValue(percentage) def showAbout(self): """Show the about box""" dlg = AboutBox() dlg.exec_() def doExit(self): """Exit the program cleanly""" self.log.info("Closing Application") self.close() def updateStatus(self, text): """Update the status text in the main text area of the app""" self.statusText = self.statusText + text self.txtStatus.setPlainText(self.statusText) def clearStatus(self): """Clear the status text in the main text area of the app""" self.statusText = "" self.txtStatus.setPlainText("") def load_configuration(self, filename='config.ini'): """Check if a configuration file exists. If it does, load it If not, load the default""" if path.exists(filename): self.config.read(filename) self.log.info(f"Loaded Configuration: {filename}") else: self.load_default_configuration() self.log.info("Configuration File Does Not Exist. Loading Defaults.") def load_default_configuration(self): """Load built in default configuration""" default_config = """ [general] # Comment out lines that are unnecessary with hash save to file = False debug = True # Debug Levels: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET (NOTSET turns off logging) debug level = INFO [email_submission] enabled = False # Not Yet Implemented [cpu] enabled = True [gpu] enabled = True [harddrive] enabled = True [location] enabled = True # options: ipinfo, provider = ipinfo [memory] enabled = True [network] enabled = True [sound] enabled = True [speedtest] enabled = True # options: fast, provider = fast [system] enabled = True """ self.config.read_string(default_config) self.log.info("Loaded Default Configuration") def load_logging_configuration(self): """todo Sets Application Wide Log Level From Configuration""" log_flag = bool(self.config.get('general', 'debug')) log_level = self.config.get('general', 'debug level') if log_flag is False: log_level = "NOTSET"
class CommonQueryTab(AnalysisTab): """Common browser and query tab.""" def __init__(self, parent, policy, perm_map): super(CommonQueryTab, self).__init__(parent) self.log = logging.getLogger(__name__) self.policy = policy self.query = CommonQuery(policy) self.setupUi() def __del__(self): self.thread.quit() self.thread.wait(5000) logging.getLogger("setools.commonquery").removeHandler(self.handler) def setupUi(self): self.load_ui("commonquery.ui") # populate commons list self.common_model = SEToolsListModel(self) self.common_model.item_list = sorted(c for c in self.policy.commons()) self.commons.setModel(self.common_model) # populate perm list self.perms_model = SEToolsListModel(self) perms = set() for com in self.policy.commons(): perms.update(com.perms) self.perms_model.item_list = sorted(perms) self.perms.setModel(self.perms_model) # set up results self.table_results_model = CommonTableModel(self) self.sort_proxy = QSortFilterProxyModel(self) self.sort_proxy.setSourceModel(self.table_results_model) self.table_results.setModel(self.sort_proxy) self.table_results.sortByColumn(0, Qt.AscendingOrder) # setup indications of errors self.errors = set() self.orig_palette = self.name.palette() self.error_palette = self.name.palette() self.error_palette.setColor(QPalette.Base, Qt.red) self.clear_name_error() # set up processing thread self.thread = QThread() self.worker = QueryResultsUpdater(self.query, self.table_results_model) self.worker.moveToThread(self.thread) self.worker.raw_line.connect(self.raw_results.appendPlainText) self.worker.finished.connect(self.update_complete) self.worker.finished.connect(self.thread.quit) self.thread.started.connect(self.worker.update) # create a "busy, please wait" dialog self.busy = QProgressDialog(self) self.busy.setModal(True) self.busy.setRange(0, 0) self.busy.setMinimumDuration(0) self.busy.canceled.connect(self.thread.requestInterruption) self.busy.reset() # update busy dialog from query INFO logs self.handler = LogHandlerToSignal() self.handler.message.connect(self.busy.setLabelText) logging.getLogger("setools.commonquery").addHandler(self.handler) # Ensure settings are consistent with the initial .ui state self.set_name_regex(self.name_regex.isChecked()) self.notes.setHidden(not self.notes_expander.isChecked()) # connect signals self.commons.doubleClicked.connect(self.get_detail) self.commons.get_detail.triggered.connect(self.get_detail) self.name.textEdited.connect(self.clear_name_error) self.name.editingFinished.connect(self.set_name) self.name_regex.toggled.connect(self.set_name_regex) self.perms.selectionModel().selectionChanged.connect(self.set_perms) self.invert_perms.clicked.connect(self.invert_perms_selection) self.buttonBox.clicked.connect(self.run) # # Class browser # def get_detail(self): # .ui is set for single item selection. index = self.commons.selectedIndexes()[0] item = self.common_model.data(index, Qt.UserRole) self.log.debug("Generating detail window for {0}".format(item)) common_detail(self, item) # # Name criteria # def clear_name_error(self): self.clear_criteria_error(self.name, "Match the common name.") def set_name(self): try: self.query.name = self.name.text() except Exception as ex: self.log.error("Common name error: {0}".format(ex)) self.set_criteria_error(self.name, ex) def set_name_regex(self, state): self.log.debug("Setting name_regex {0}".format(state)) self.query.name_regex = state self.clear_name_error() self.set_name() # # Permissions criteria # def set_perms(self): selected_perms = [] for index in self.perms.selectionModel().selectedIndexes(): selected_perms.append(self.perms_model.data(index, Qt.UserRole)) self.query.perms = selected_perms def invert_perms_selection(self): invert_list_selection(self.perms.selectionModel()) # # Save/Load tab # def save(self): """Return a dictionary of settings.""" if self.errors: raise TabFieldError("Field(s) are in error: {0}". format(" ".join(o.objectName() for o in self.errors))) settings = {} save_checkboxes(self, settings, ["criteria_expander", "notes_expander", "name_regex", "perms_equal"]) save_lineedits(self, settings, ["name"]) save_listviews(self, settings, ["perms"]) save_textedits(self, settings, ["notes"]) return settings def load(self, settings): load_checkboxes(self, settings, ["criteria_expander", "notes_expander", "name_regex", "perms_equal"]) load_lineedits(self, settings, ["name"]) load_listviews(self, settings, ["perms"]) load_textedits(self, settings, ["notes"]) # # Results runner # def run(self, button): # right now there is only one button. self.query.perms_equal = self.perms_equal.isChecked() # start processing self.busy.setLabelText("Processing query...") self.busy.show() self.raw_results.clear() self.thread.start() def update_complete(self, count): self.log.info("{0} common(s) found.".format(count)) # update sizes/location of result displays if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's columns; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeColumnsToContents() # If the permissions column width is too long, pull back # to a reasonable size header = self.table_results.horizontalHeader() if header.sectionSize(1) > 400: header.resizeSection(1, 400) if not self.busy.wasCanceled(): self.busy.setLabelText("Resizing the result table's rows; GUI may be unresponsive") self.busy.repaint() self.table_results.resizeRowsToContents() if not self.busy.wasCanceled(): self.busy.setLabelText("Moving the raw result to top; GUI may be unresponsive") self.busy.repaint() self.raw_results.moveCursor(QTextCursor.Start) self.busy.reset()
class BatchExtractQWidget(QWidget): def __init__(self, mainwindow: QMainWindow): super().__init__() self.mainwindow = mainwindow self.thread = None self.init_ui() def init_ui(self): ''' 添加简历文件标签 添加简历文件展示框 添加选择文件按钮 添加保存路径标签 添加保存路径展示框 添加修改保存路径窗口 ''' #添加组件并绑定事件 srcresume_label = QLabel('简历文件') self.srcresume_edit = QTextEdit() self.srcresume_edit.setReadOnly(True) resume_choose_button = QPushButton('选择简历文件') resume_choose_button.clicked.connect(self._choose_extract_file) save_label = QLabel('处理好的简历文件保存路径:') self.save_edit = QLineEdit() self.save_edit.setReadOnly(True) change_save_button = QPushButton('选择保存路径') change_save_button.clicked.connect(self._choose_save_dic) batch_extract_button = QPushButton(' 批量抽取 ') batch_extract_button.clicked.connect(self._batch_extract) #设置布局 hbox = QHBoxLayout() hbox.addStretch(10) hbox.addWidget(resume_choose_button) hbox1 = QHBoxLayout() hbox1.addWidget(save_label) hbox1.addWidget(self.save_edit) hbox1.addWidget(change_save_button) hbox2 = QHBoxLayout() hbox2.addStretch(10) hbox2.addWidget(batch_extract_button) vbox = QVBoxLayout() vbox.addWidget(srcresume_label) vbox.addWidget(self.srcresume_edit) vbox.addLayout(hbox) vbox.addLayout(hbox1) vbox.addLayout(hbox2) self.setLayout(vbox) #设置主窗体属性 self.mainwindow.setMinimumSize(700, 700) self.mainwindow.resize(700, 700) self.mainwindow.setWindowTitle('处理批量简历界面') self.mainwindow.center() #显示组件 self.show() #选择要抽取的文件 def _choose_extract_file(self): #选择文件 file_names = QFileDialog.getOpenFileNames(self, 'Open Files', config.TESTDATA_DIC) #处理文件名 file_name_list = [file_name for file_name in file_names[0]] text = '\n'.join(file_name_list) #显示到srcresume_edit上面 self.srcresume_edit.setText(text) #选择要保存的文件路径 def _choose_save_dic(self): #选择文件夹 dic_name = QFileDialog.getExistingDirectory(self, 'Change Save Dic', config.RESULTDATA_DIC) #显示到save_edit上面 self.save_edit.setText(str(dic_name)) #批量抽取简历信息 def _batch_extract(self): if self.thread != None and self.thread.isRunning(): QMessageBox.about(self, 'message', '正在处理简历中,请稍等!') return file_names = str(self.srcresume_edit.toPlainText()) save_dic = str(self.save_edit.text()) if file_names == '' and save_dic == '': #都没有内容 QMessageBox.about(self, 'message', '请选择要处理的简历文件和保存路径!') elif file_names == '': QMessageBox.about(self, 'message', '请选择处理的简历文件!') elif save_dic == '': QMessageBox.about(self, 'message', '请选择保存路径!') elif file_names != '' and save_dic != '': #将所有文件发送给服务器端 self.thread = QThread() self.object_client = ClientObject( resume_filepath_list=file_names.split('\n'), save_dic=save_dic) self.object_client.batch_extract_finished.connect( self._batch_extract_finish) self.object_client.moveToThread(self.thread) self.thread.started.connect(self.object_client.batch_extract) self.thread.start() QMessageBox.about(self, 'message', '正在处理简历中,请稍等!') pass #显示弹出框信息 def _batch_extract_finish(self): QMessageBox.about(self, 'message', '处理批量简历完成!') self.thread.quit() pass #BatchExtractQWidget class end