def handleSuggestPlaylist(self, result): if 'lists' in result: if self._lists != result['lists']: self._lists = result['lists'] self._suggestPlayListModel.clear() else: return lists = self._lists for playlist in lists: index = lists.index(playlist) _list = {} _list['listId'] = playlist['listId'] _list['listName'] = playlist['listName'] _list['playAll'] = playlist['playAll'] if self.isPlaylistCoverExisted(playlist['listName']): _list['cover'] = self.getPlaylistCoverPath(playlist[ 'listName']) else: d = PlaylistCover360Runnable( index, self, playlist['listName'], playlist['image']) QThreadPool.globalInstance().start(d) obj = QmlSuggestPlaylistObject(**_list) self._suggestPlayListModel.append(obj)
def __init__(self): super(CoverWorker, self).__init__() QThreadPool.globalInstance().setMaxThreadCount(4) self.downloadArtistCoverSuccessed.connect(self.cacheArtistCover) self.downloadAlbumCoverSuccessed.connect(self.cacheAlbumCover) self.artistCovers = {} self.albumCovers = {}
def downloadOnlineAlbumCover(self, artist, title, url, medias): f = self.onlineSongCoverPath(artist, title) if os.path.exists(f): return if url: d = AlbumCover360Runnable(self, artist, title, url, medias) QThreadPool.globalInstance().start(d)
def handleOnlineSongs(self, result): self._searchOnlineSongObjsListModel.clear() self._searchOnlineAlbumObjsListModel.clear() if 'songList' in result: songList = result['songList'] for song in songList: song['url'] = "" song['title'] = song['songName'] song['artist'] = song['singerName'] song['album'] = song['albumName'] song['songId'] = int(song['songId']) song['singerId'] = int(song['singerId']) song['albumId'] = int(song['albumId']) obj = QmlOnlineSongObject(**song) self._searchOnlineSongObjsListModel.append(obj) if 'albumList' in result and 'data' in result['albumList']: albumList = result['albumList']['data'] for album in albumList: index = albumList.index(album) album['artist'] = album['singerName'] album['album'] = album['albumName'] if self.isAlbumCoverExisted(album['artist'], album['album']): album['cover'] = self.getAlbumCoverPath(album['artist'], album['album']) else: d = AlbumCover360Runnable(index, self, album['artist'], album['album'], album['albumImage']) QThreadPool.globalInstance().start(d) obj = QmlOnlineAlbumObject(**album) self._searchOnlineAlbumObjsListModel.append(obj)
def load_children(self, item: QStandardItemModel = None) -> None: if not self.client: self.set_loading_cursor(False) return self.set_loading_cursor(True) loader = ContentLoader(self, item) QThreadPool.globalInstance().start(loader)
def run_worker(self, task, callback): """Runs a task in another thread. The `task` must be an object that implements a `run()` method. Completion is notified to the given `callback` function. """ worker = Worker(task) worker.finished.connect(callback) QThreadPool.globalInstance().start(worker)
def __init__(self, parent=None): super(CoverWorker, self).__init__(parent) QThreadPool.globalInstance().setMaxThreadCount(50) self.albumCoverThreadPool.setMaxThreadCount(50) self.artistCovers = {} self.albumCovers = {} self.onlineSongCovers = {} self.taskNumber = 0 self._artists = set() self._albums = set() self.initConnect()
def addSong(self, media): _songDict = self.updateTags(media) url = media['url'] self._songsDict[url] = _songDict songObj = QmlOnlineSongObject(**_songDict) self._songObjs[url] = songObj if not songObj.playlinkUrl: d = RequestSongRunnable(self, url) QThreadPool.globalInstance().start(d) else: self.downloadCover(_songDict)
def __init__(self, # A number *n* to create a pool of *n* simple threads, where each thread # lacks an event loop, so that it can emit but not receive signals. # This means that g_ may **not** be run in a thread of this pool, # without manually adding a event loop. If *n* < 1, the global thread # pool is used. maxThreadCount, # See parent_. parent=None): super().__init__(parent) if maxThreadCount < 1: self.threadPool = QThreadPool.globalInstance() else: self.threadPool = QThreadPool(self) self.threadPool.setMaxThreadCount(maxThreadCount)
def downloadArtistCover(self, artist): f = self.artistCoverPath(artist) if os.path.exists(f): return if ',' in artist: artist = artist.split(',') for item in artist: if item not in self._artists: self._artists.add(item) self.taskNumber += 1 d = CoverRunnable(self, item, qtype="artist") QThreadPool.globalInstance().start(d) else: if artist not in self._artists: self._artists.add(artist) self.taskNumber += 1 d = CoverRunnable(self, artist, qtype="artist") QThreadPool.globalInstance().start(d)
class AsyncPoolController(_AsyncAbstractController): def __init__(self, # A number *n* to create a pool of *n* simple threads, where each thread # lacks an event loop, so that it can emit but not receive signals. # This means that g_ may **not** be run in a thread of this pool, # without manually adding a event loop. If *n* < 1, the global thread # pool is used. maxThreadCount, # See parent_. parent=None): super().__init__(parent) if maxThreadCount < 1: self.threadPool = QThreadPool.globalInstance() else: self.threadPool = QThreadPool(self) self.threadPool.setMaxThreadCount(maxThreadCount) # See `_start`_. def _start(self, future): # Asynchronously invoke f_. apw = _AsyncPoolWorker(future) self.threadPool.start(apw) # See `_terminate`_. def _terminate(self): self.threadPool.waitForDone() del self.threadPool
def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self._counter = 0 self.init_ui() self._threadpool = QThreadPool() #self._threadpool = QtCore.QThreadPool.globalInstance() #self._threadpool.setMaxThreadCount(2) print("Multithreading with maximum {} threads" .format(self._threadpool.maxThreadCount())) self._timer = QTimer() self._timer.setInterval(1000) self._timer.timeout.connect(self.recurring_timer) self._timer.start()
def close(self): QThreadPool.globalInstance().clear() mediaPlayer.stop() configWorker.save() playlistWorker.savePlaylists()
def run(self): engine = NetEaseEngine() if self.qtype == "artist": url = engine.searchCoverByArtistName(self.name) elif self.qtype == "album": url = engine.searchCoverByAlbumName(self.name) coverWorker.receiveCover.emit(self.qtype, self.name, url) if __name__ == "__main__": app = QGuiApplication(sys.argv) artists = [] for artist in db.get_cursor().execute("Select name from artist").fetchall(): artists.append(artist) albums = [] for album in db.get_cursor().execute("Select name from album").fetchall(): albums.append(album) for artist in artists: d = DRunnable(artist, qtype="artist") QThreadPool.globalInstance().start(d) for album in albums: d = DRunnable(album, qtype="album") QThreadPool.globalInstance().start(d) exitCode = app.exec_() sys.exit(exitCode)
def restoreSongs(self, songs): d = RestoreDBRunnable(self, songs) QThreadPool.globalInstance().start(d)
def __init__(self, appctxt, BbRouter, download_dir, modules: List[Tuple[str, str]], last_dialog: QtWidgets.QDialog): """Download Dialog for selecting files to download/ignore Args: appctxt (ApplicationContext): fbs application context BbRouter (str): authentication token download_dir (str): download directory modules (List[Tuple[str, str]]): list of course name and course id tuples last_dialog (QtWidgets.QDialog): last dialog to return on back button press """ super(DownloadDialog, self).__init__() uic.loadUi(appctxt.get_resource("layouts/download.ui"), self) self.logger = Logger(appctxt) self.appctxt = appctxt self.BbRouter = BbRouter self.download_dir = download_dir self.modules = modules self.last_dialog = last_dialog dirLabel = self.findChild(QtWidgets.QLabel, "downloadDirLabel") dirLabel.setText("Downloading to: {}".format(download_dir)) # load icons backIcon = QtGui.QIcon(appctxt.get_resource("images/back.png")) self.folderIcon = QtGui.QIcon( appctxt.get_resource("images/folder.png")) self.videoIcon = QtGui.QIcon(appctxt.get_resource("images/video.png")) self.fileIcon = QtGui.QIcon(appctxt.get_resource("images/file.png")) # load buttons self.backButton = self.findChild(QtWidgets.QToolButton, "backButton") self.backButton.setIcon(backIcon) self.ignoreButton = self.findChild(QtWidgets.QPushButton, "ignoreButton") self.downloadButton = self.findChild(QtWidgets.QPushButton, "downloadButton") self.selectAllButton = self.findChild(QtWidgets.QPushButton, "selectAllButton") self.deselectAllButton = self.findChild(QtWidgets.QPushButton, "deselectAllButton") self.reloadButton = self.findChild(QtWidgets.QPushButton, "reloadButton") self.selectFilesButton = self.findChild(QtWidgets.QPushButton, "selectFilesButton") self.selectVideosButton = self.findChild(QtWidgets.QPushButton, "selectVideosButton") self.backButton.clicked.connect(self.handle_back) self.selectAllButton.clicked.connect(self.handle_select_all) self.deselectAllButton.clicked.connect(self.handle_unselect_all) self.ignoreButton.clicked.connect(self.handle_ignore) self.downloadButton.clicked.connect(self.handle_download) self.reloadButton.clicked.connect(self.handle_reload) self.selectFilesButton.clicked.connect(self.handle_select_files) self.selectVideosButton.clicked.connect(self.handle_select_videos) self.progressBar = self.findChild(QtWidgets.QProgressBar, "progressBar") self.progressBar.setValue(0) self.downloadProgressText = self.findChild(QtWidgets.QLabel, "downloadProgressText") self.downloadProgressText.setText( "Click download to start downloading files") # get download dir from NTU Learn and load tree self.threadPool = QThreadPool() self.tree = self.findChild(QtWidgets.QTreeWidget, "treeWidget") # NOTE do not show tree even though we have data as we want the user to # act on fresh download data self.storage = Storage(download_dir) self.data = self.storage.download_dir # add loading text node = QtWidgets.QTreeWidgetItem(self.tree) node.setText(0, "Click Reload to pull data from NTU Learn") self.show()
def createImageData(main_window, image_files, output_image, *finish_fn_args, info_var=None, convert_numpy=False, convert_raw=True, resample=False, target_size=0.125, crop_image=False, origin=(0, 0, 0), target_z_extent=(0, 0), tempfolder=None, finish_fn=None, **finish_fn_kwargs): # print("Create image data") if len(image_files) == 1: image = image_files[0] file_extension = os.path.splitext(image)[1] else: for image in image_files: file_extension = imghdr.what(image) if file_extension != 'tiff': main_window.e( '', '', 'When reading multiple files, all files must TIFF formatted.' ) error_title = "Read Error" error_text = "Error reading file: ({filename})".format( filename=image) displayFileErrorDialog(main_window, message=error_text, title=error_title) return if file_extension in ['.mha', '.mhd']: createProgressWindow(main_window, "Converting", "Converting Image") image_worker = Worker(loadMetaImage, main_window, image, output_image, info_var, resample, target_size, crop_image, origin, target_z_extent, convert_numpy, convert_raw, tempfolder) elif file_extension in ['.npy']: createProgressWindow(main_window, "Converting", "Converting Image") image_worker = Worker(loadNpyImage, image, output_image, info_var, resample, target_size, crop_image, origin, target_z_extent) elif file_extension in ['tif', 'tiff', '.tif', '.tiff']: reader = vtk.vtkTIFFReader() reader.AddObserver("ErrorEvent", main_window.e) createProgressWindow(main_window, "Converting", "Converting Image") image_worker = Worker(loadTif, image_files, reader, output_image, convert_numpy, info_var) elif file_extension in ['.raw']: if 'file_type' in info_var and info_var['file_type'] == 'raw': createConvertRawImageWorker(main_window, image, output_image, info_var, resample, target_size, crop_image, origin, target_z_extent, finish_fn) return else: #if we aren't given the image dimensions etc, the user needs to enter them main_window.raw_import_dialog = createRawImportDialog( main_window, image, output_image, info_var, resample, target_size, crop_image, origin, target_z_extent, finish_fn) dialog = main_window.raw_import_dialog['dialog'].show() return else: main_window.e( '', '', 'File format is not supported. Accepted formats include: .mhd, .mha, .npy, .tif, .raw' ) error_title = "Error" error_text = "Error reading file: ({filename})".format( filename=image) displayFileErrorDialog(main_window, message=error_text, title=error_title) return main_window.progress_window.setValue(10) image_worker.signals.progress.connect( partial(progress, main_window.progress_window)) if finish_fn is not None: image_worker.signals.finished.connect( lambda: finish_fn(*finish_fn_args, **finish_fn_kwargs)) main_window.threadpool = QThreadPool() main_window.threadpool.start(image_worker) print("Started worker")
engine = NetEaseEngine() if self.qtype == "artist": url = engine.searchCoverByArtistName(self.name) elif self.qtype == "album": url = engine.searchCoverByAlbumName(self.name) coverWorker.receiveCover.emit(self.qtype, self.name, url) if __name__ == '__main__': app = QGuiApplication(sys.argv) artists = [] for artist in db.get_cursor().execute( 'Select name from artist').fetchall(): artists.append(artist) albums = [] for album in db.get_cursor().execute('Select name from album').fetchall(): albums.append(album) for artist in artists: d = DRunnable(artist, qtype="artist") QThreadPool.globalInstance().start(d) for album in albums: d = DRunnable(album, qtype="album") QThreadPool.globalInstance().start(d) exitCode = app.exec_() sys.exit(exitCode)
def setupUi(self): serialport = QLabel("GPIB port", self) self.serialportEdit = QLineEdit(self.k2001aport, self) self.runstopButton = QPushButton("START", self) self.clearButton = QPushButton("Clear", self) schroll_lbl = QLabel("Schroll elapsed time ", self) self.combo0 = QComboBox(self) mylist0 = ["100", "200", "400", "600", "800", "1000", "1500", "2000"] self.combo0.addItems(mylist0) self.combo0.setCurrentIndex(mylist0.index(str(self.schroll_pts))) ############################################## g0_1 = QGridLayout() g0_1.addWidget(serialport, 0, 0) g0_1.addWidget(self.serialportEdit, 0, 1) g0_1.addWidget(schroll_lbl, 0, 2) g0_1.addWidget(self.combo0, 0, 3) g0_1.addWidget(self.runstopButton, 0, 4) g0_1.addWidget(self.clearButton, 0, 5) ############################################## # set graph and toolbar to a new vertical group vcan self.pw1 = pg.PlotWidget() ############################################## # create table self.tableWidget = self.createTable() ############################################## # SET ALL VERTICAL COLUMNS TOGETHER vbox = QVBoxLayout() vbox.addLayout(g0_1) vbox.addWidget(self.pw1) hbox = QHBoxLayout() hbox.addLayout(vbox) hbox.addWidget(self.tableWidget) self.threadpool = QThreadPool() print("Multithreading in the K2001A with maximum %d threads" % self.threadpool.maxThreadCount()) self.isRunning = False self.setLayout(hbox) self.setWindowTitle("Test Keithley 2001A") # PLOT 2 settings # create plot and add it to the figure canvas self.p1 = self.pw1.plotItem self.curve1 = self.p1.plot(pen='w') # create plot and add it to the figure self.p0_1 = pg.ViewBox() self.curve2 = pg.PlotCurveItem(pen='r') self.p0_1.addItem(self.curve2) # connect respective axes to the plot #self.p1.showAxis('left') self.p1.getAxis('left').setLabel("Voltage", units="V", color='yellow') self.p1.showAxis('right') self.p1.getAxis('right').setLabel("Arb unit, 1023=1.1V", units="", color='red') self.p1.scene().addItem(self.p0_1) self.p1.getAxis('right').linkToView(self.p0_1) self.p0_1.setXLink(self.p1) self.p1.getAxis('bottom').setLabel("Points", units="", color='yellow') # Use automatic downsampling and clipping to reduce the drawing load self.pw1.setDownsampling(mode='peak') self.pw1.setClipToView(True) # Initialize and set titles and axis names for both plots self.clear_vars_graphs() self.combo0.activated[str].connect(self.onActivated0) # run or cancel the main script self.runstopButton.clicked.connect(self.runstop) self.clearButton.clicked.connect(self.set_clear) self.clearButton.setEnabled(False)
def using_q_runnable(): app = QCoreApplication([]) runnable = Runnable() QThreadPool.globalInstance().start(runnable) sys.exit(app.exec_())
def loadSoundsConf(self): loadConf=load_conf(config="sounds.json") loadConf.signals.hasConf_d.connect(self.storeSoundsInfo) loadConf.signals.hasError.connect(logging.error) QThreadPool.globalInstance().start(loadConf)
def saveExercises_(self): saveMe=UEC('exercise.json',self.edit_exercise_model.item) saveMe.signals.hasErrors.connect(logging.error) saveMe.signals.finished.connect(self.reloadConf) QThreadPool.globalInstance().start(saveMe)
def initUI(self): #self.hbox = QVBoxLayout(self) self.gridLayout = QGridLayout(self) self.gridLayout.setContentsMargins(11, 11, 11, 11) self.lblCodec = QLabel("Codec", self) self.lblAlpha = QLabel("Alpha", self) self.lblFrameRate = QLabel("Frame Rate", self) self.gridLayout.addWidget(self.lblCodec, 0, 0, 1, 1) self.gridLayout.addWidget(self.lblAlpha, 0, 1, 1, 1) self.gridLayout.addWidget(self.lblFrameRate, 0, 2, 1, 1) self.comboCodec = QComboBox(self) self.comboCodec.setMinimumWidth(80) self.comboCodec.addItem("UT Video") self.comboAlpha = QComboBox(self) self.comboAlpha.setMinimumWidth(80) self.comboAlpha.addItem("No Alpha") self.comboAlpha.addItem("with Alpha") self.comboFrameRate = QComboBox(self) self.comboFrameRate.setMinimumWidth(80) self.comboFrameRate.addItem("24.00") self.comboFrameRate.addItem("30.00") self.buttonCompress = QPushButton("Compress", self) self.buttonCompress.clicked[bool].connect(self.compressPress) self.gridLayout.addWidget(self.comboCodec, 1, 0, 1, 1) self.gridLayout.addWidget(self.comboAlpha, 1, 1, 1, 1) self.gridLayout.addWidget(self.comboFrameRate, 1, 2, 1, 1) self.gridLayout.addWidget(self.buttonCompress, 1, 3, 1, 1) self.pbList = [] for i in range(len(self.inList)): self.tempPB = QProgressBar(self) self.tempPB.setMinimum(0) self.tempPB.setMaximum(100) self.tempPB.setTextVisible(True) self.tempPB.setFormat(str(self.inList[i]) + " %p%") self.tempPB.setAlignment(Qt.AlignCenter) self.tempPB.setValue(0) if i == 0: self.defaulStyle = self.tempPB.styleSheet() self.gridLayout.addWidget(self.tempPB, i + 2, 0, 1, 4) self.pbList.append(self.tempPB) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, len(self.inList) + 2, 0, 1, 1) self.comboCodec.activated[str].connect(self.chooseCodec) self.comboAlpha.activated[str].connect(self.chooseAlpha) self.comboFrameRate.activated[str].connect(self.chooseFrameRate) self.setGeometry(300, 300, 390, 100) self.gridLayout.setGeometry(QRect(19, 19, 581, 94)) self.setWindowTitle('FFMpeg Python Compressor') self.show() self.threadpool = QThreadPool() print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
# -*- coding: utf-8 -*- # async guidance from # https://martinfitzpatrick.name/article/multithreading-pyqt-applications-with-qthreadpool/ # (didn't use inbuilt python async/await since it requires an event/thread loop which it seems # qt itself isn't playing nice with... maybe?) from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool threadpool = QThreadPool() print("Multithreading with maximum %d threads" % threadpool.maxThreadCount()) class Worker(QRunnable): ''' Worker thread Inherits from QRunnable to handler worker thread setup, signals and wrap-up. :param callback: The function callback to run on this worker thread. Supplied args and kwargs will be passed through to the runner. :type callback: function :param args: Arguments to pass to the callback function :param kwargs: Keywords to pass to the callback function ''' def __init__(self, fn, *args, **kwargs): super(Worker, self).__init__() # Store constructor arguments (re-used for processing) self.fn = fn
def __init__(self, *args, obj=None, **Kwargs): super(MainWindow, self).__init__(*args, **Kwargs) self.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint) self.setupUi(self) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.setSizePolicy(sizePolicy) self.setGeometry(0, 0, gv.WIDTH, gv.HEIGHT) self.setMaximumSize(QSize(gv.WIDTH, gv.HEIGHT)) # Connect to database self.db = cuf.db_connect("QSQLITE", "pa_db.db") self.db.open() ################################################################################### #region MainWindow SETUP # Toggle function self.menu_toggle_btn.clicked.connect( lambda status, self=self: uif.toggle_menu(self, status)) #Page change settings self.home_btn.clicked.connect(lambda status, self=self, idx=0: uif. change_page(self, idx, status)) self.anchor_btn.clicked.connect(lambda status, self=self, idx=1: uif. change_page(self, idx, status)) self.sub_btn.clicked.connect(lambda status, self=self, idx=2: uif. change_page(self, idx, status)) self.settings_btn.clicked.connect(lambda status, self=self, idx=3: uif. change_page(self, idx, status)) self.add_btn.clicked.connect(lambda status, self=self, idx=4: uif. change_page(self, idx, status)) #endregion MainWindow SETUP ################################################################################### # set Current ####################################################################### #region PAGE ONE CONFIG # define Slot for update image. Page1 = self.page1 currentFrame = None # store value of current frame for @pyqtSlot() def processedImage(lpText, img): qt_img = convert_cv_to_qt(img) self.page1.feed.setPixmap(qt_img) def convert_cv_to_qt(img): h, w, ch = img.shape bytes_per_line = ch * w convert_cv_to_Qt_format = QImage(img.data, w, h, QImage.Format_RGB888) currentFrame = img p = convert_cv_to_Qt_format.scaled(gv.FeedWidth, gv.FeedHeight, Qt.KeepAspectRatio) return QPixmap.fromImage(p) # function to emit requestFrame signl def scanFrame(): # change global status of image process gv.ProcessFrame = True self.feedthread = QThreadPool() self.feedWorker = FeedWorker() self.feedWorker.signals.processedImage.connect(processedImage) self.feedthread.start(self.feedWorker) Page1.cam_scan_btn.clicked.connect(scanFrame) #endregion PAGE ON CONFIG ######################################################################## ###################################################################### #region Page Two Config page2 = self.page2 query = QSqlQuery("SELECT * FROM Client") page2.db_table.show_data(query) #endregion Page Two Config ###################################################################### ###################################################################### #region Page Three Config page3 = self.page3 query = QSqlQuery("SELECT * FROM Client") page3.db_table.show_data(query) #endregion Page Three Config ###################################################################### # clos database connection self.db.close()
class MainWindow(QMainWindow): """ You can use @pyqtSlot(int) syntax (with parameters), or you can pass this, but it make code more readable. Вы можете использовать синтаксис объявления слотов @pyqtSlot(int) с указанием типа передаваемых значений, или опустить его вовсе, однако это делает код нагляднее и позволяет быстро понять, что слот, а что - функция. """ def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self._counter = 0 self.init_ui() self._threadpool = QThreadPool() #self._threadpool = QtCore.QThreadPool.globalInstance() #self._threadpool.setMaxThreadCount(2) print("Multithreading with maximum {} threads" .format(self._threadpool.maxThreadCount())) self._timer = QTimer() self._timer.setInterval(1000) self._timer.timeout.connect(self.recurring_timer) self._timer.start() def init_ui(self): layout = QVBoxLayout() self._label = QLabel("Start") b = QPushButton("Start QRunnable") b.pressed.connect(self.start_new_runnable) layout.addWidget(self._label) layout.addWidget(b) w = QWidget() w.setLayout(layout) self.setCentralWidget(w) @pyqtSlot(int) def thread_progress_fn(self, n): print("{}% done".format(n)) @pyqtSlot(object) def thread_print_output(self, s): print('Result: {}'.format(s)) @pyqtSlot() def thread_complete(self): print("QRunnable worker COMPLETE!") @pyqtSlot(tuple) def thread_error(self, err): QMessageBox.warning(self, "Warning!", err[1], QMessageBox.Ok) print('Error {}\n{}'.format(err[1], err[2])) @pyqtSlot() def start_new_runnable(self): # Pass the function to execute worker = Worker(1, debug=True) # Any other args, kwargs are passed to the run function worker.signals.result.connect(self.thread_print_output) worker.signals.finished.connect(self.thread_complete) worker.signals.progress.connect(self.thread_progress_fn) worker.signals.error.connect(self.thread_error) worker.setAutoDelete(True) # Execute (tryStart() better than start() ) if self._threadpool.tryStart(worker) is False: print("Can't create worker!") QMessageBox.warning(self, "Warning!", "Can't create worker!", QMessageBox.Ok) @pyqtSlot() def recurring_timer(self): self._counter += 1 self._label.setText("Counter: {}".format(self._counter)) print('Active thread count: {}'.format(self._threadpool.activeThreadCount())) def closeEvent(self, event): """Main window closed, override PyQt5 widget function""" print('Try to exit, active thread count: {}'.format(self._threadpool.activeThreadCount())) reply = QMessageBox.question(self, 'Message', "Are you sure to quit?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self._threadpool.waitForDone() self._timer.stop() event.accept() else: event.ignore()
class ActionWidget(QWidget): def __init__(self, parent=None, dev=None): super(ActionWidget, self).__init__(parent) self.setStyleSheet("background-color: black;") vbox = QVBoxLayout() self.dev = dev self.action = ActionLabel(self) self.timerlabel = TimerLabel(self) vbox.addWidget(self.action) vbox.addWidget(self.timerlabel) vbox.setContentsMargins(QMargins(0, 0, 0, 0)) self.setLayout(vbox) # Workers self.threadpool = QThreadPool() self.quit_thread = False self._active_listen = True # Timer self._interval_done = True self._interval_time = 0 self._timer = QTimer() self._timer.timeout.connect(self.iterate) # Sound self.timer_sound = QSound(TIMER_FX) self.interval_sound = QSound(TIMER_FX3) # Button self._button = None # ON EXIT self.thread_done = False self.start_action_signal() def toggle_led(self): self._active_listen = False self.dev.wr_cmd("pyb.LED(1).toggle()") self.action.toggle() self._active_listen = True if self.action.value: self._timer.start(1000) else: self._timer.stop() def update_state(self, state): self.action.toggle() self._button.pushbutton(True) if state == "ON": self._timer.start(1000) else: self._timer.stop() def listen_action_state(self, progress_callback): while not self.quit_thread: if self._active_listen: if self.dev.serial.readable() and self.dev.serial.in_waiting: state = self.dev.serial.readline().decode().replace( '\r\n', '') progress_callback.emit(state) print(state) time.sleep(0.1) print('Thread Done!') self.thread_done = True def start_action_signal(self): # Pass the function to execute # Any other args, kwargs are passed to the run function worker_led = Worker(self.listen_action_state) # worker.signals.result.connect(self.print_output) # worker.signals.finished.connect(self.thread_complete) # worker.signals.progress.connect(self.progress_fn) worker_led.signals.progress.connect(self.update_state) # Execute self.threadpool.start(worker_led) def iterate(self): try: if self._interval_done: interval_type, self._interval_time = next( self.timerlabel._int_gen) self.action.setStyleSheet( self.action._type_action_style[interval_type]) self.action.setText(self.action._type_action[interval_type]) self.timerlabel.setText( str(timedelta( seconds=self._interval_time)).split('.')[0][2:]) self._interval_done = False else: self._interval_time -= 1 if self._interval_time > 0 and self._interval_time <= 3: self.timer_sound.play() elif self._interval_time == 0: self._interval_done = True self.interval_sound.play() self.timerlabel.setText( str(timedelta( seconds=self._interval_time)).split('.')[0][2:]) except StopIteration: self.toggle_led() self.finish_state() self.timerlabel.reset_intervals() self._button.pushbutton(True) def finish_state(self): self.action.setStyleSheet(self.action.finished_bg) self.action.setText("Finished") self.timerlabel.setText("00:00") def closeEvent(self, event): self._timer.stop() self.quit_thread = True try: while not self.thread_done: time.sleep(0.5) print("shutdown...") except Exception as e: print(e) print("SHUTDOWN COMPLETE") sys.exit()
def cacheAlbumCover(self, artist, album, url): # print artist, album, url self.albumCovers[album] = url def downloadArtistCover(self, artist): d = CoverRunnable(self, artist, qtype="artist") QThreadPool.globalInstance().start(d) def downloadAlbumCover(self, artist, album): d = CoverRunnable(self, artist, album, qtype="artist") QThreadPool.globalInstance().start(d) if __name__ == '__main__': app = QGuiApplication(sys.argv) QThreadPool.globalInstance().setMaxThreadCount(10) from peewee import * MusicDBFile = '/home/djf/.config/DeepinMusic3/music.db' db = SqliteDatabase(MusicDBFile, threadlocals=True) db.connect() coverWorker = CoverWorker() artists = [] for artist in db.get_cursor().execute('Select name from artist').fetchall(): artists.append(artist[0]) for artist in artists: coverWorker.downloadArtistCover(artist) albums = [] for artist, album in db.get_cursor().execute( 'Select artist, name from album').fetchall():
class MainWindow(QWidget): def __init__(self, inList): super().__init__() self.inList = inList self.codec = 'utvideo' self.alpha = False self.frameRate = 24 self.defaulStyle = '' self.initUI() def initUI(self): #self.hbox = QVBoxLayout(self) self.gridLayout = QGridLayout(self) self.gridLayout.setContentsMargins(11, 11, 11, 11) self.lblCodec = QLabel("Codec", self) self.lblAlpha = QLabel("Alpha", self) self.lblFrameRate = QLabel("Frame Rate", self) self.gridLayout.addWidget(self.lblCodec, 0, 0, 1, 1) self.gridLayout.addWidget(self.lblAlpha, 0, 1, 1, 1) self.gridLayout.addWidget(self.lblFrameRate, 0, 2, 1, 1) self.comboCodec = QComboBox(self) self.comboCodec.setMinimumWidth(80) self.comboCodec.addItem("UT Video") self.comboAlpha = QComboBox(self) self.comboAlpha.setMinimumWidth(80) self.comboAlpha.addItem("No Alpha") self.comboAlpha.addItem("with Alpha") self.comboFrameRate = QComboBox(self) self.comboFrameRate.setMinimumWidth(80) self.comboFrameRate.addItem("24.00") self.comboFrameRate.addItem("30.00") self.buttonCompress = QPushButton("Compress", self) self.buttonCompress.clicked[bool].connect(self.compressPress) self.gridLayout.addWidget(self.comboCodec, 1, 0, 1, 1) self.gridLayout.addWidget(self.comboAlpha, 1, 1, 1, 1) self.gridLayout.addWidget(self.comboFrameRate, 1, 2, 1, 1) self.gridLayout.addWidget(self.buttonCompress, 1, 3, 1, 1) self.pbList = [] for i in range(len(self.inList)): self.tempPB = QProgressBar(self) self.tempPB.setMinimum(0) self.tempPB.setMaximum(100) self.tempPB.setTextVisible(True) self.tempPB.setFormat(str(self.inList[i]) + " %p%") self.tempPB.setAlignment(Qt.AlignCenter) self.tempPB.setValue(0) if i == 0: self.defaulStyle = self.tempPB.styleSheet() self.gridLayout.addWidget(self.tempPB, i + 2, 0, 1, 4) self.pbList.append(self.tempPB) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gridLayout.addItem(spacerItem, len(self.inList) + 2, 0, 1, 1) self.comboCodec.activated[str].connect(self.chooseCodec) self.comboAlpha.activated[str].connect(self.chooseAlpha) self.comboFrameRate.activated[str].connect(self.chooseFrameRate) self.setGeometry(300, 300, 390, 100) self.gridLayout.setGeometry(QRect(19, 19, 581, 94)) self.setWindowTitle('FFMpeg Python Compressor') self.show() self.threadpool = QThreadPool() print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) ''' Button functions ''' def chooseAlpha(self, text): switcher = {"No Alpha": False, "with Alpha": True} self.alpha = switcher.get(text, "Invalid day of week") #print (self.alpha) def chooseCodec(self, text): switcher = {"UT Video": "utvideo"} self.codec = switcher.get(text, "Invalid day of week") #print (self.codec) def chooseFrameRate(self, text): self.frameRate = float(text) #print (self.frameRate) def currentData(self, widget): return widget.currentText() def pushTest(self): print("pressed") for pb in self.pbList: count = 0 while count < TIME_LIMIT: count += 2 time.sleep(0.5) pb.setValue(count) #time as HH:MM:SS.mm as timeobject, framerate as float def timeToFrames(self, t, frameRate): return floor(t.hour * 60 * 60 * 60 * frameRate + t.minute * 60 * 60 * frameRate + t.second * frameRate + t.microsecond / 1000000 * frameRate) #time as string as "HH:MM:SS.mm" def stringToTime(self, timeString): return (datetime.strptime(timeString, "%H:%M:%S.%f")) def progress_fn(self, n): #print("%d%% done" % n) return n def getProcessPercent(self, process, frameRate, pySignalObj): for line in process.stdout: frameLine = re.search("^frame=.*fps=", line) durationLine = re.search("^ Duration: ", line) if (durationLine): dTimeString = re.findall("\d{1,2}:\d{2}:\d{2}.\d{2}", line) if (dTimeString): duration = self.stringToTime(dTimeString[0]) durationFrames = self.timeToFrames(duration, frameRate) if (frameLine): frameString = re.findall("[0-9]+", line) if (frameString): #test for errors here try: #self.worker.signals.progress.emit(int(frameString[0])/durationFrames * 100) pySignalObj.emit( int(frameString[0]) / durationFrames * 100) except: #self.worker.signals.progress.emit(100) print('setting to -1') raise Exception('error in ffmepg compression') #pySignalObj.emit(-1) #traceback.print_exc() #exctype, value = sys.exc_info()[:2] #self.worker.signals.error.emit( (exctype, value, traceback.format_exc()) ) #self.worker.signals.error.emit( ('error') ) break def execute_this_fn(self, path, codec, alpha, frameRate, progress_callback, errorFFMPEG_callback): #print(path) pyCompression = pyFFMEGCompress(path, codec, alpha, frameRate) ffProcess = pyCompression.ffmpegCompress() #with kwargs kwargs = { 'progress_callback': progress_callback, 'errorFFMPEG_callback': errorFFMPEG_callback } pyCompression.printProcess(ffProcess, **kwargs) #would be nicer to use the class method instead here but doesn't update only prints the final result #progress_callback.emit(pyCompression.printProcess(ffProcess)) ''' #self.getProcessPercent(ffProcess, frameRate, progress_callback, errorFFMPEG_callback) for line in ffProcess.stdout: frameLine = re.search("^frame=.*fps=", line) durationLine = re.search("^ Duration: ", line) if (durationLine): dTimeString = re.findall("\d{1,2}:\d{2}:\d{2}.\d{2}", line) if (dTimeString): duration = self.stringToTime(dTimeString[0]) durationFrames = self.timeToFrames(duration,frameRate) if (frameLine): frameString = re.findall("[0-9]+", line) if (frameString): #test for errors here try: #self.worker.signals.progress.emit(int(frameString[0])/durationFrames * 100) progress_callback.emit(int(frameString[0])/durationFrames * 100) #pySignalObj.emit(int(frameString[0])/durationFrames * 100) except: #self.worker.signals.progress.emit(100) #print('setting to -1') errorFFMPEG_callback.emit(path + " %p%") #raise Exception ('error in ffmepg compression') #pySignalObj.emit(-1) #traceback.print_exc() #exctype, value = sys.exc_info()[:2] #self.worker.signals.error.emit( (exctype, value, traceback.format_exc()) ) #self.worker.signals.error.emit( ('error') ) break ''' return "Done." def print_output(self, s): print("Printing output " + str(s)) def thread_complete(self, r): self.i = self.i + 1 print("THREAD COMPLETE! WITH ERROR " + str(r)) def errorPB(self, err): for o in self.pbList: if o.format() == err: o.setValue(100) o.setStyleSheet( "QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );border-radius: 3px; border: 1px solid #a60233;}QProgressBar{color:white}" ) #("QProgressBar::chunk { background-color: red; }") #pb.setStyleSheet("QProgressBar::chunk { background-color: red; }") o.setFormat(o.format() + " - Error") def resetProgressBar(self, pb, text): pb.setValue(0) pb.setFormat(text + ' %p%') pb.setStyleSheet(self.defaulStyle) #figure out how to queue tasks better def compressPress(self): self.i = 0 for self.i in range(len(self.pbList)): i = self.i print(i) print(self.i) #print(self.pbList[i].format()) #print(self.pbList[i]) #print(self.inList[i]) self.resetProgressBar(self.pbList[i], self.inList[i]) worker = Worker( self.execute_this_fn, self.inList[i], self.codec, self.alpha, self.frameRate ) # Any other args, kwargs are passed to the run function worker.signals.result.connect(self.print_output) #worker.signals.progress.connect(self.progress_fn) worker.signals.progress.connect(self.pbList[i].setValue) #worker.signals.errorFFMPEG.connect(lambda n: self.errorPB(n,self.pbList[i])) worker.signals.errorFFMPEG.connect(self.errorPB) worker.signals.finished.connect(self.thread_complete) #This sort of worked, lambda function evals at the time of error not at the time of assignment #so the i is not the actual i of the erroring signal #worker.signals.error.connect(lambda n: self.errorPB(n,self.pbList[i]))#,self.tempPB.setFormat)) # Execute self.threadpool.start(worker)
def downloadAlbumCover(self, artist, album): d = CoverRunnable(self, artist, album, qtype="artist") QThreadPool.globalInstance().start(d)
class Test(QObject): threadPool = QThreadPool() message = pyqtSignal(str, int)
class Ui(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): QtWidgets.QMainWindow.__init__(self) # Call the inherited classes __init__ method # uic.loadUi('gui/ui/MA35D1-Writer.ui', self) # Load the .ui file self.setupUi(self) self.setWindowTitle("MA35D1 NuWriter") self.addMedia() self.text_browser = QtWidgets.QTextBrowser(self) self.verticalLayout.addWidget(self.text_browser) # Install the custom output stream # sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) self.outputStream = EmittingStream(textWritten=self.normalOutputWritten) sys.stdout = self.outputStream sys.stderr = self.outputStream self.initToolSetting() # Attach self.browseDDR_btn.clicked.connect(self.iniBrowse) self.attach_btn.clicked.connect(self.doAttach) self.threadpool = QThreadPool() def addMedia(self): # DEV_DDR_SRAM = 0 self.ddrPage = MediaPage(DEV_DDR_SRAM, self) self.tabMedia.addTab(self.ddrPage, "DDR/SRAM") # DEV_NAND = 1 self.nandPage = MediaPage(DEV_NAND, self) self.tabMedia.addTab(self.nandPage, "NAND") # DEV_SD_EMMC = 2 self.sdEmmcPage = MediaPage(DEV_SD_EMMC, self) self.tabMedia.addTab(self.sdEmmcPage, "SD/EMMC") # DEV_SPINOR = 3 self.spiNorPage = MediaPage(DEV_SPINOR, self) self.tabMedia.addTab(self.spiNorPage, "SPI NOR") # DEV_SPINAND = 4 self.spiNandPage = MediaPage(DEV_SPINAND, self) self.tabMedia.addTab(self.spiNandPage, "SPI NAND") # DEV_OTP = 6 def __del__(self): # Restore sys.stdout sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ ################################################################################################ # 'MA35D1.ini' ################################################################################################ def initToolSetting(self): self.iniFileName = 'MA35D1.ini' iniFileName = self.iniFileName # https://pyinstaller.readthedocs.io/en/stable/runtime-information.html if getattr(sys, 'frozen', False): # we are running in a bundle app_dir = os.path.dirname(os.path.abspath(sys.executable)) else: # we are running in a normal Python environment app_dir = os.path.dirname(os.path.abspath(__file__)) iniFilePath = os.path.join(app_dir, iniFileName) if not os.path.exists(iniFilePath): open(iniFilePath, 'w', encoding='utf-8') self.conf = configparser.ConfigParser() self.conf.read(iniFilePath, encoding='utf-8') self.iniFilePath = iniFilePath if not self.conf.has_section('Attach'): self.conf.add_section('Attach') self.ddrFileLineEdit.setText(self.conf.get('Attach', 'Ini File', fallback='')) sections = ['DDR', 'NAND', 'SD', 'SPINOR', 'SPINAND', 'OTP', 'USBH'] for section in sections: if not self.conf.has_section(section): self.conf.add_section(section) if section == 'DDR': page = self.ddrPage elif section == 'NAND': page = self.nandPage elif section == 'SD': page = self.sdEmmcPage elif section == 'SPINOR': page = self.spiNorPage elif section == 'SPINAND': page = self.spiNandPage else: # print(f'{section} is not supported yet') continue page.imgPathLine.setText(self.conf.get(section, 'write file', fallback='')) page.imgAddress.setText(self.conf.get(section, 'write addr', fallback='')) try: page.radioPack.setChecked(self.conf.getboolean(section, 'write pack', fallback=False)) except: if section != 'DDR': print(f'fail to set pack in {section}') pass option = self.conf.get(section, 'write option', fallback='') try: if option == "Verify": page.verifyWrite.setChecked(True) elif option == "Raw": page.rawWrite.setChecked(True) elif option == "Execute": page.optExecute.setChecked(True) elif option != '' and option != 'None': print(f'unknown optioin {option}') except: print(f'fail to set optioin {option} in {section}') pass page.fileSave.setText(self.conf.get(section, 'read file', fallback='')) page.readStart.setText(self.conf.get(section, 'read start', fallback='')) page.readEnd.setText(self.conf.get(section, 'read length', fallback='')) option = self.conf.get(section, 'read option', fallback='') try: if option == "WithBad": page.readWithBad.setChecked(True) elif option != '' and option != 'None': print(f'unknown optioin {option}') except: print(f'fail to set optioin {option} in {section}') pass if section == 'DDR': continue if section == 'SD': page.reservedSize.setText(self.conf.get(section, 'storage size', fallback='')) page.optEject.setChecked(self.conf.get(section, 'storage option', fallback='') == 'Eject') else: page.eraseStart.setText(self.conf.get(section, 'erase start', fallback='')) page.eraseEnd.setText(self.conf.get(section, 'erase length', fallback='')) page.eraseAll.setChecked(self.conf.getboolean(section, 'erase all', fallback=False)) # def closeEvent(self, evt): # pass def normalOutputWritten(self, text): """Append text to the QTextEdit.""" # Maybe QTextEdit.append() works as well, but this is how I do it: self.text_browser.insertPlainText(text) self.text_browser.moveCursor(QtGui.QTextCursor.End) def iniBrowse(self): filename = "" # Fix for crash in X on Ubuntu 14.04 filename, _ = QtWidgets.QFileDialog.getOpenFileName() if filename != "": self.ddrFileLineEdit.setText(filename) ################################################################################################ # command line ################################################################################################ @QtCore.pyqtSlot() def doAttach(self): iniFile = self.ddrFileLineEdit.text() self.conf.set('Attach', 'Ini File', iniFile) self.conf.write(open(self.iniFilePath, 'w', encoding='utf-8')) self.text_browser.clear() # print(f'do_attach({iniFile})') # do_attach(iniFile) worker = Worker(do_attach, iniFile) # Execute self.threadpool.start(worker) # def do_img_read(media, start, out_file_name, length=0x1, option=OPT_NONE) -> None: @QtCore.pyqtSlot(int, str, str, str, int, bool) def doImgRead(self, media, startStr, fileStr, lengthStr, option, isall=False): if isall: start = 0 length = 0 else: try: start = int(startStr, 0) & 0xffffffff except: start = 0 try: length = int(lengthStr, 0) & 0xffffffff except: length = 0x1 self.text_browser.clear() if media in [DEV_DDR_SRAM, DEV_NAND, DEV_SPINOR, DEV_SPINAND, DEV_SD_EMMC]: if media == DEV_DDR_SRAM: section = 'DDR' elif media == DEV_NAND: section = 'NAND' elif media == DEV_SPINOR: section = 'SPINOR' elif media == DEV_SPINAND: section = 'SPINAND' else: section = 'SD' self.conf.set(section, 'read file', fileStr) self.conf.set(section, 'read start', startStr) self.conf.set(section, 'read length', lengthStr) if option == OPT_NONE: self.conf.set(section, 'read option', "None") elif option == OPT_WITHBAD: self.conf.set(section, 'read option', "WithBad") if isall: self.conf.set(section, 'read all', 'true') else: self.conf.set(section, 'read all', 'false') self.conf.write(open(self.iniFilePath, 'w', encoding='utf-8')) # print(f'do_img_read({media}, {start}, {fileStr}, {length}, {option})') # do_img_read(media, start, fileStr, length, option) worker = Worker(do_img_read, media, start, fileStr, length, option) # Execute self.threadpool.start(worker) # def do_img_program(media, start, image_file_name, option=OPT_NONE) -> None: @QtCore.pyqtSlot(int, str, str, int, bool) def doImgProgram(self, media, startStr, image_file_name, option, ispack=False): try: start = int(startStr, 0) & 0xffffffff except: start = 0 self.text_browser.clear() if media == DEV_DDR_SRAM: section = 'DDR' self.conf.set(section, 'write file', image_file_name) self.conf.set(section, 'write addr', startStr) if option == 0: self.conf.set(section, 'write option', "None") elif option == 2: self.conf.set(section, 'write option', "Execute") elif media in [DEV_NAND, DEV_SPINOR, DEV_SPINAND, DEV_SD_EMMC]: if media == DEV_NAND: section = 'NAND' elif media == DEV_SPINOR: section = 'SPINOR' elif media == DEV_SPINAND: section = 'SPINAND' else: section = 'SD' self.conf.set(section, 'write file', image_file_name) self.conf.set(section, 'write addr', startStr) if ispack: self.conf.set(section, 'write pack', 'true') else: self.conf.set(section, 'write pack', 'false') elif media == DEV_OTP: section = 'OTP' self.conf.set(section, 'write file', image_file_name) if option == OPT_NONE: self.conf.set(section, 'write option', "None") elif option == OPT_VERIFY: self.conf.set(section, 'write option', "Verify") elif option == OPT_RAW: self.conf.set(section, 'write option', "Raw") self.conf.write(open(self.iniFilePath, 'w', encoding='utf-8')) if media == DEV_OTP: # print(f'do_otp_program({image_file_name})') # do_otp_program(image_file_name) worker = Worker(do_otp_program, image_file_name) elif ispack: # print(f'do_pack_program({media}, {image_file_name}, {option})') # do_pack_program(media, image_file_name, option) worker = Worker(do_pack_program, media, image_file_name, option) else: # print(f'do_img_program({media}, {start}, {image_file_name}, {option})') # do_img_program(media, start, image_file_name, option) worker = Worker(do_img_program, media, start, image_file_name, option) # Execute self.threadpool.start(worker) # def do_img_erase(media, start, length=0, option=OPT_NONE) -> None: @QtCore.pyqtSlot(int, str, str, int, bool) def doImgErase(self, media, startStr, lengthStr, option, isall=False): if isall: start = 0 length = 0 else: try: start = int(startStr, 0) & 0xffffffff except: start = 0 try: length = int(lengthStr, 0) & 0xffffffff except: length = 0x1 self.text_browser.clear() if media in [DEV_NAND, DEV_SPINOR, DEV_SPINAND, DEV_SD_EMMC]: if media == DEV_NAND: section = 'NAND' elif media == DEV_SPINOR: section = 'SPINOR' elif media == DEV_SPINAND: section = 'SPINAND' else: section = 'SD' self.conf.set(section, 'erase start', startStr) self.conf.set(section, 'erase length', lengthStr) if isall: self.conf.set(section, 'erase all', 'true') else: self.conf.set(section, 'erase all', 'false') self.conf.write(open(self.iniFilePath, 'w', encoding='utf-8')) # print(f'do_img_erase({media}, {start}, {length}, {option})') # do_img_erase(media, start, length, option) worker = Worker(do_img_erase, media, start, length, option) # Execute self.threadpool.start(worker) @QtCore.pyqtSlot(str, int) def doMsc(self, reserveStr, option): try: reserve = int(reserveStr, 0) & 0xffffffff except: reserve = 0 self.text_browser.clear() section = 'SD' if option == OPT_EJECT: self.conf.set(section, 'storage option', "Eject") reserve = 0 else: self.conf.set(section, 'storage option', "None") self.conf.set(section, 'storage size', reserveStr) self.conf.write(open(self.iniFilePath, 'w', encoding='utf-8')) media = DEV_SD_EMMC # print(f'do_msc({media}, {reserve}, {option})') # do_msc(media, reserve, option) worker = Worker(do_msc, media, reserve, option) # Execute self.threadpool.start(worker)
def loadConf(self): confWorker=load_conf() confWorker.signals.hasConf.connect(self.storeConf) confWorker.signals.hasError.connect(lambda x:print(x)) QThreadPool.globalInstance().start(confWorker)
def __init__(self): super().__init__() self.pool = QThreadPool() self.pool.globalInstance() self.pool.setMaxThreadCount(3)
def loadTimerConf(self): print("loading Timer") loadConf=load_conf(config="timer.json") loadConf.signals.hasConf_d.connect(self.storeTimerConf) loadConf.signals.hasError.connect(logging.error) QThreadPool.globalInstance().start(loadConf)
class MainWindow(QWidget): def __init__(self, inList): super().__init__() self.inList = inList self.nameFrom = 'Folder' self.codec = 'utvideo' self.alpha = False self.frameRate = 24 self.defaulStyle = '' self.okIcon = QIcon(self.style().standardIcon(QStyle.SP_CustomBase)) self.okPix = QPixmap(self.okIcon.pixmap(QSize(13, 13))) self.goodIcon = QIcon(self.style().standardIcon(QStyle.SP_DialogApplyButton)) self.goodPix = QPixmap(self.goodIcon.pixmap(QSize(13, 13))) self.badIcon = QIcon(self.style().standardIcon(QStyle.SP_MessageBoxCritical)) self.badPix = QPixmap(self.badIcon.pixmap(QSize(13, 13))) self.processingIcon = QIcon(self.style().standardIcon(QStyle.SP_ArrowRight)) self.processingPix = QPixmap(self.processingIcon.pixmap(QSize(13, 13))) self.removeIcon = QIcon(self.style().standardIcon(QStyle.SP_DockWidgetCloseButton)) self.removePix = QPixmap(self.removeIcon.pixmap(QSize(19, 19))) self.pbList = [] self.chList = [] self.lblList = [] self.rmbList = [] #self.newFolders = [] self.initUI() def initUI(self): self.resize(653, 476) self.setWindowTitle('FFMpeg Python Compressor') self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setContentsMargins(11, 11, 11, 11) self.verticalLayout.setSpacing(11) #COMBOBOX LABELS self.gridLayoutControlls = QGridLayout() self.codecLabel = QLabel('Codec', self) self.alphaLabel = QLabel('Alpha' , self) self.frameRateLabel = QLabel('Frame Rate' , self) self.gridLayoutControlls.addWidget(self.codecLabel, 0, 0, 1, 1) self.gridLayoutControlls.addWidget(self.alphaLabel, 0, 1, 1, 1) self.gridLayoutControlls.addWidget(self.frameRateLabel, 0, 2, 1, 1) #COMBOBOXES AND COMPRESS BUTTON self.codecComboBox = QComboBox(self) self.codecComboBox.setMinimumWidth(80) self.codecComboBox.addItem("UT Video") self.codecComboBox.activated[str].connect(self.chooseCodec) self.alphaComboBox = QComboBox(self) self.alphaComboBox.setMinimumWidth(80) self.alphaComboBox.addItem("No Alpha") self.alphaComboBox.addItem("with Alpha") self.alphaComboBox.activated[str].connect(self.chooseAlpha) self.frameRateComboBox = QComboBox(self) self.frameRateComboBox.setMinimumWidth(80) self.frameRateComboBox.addItem("24.00") self.frameRateComboBox.addItem("30.00") self.frameRateComboBox.activated[str].connect(self.chooseFrameRate) self.compressButton = QPushButton('Compress', self) self.compressButton.clicked[bool].connect(self.compressPress) self.gridLayoutControlls.addWidget(self.codecComboBox, 1, 0, 1, 1) self.gridLayoutControlls.addWidget(self.alphaComboBox, 1, 1, 1, 1) self.gridLayoutControlls.addWidget(self.frameRateComboBox, 1, 2, 1, 1) self.gridLayoutControlls.addWidget(self.compressButton, 1, 3, 1, 1) #RADIO BUTTON GROUP self.groupBox = QButtonGroup(self) self.radio1 = QRadioButton('Output file name from Folder name', self) self.radio2 = QRadioButton('Output file name from File name', self) self.radio1.setChecked(True) self.groupBox.addButton(self.radio1,1) self.groupBox.addButton(self.radio2,2) self.groupBox.buttonClicked[int].connect(self.radioBtnState) self.gridLayoutControlls.addWidget(self.radio1, 2, 0, 1, 2) self.gridLayoutControlls.addWidget(self.radio2, 2, 2, 1, 2) #LINE self.line = QFrame(self) self.line.setLineWidth(2) self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.gridLayoutControlls.addWidget(self.line, 3, 0, 1, 4) #PROGRESS BAR self.gridLayoutProgress = QGridLayout() self.gridLayoutProgress.setSizeConstraint(QLayout.SetNoConstraint) self.removeGroupBox = QButtonGroup(self) for i in range(len(self.inList)): self.tempCheckBox = QCheckBox(self) self.tempCheckBox.setChecked(True) self.tempRemoveButton = QPushButton(self) self.tempRemoveButton.setIcon(self.removeIcon) self.tempRemoveButton.setFlat(True) self.tempRemoveButton.setIconSize(QSize(19,19)) self.tempRemoveButton.setFixedSize(QSize(21,21)) self.tempPB = QProgressBar(self) self.tempPB.setMinimum(0) self.tempPB.setMaximum(100) self.tempPB.setTextVisible(True) self.tempPB.setFormat(str(self.inList[i])+" %p%") self.tempPB.setAlignment(Qt.AlignCenter) self.tempPB.setValue(0) if i==0: self.defaulStyle = self.tempPB.styleSheet() self.tempStatusLabel = QLabel(self) self.tempStatusLabel.setPixmap(self.okPix) self.gridLayoutProgress.addWidget(self.tempCheckBox, i, 0, 1, 1) self.gridLayoutProgress.addWidget(self.tempPB, i, 1, 1, 1) self.gridLayoutProgress.addWidget(self.tempStatusLabel, i, 2, 1, 1) self.gridLayoutProgress.addWidget(self.tempRemoveButton, i, 3, 1, 1) self.removeGroupBox.addButton(self.tempRemoveButton,i) self.pbList.append(self.tempPB) self.chList.append(self.tempCheckBox) self.lblList.append(self.tempStatusLabel) self.rmbList.append(self.tempRemoveButton) self.removeGroupBox.buttonClicked[int].connect(self.removeButtonClicked) #ADD MORE AREA self.gridLayoutAddMore = QGridLayout() self.gridLayoutAddMore.setContentsMargins(0, 0, 0, 0) self.dragAndDropLabel = QLabel("Drag and Drop more folders here", self) self.dragAndDropLabel.setMinimumSize(QSize(0, 40)) self.dragAndDropLabel.setAlignment(Qt.AlignCenter) self.gridLayoutAddMore.addWidget(self.dragAndDropLabel, 1, 0, 1, 1) #DEBUG AREA self.gridLayoutDebug = QGridLayout() self.line_2 = QFrame(self) self.line_2.setLineWidth(2) self.line_2.setFrameShape(QFrame.HLine) self.line_2.setFrameShadow(QFrame.Sunken) self.debugLabel = QLabel('Debug:',self) self.debugLabel.setMinimumSize(QSize(0, 20)) self.debugLabel.setAlignment(Qt.AlignBottom|Qt.AlignLeading|Qt.AlignLeft) self.errorText = QPlainTextEdit('',self) self.errorText.setReadOnly(True) self.gridLayoutDebug.addWidget(self.line_2, 0, 0, 1, 1) self.gridLayoutDebug.addWidget(self.debugLabel, 1, 0, 1, 1) self.gridLayoutDebug.addWidget(self.errorText, 2, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayoutControlls) self.verticalLayout.addLayout(self.gridLayoutProgress) self.verticalLayout.addLayout(self.gridLayoutAddMore) self.verticalLayout.addLayout(self.gridLayoutDebug) # Enable dragging and dropping onto the GUI self.setAcceptDrops(True) #QtCore.QMetaObject.connectSlotsByName(self) self.show() self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(1) print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) ''' Drag+Drop Functions ''' # The following three methods set up dragging and dropping for the app def dragEnterEvent(self, e): if e.mimeData().hasUrls: e.accept() else: e.ignore() def dragMoveEvent(self, e): if e.mimeData().hasUrls: e.accept() else: e.ignore() def dropEvent(self, e): """ Drop files directly onto the widget File locations are stored in fname :param e: :return: """ newFolders = [] if e.mimeData().hasUrls: e.setDropAction(Qt.CopyAction) e.accept() # Workaround for OSx dragging and dropping for url in e.mimeData().urls(): if op_sys == 'Darwin': #check for dir here as well fname = str(NSURL.URLWithString_(str(url.toString())).filePathURL().path()) else: fname = str(url.toLocalFile()) if os.path.isdir(fname) == True: newFolders.append(fname) #print(fname) #self.fname = fname #print(self.fname) #self.load_image() self.addNewFolders(newFolders) self.inList = self.inList + newFolders else: e.ignore() #rewrite as a def here and at the init function def addNewFolders(self, newFolders): pbCount = len(self.pbList) for i in range(len(newFolders)): self.tempCheckBox = QCheckBox(self) self.tempCheckBox.setChecked(True) self.tempPB = QProgressBar(self) self.tempPB.setMinimum(0) self.tempPB.setMaximum(100) self.tempPB.setTextVisible(True) self.tempPB.setFormat(str(newFolders[i])+" %p%") self.tempPB.setAlignment(Qt.AlignCenter) self.tempPB.setValue(0) self.tempStatusLabel = QLabel(self) self.tempStatusLabel.setPixmap(self.okPix) self.gridLayoutProgress.addWidget(self.tempCheckBox, pbCount+i, 0, 1, 1) self.gridLayoutProgress.addWidget(self.tempPB, pbCount+i, 1, 1, 1) self.gridLayoutProgress.addWidget(self.tempStatusLabel, pbCount+i, 2, 1, 1) self.pbList.append(self.tempPB) self.chList.append(self.tempCheckBox) self.lblList.append(self.tempStatusLabel) ''' Button Functions ''' def chooseAlpha(self, text): switcher={ "No Alpha":False, "with Alpha":True } self.alpha = switcher.get(text,"Invalid day of week") #print (self.alpha) def chooseCodec(self, text): switcher={ "UT Video":"utvideo" } self.codec = switcher.get(text,"Invalid day of week") #print (self.codec) def chooseFrameRate(self, text): self.frameRate = float(text) #print (self.frameRate) def currentData(self, widget): return widget.currentText() def radioBtnState(self, text): switcher={ 1:'Folder', 2:'File' } self.nameFrom = switcher.get(text,"Invalid day of week") #print(self.nameFrom) def removeButtonClicked(self, i): #print('remove trigger on id '+str(i)) #self.pbList.pop(i) self.pbList[i].hide() self.chList[i].setChecked(False) self.chList[i].hide() self.lblList[i].hide() self.rmbList[i].hide() self.removeGroupBox.removeButton(self.rmbList[i]) self.gridLayoutProgress.removeWidget(self.pbList[i]) self.gridLayoutProgress.removeWidget(self.chList[i]) self.gridLayoutProgress.removeWidget(self.lblList[i]) self.gridLayoutProgress.removeWidget(self.rmbList[i]) ''' print(self.pbList) print(self.chList) print(self.lblList) print(self.rmbList) ''' self.pbList.pop(i) self.chList.pop(i) self.lblList.pop(i) self.rmbList.pop(i) self.inList.pop(i) for j in range(len(self.removeGroupBox.buttons())): button = self.removeGroupBox.buttons()[j] #print(button) #print('original id '+str(self.removeGroupBox.id(button))) #print('new id '+str(j)) self.removeGroupBox.setId(button,j) ''' Execution Functions ''' def execute_this_fn(self, path, codec, alpha, frameRate, nameFrom, i, progress_callback, errorFFMPEG_callback): #print(path) pyCompression = pyFFMEGCompress(path, codec, alpha, frameRate, nameFrom) ffProcess = pyCompression.ffmpegCompress() self.lblList[i].setPixmap(self.processingPix) #with kwargs kwargs = {'progress_callback':progress_callback, 'errorFFMPEG_callback':errorFFMPEG_callback} pyCompression.printProcess(ffProcess, **kwargs) return (pyCompression.debugString,pyCompression.error,i) def printOutput(self, s): print("Printing output "+ str(s)) def threadComplete(self, r): #print("THREAD COMPLETE! WITH ERROR " + str(r[2]) ) if r[1]==False: self.lblList[r[2]].setPixmap(self.goodPix) self.pbList[r[2]].setStyleSheet("QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #44dd14,stop: 0.4999 #39c10f,stop: 0.5 #39c10f,stop: 1 #39c10f );border-radius: 3px; border: 1px solid #29880b;}QProgressBar{color:white}") def errorPB(self, err): for i in range(len(self.pbList)): if self.pbList[i].format() == err: self.pbList[i].setValue(100) self.pbList[i].setStyleSheet("QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1,stop: 0 #FF0350,stop: 0.4999 #FF0020,stop: 0.5 #FF0019,stop: 1 #FF0000 );border-radius: 3px; border: 1px solid #a60233;}QProgressBar{color:white}") self.pbList[i].setFormat(self.pbList[i].format()+" - Error") self.chList[i].setChecked(False) self.lblList[i].setPixmap(self.badPix) def resetProgressBar(self, pb, text, lbl): pb.setValue(0) pb.setFormat(text + ' %p%') pb.setStyleSheet(self.defaulStyle) lbl.setPixmap(self.okPix) def compressPress(self): for i in range(len(self.pbList)): if self.chList[i].isChecked(): self.resetProgressBar(self.pbList[i],self.inList[i],self.lblList[i]) worker = Worker(self.execute_this_fn, self.inList[i], self.codec, self.alpha, self.frameRate, self.nameFrom, i) # Any other args, kwargs are passed to the run function #worker.signals.result.connect(self.printOutput) worker.signals.result.connect(self.errorText.appendPlainText) worker.signals.progress.connect(self.pbList[i].setValue) worker.signals.errorFFMPEG.connect(self.errorPB) worker.signals.error.connect(self.errorPB) worker.signals.finished.connect(self.threadComplete) #worker.signals.finished.connect(self.errorText.appendPlainText) # Execute self.threadpool.start(worker)
class K2001A_TEST_dialog(QDialog): def __init__(self, parent): super().__init__(parent) # Initial read of the config file self.config = configparser.ConfigParser() try: self.config.read('config.ini') self.last_used_scan = self.config.get('LastScan', 'last_used_scan') self.schroll_pts = int( self.config.get(self.last_used_scan, 'schroll')) self.k2001aport = self.config.get( "Instruments", 'k2001aport').strip().split(',')[0] except configparser.NoOptionError as e: QMessageBox.critical( self, 'Message', ''.join( ["Main FAULT while reading the config.ini file\n", str(e)])) raise self.setupUi() def setupUi(self): serialport = QLabel("GPIB port", self) self.serialportEdit = QLineEdit(self.k2001aport, self) self.runstopButton = QPushButton("START", self) self.clearButton = QPushButton("Clear", self) schroll_lbl = QLabel("Schroll elapsed time ", self) self.combo0 = QComboBox(self) mylist0 = ["100", "200", "400", "600", "800", "1000", "1500", "2000"] self.combo0.addItems(mylist0) self.combo0.setCurrentIndex(mylist0.index(str(self.schroll_pts))) ############################################## g0_1 = QGridLayout() g0_1.addWidget(serialport, 0, 0) g0_1.addWidget(self.serialportEdit, 0, 1) g0_1.addWidget(schroll_lbl, 0, 2) g0_1.addWidget(self.combo0, 0, 3) g0_1.addWidget(self.runstopButton, 0, 4) g0_1.addWidget(self.clearButton, 0, 5) ############################################## # set graph and toolbar to a new vertical group vcan self.pw1 = pg.PlotWidget() ############################################## # create table self.tableWidget = self.createTable() ############################################## # SET ALL VERTICAL COLUMNS TOGETHER vbox = QVBoxLayout() vbox.addLayout(g0_1) vbox.addWidget(self.pw1) hbox = QHBoxLayout() hbox.addLayout(vbox) hbox.addWidget(self.tableWidget) self.threadpool = QThreadPool() print("Multithreading in the K2001A with maximum %d threads" % self.threadpool.maxThreadCount()) self.isRunning = False self.setLayout(hbox) self.setWindowTitle("Test Keithley 2001A") # PLOT 2 settings # create plot and add it to the figure canvas self.p1 = self.pw1.plotItem self.curve1 = self.p1.plot(pen='w') # create plot and add it to the figure self.p0_1 = pg.ViewBox() self.curve2 = pg.PlotCurveItem(pen='r') self.p0_1.addItem(self.curve2) # connect respective axes to the plot #self.p1.showAxis('left') self.p1.getAxis('left').setLabel("Voltage", units="V", color='yellow') self.p1.showAxis('right') self.p1.getAxis('right').setLabel("Arb unit, 1023=1.1V", units="", color='red') self.p1.scene().addItem(self.p0_1) self.p1.getAxis('right').linkToView(self.p0_1) self.p0_1.setXLink(self.p1) self.p1.getAxis('bottom').setLabel("Points", units="", color='yellow') # Use automatic downsampling and clipping to reduce the drawing load self.pw1.setDownsampling(mode='peak') self.pw1.setClipToView(True) # Initialize and set titles and axis names for both plots self.clear_vars_graphs() self.combo0.activated[str].connect(self.onActivated0) # run or cancel the main script self.runstopButton.clicked.connect(self.runstop) self.clearButton.clicked.connect(self.set_clear) self.clearButton.setEnabled(False) def onActivated0(self, text): old_st = self.schroll_pts self.schroll_pts = int(str(text)) if old_st > self.schroll_pts: self.set_clear() def createTable(self): tableWidget = QTableWidget() # set row count #tableWidget.setRowCount(20) # set column count tableWidget.setColumnCount(2) # hide grid tableWidget.setShowGrid(False) # hide vertical header vh = tableWidget.verticalHeader() vh.setVisible(False) # set the font font = QFont("Courier New", 9) tableWidget.setFont(font) tableWidget.setStyleSheet("color: blue") # place content into individual table fields #tableWidget.setItem(0,0, QTableWidgetItem("abe")) tableWidget.setHorizontalHeaderLabels(["Point no.", "U[V]"]) #tableWidget.setVerticalHeaderLabels(["aa","bb","cc","dd"]) # set horizontal header properties hh = tableWidget.horizontalHeader() hh.setStretchLastSection(True) # set column width to fit contents tableWidget.resizeColumnsToContents() # enable sorting #tableWidget.setSortingEnabled(True) return tableWidget def set_cancel(self): self.worker.abort() self.clearButton.setEnabled(True) self.runstopButton.setText("START") def set_clear(self): self.clear_vars_graphs() self.clearButton.setEnabled(False) self.clearButton.setText("Cleared") def runstop(self): sender = self.sender() if sender.text() == "START": self.set_run() elif sender.text() == "STOP": self.set_cancel() def set_run(self): try: self.K2001A = K2001A.K2001A(str(self.serialportEdit.text()), False) rm = visa.ResourceManager() print(rm.list_resources()) except Exception as e: reply = QMessageBox.critical( self, 'Keithley 2001A TEST MODE', ''.join([ "K2001A could not return valid echo signal. Check the port name and check the connection.\n", str(e), "\n\nShould the program proceeds into the TEST MODE?" ]), QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: self.K2001A = K2001A.K2001A(str(self.serialportEdit.text()), True) else: return self.runstopButton.setEnabled(True) self.runstopButton.setText("STOP") self.clearButton.setEnabled(False) self.combo0.setEnabled(False) self.serialportEdit.setEnabled(False) self.isRunning = True setrun_obj = type('setscan_obj', (object, ), {'k2001a': self.K2001A}) self.worker = K2001A_Worker(setrun_obj) self.worker.signals.update0.connect(self.update0) self.worker.signals.finished.connect(self.finished) # Execute self.threadpool.start(self.worker) def update0(self, volts): self.tal += 1 # set row height self.tableWidget.setRowCount(self.tal + 1) self.tableWidget.setRowHeight(self.tal, 12) # add row elements self.tableWidget.setItem(self.tal, 0, QTableWidgetItem(str(self.tal))) self.tableWidget.setItem(self.tal, 1, QTableWidgetItem(str(volts))) if len(self.tals) > self.schroll_pts: self.tals[:-1] = self.tals[ 1:] # shift data in the array one sample left self.tals[-1] = self.tal self.plot_as_tr[:-1] = self.plot_as_tr[ 1:] # shift data in the array one sample left self.plot_as_tr[-1] = volts #self.plot_time_tr[:-1] = self.plot_time_tr[1:] # shift data in the array one sample left #self.plot_time_tr[-1] = timelist self.plot_volts_tr[:-1] = self.plot_volts_tr[ 1:] # shift data in the array one sample left self.plot_volts_tr[-1] = volts else: self.tals.extend([self.tal]) self.plot_as_tr.extend([volts]) self.plot_volts_tr.extend([volts]) #self.plot_time_tr.extend([ timelist ]) ## Handle view resizing def updateViews(): ## view has resized; update auxiliary views to match self.p0_1.setGeometry(self.p1.vb.sceneBoundingRect()) #p3.setGeometry(p1.vb.sceneBoundingRect()) ## need to re-update linked axes since this was called ## incorrectly while views had different shapes. ## (probably this should be handled in ViewBox.resizeEvent) self.p0_1.linkedViewChanged(self.p1.vb, self.p0_1.XAxis) #p3.linkedViewChanged(p1.vb, p3.XAxis) updateViews() self.p1.vb.sigResized.connect(updateViews) self.curve1.setData(self.tals, self.plot_as_tr) self.curve2.setData(self.tals, self.plot_volts_tr) def finished(self): self.isRunning = False self.serialportEdit.setEnabled(True) self.combo0.setEnabled(True) self.clearButton.setEnabled(True) self.clearButton.setText("Clear") def clear_vars_graphs(self): # PLOT 2 initial canvas settings self.tal = -1 self.tals = [] self.all_time_tr = [] self.plot_as_tr = [] self.plot_volts_tr = [] #self.plot_time_tr=[] self.curve1.clear() self.curve2.clear() self.tableWidget.clearContents() def closeEvent(self, event): reply = QMessageBox.question(self, 'Message', "Quit now? Changes will not be saved!", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: if hasattr(self, 'K2001A'): if self.isRunning: QMessageBox.warning( self, 'Message', "Run in progress. Cancel the scan then quit!") event.ignore() return event.accept() else: event.ignore()
def initUI(self): self.resize(653, 476) self.setWindowTitle('FFMpeg Python Compressor') self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setContentsMargins(11, 11, 11, 11) self.verticalLayout.setSpacing(11) #COMBOBOX LABELS self.gridLayoutControlls = QGridLayout() self.codecLabel = QLabel('Codec', self) self.alphaLabel = QLabel('Alpha' , self) self.frameRateLabel = QLabel('Frame Rate' , self) self.gridLayoutControlls.addWidget(self.codecLabel, 0, 0, 1, 1) self.gridLayoutControlls.addWidget(self.alphaLabel, 0, 1, 1, 1) self.gridLayoutControlls.addWidget(self.frameRateLabel, 0, 2, 1, 1) #COMBOBOXES AND COMPRESS BUTTON self.codecComboBox = QComboBox(self) self.codecComboBox.setMinimumWidth(80) self.codecComboBox.addItem("UT Video") self.codecComboBox.activated[str].connect(self.chooseCodec) self.alphaComboBox = QComboBox(self) self.alphaComboBox.setMinimumWidth(80) self.alphaComboBox.addItem("No Alpha") self.alphaComboBox.addItem("with Alpha") self.alphaComboBox.activated[str].connect(self.chooseAlpha) self.frameRateComboBox = QComboBox(self) self.frameRateComboBox.setMinimumWidth(80) self.frameRateComboBox.addItem("24.00") self.frameRateComboBox.addItem("30.00") self.frameRateComboBox.activated[str].connect(self.chooseFrameRate) self.compressButton = QPushButton('Compress', self) self.compressButton.clicked[bool].connect(self.compressPress) self.gridLayoutControlls.addWidget(self.codecComboBox, 1, 0, 1, 1) self.gridLayoutControlls.addWidget(self.alphaComboBox, 1, 1, 1, 1) self.gridLayoutControlls.addWidget(self.frameRateComboBox, 1, 2, 1, 1) self.gridLayoutControlls.addWidget(self.compressButton, 1, 3, 1, 1) #RADIO BUTTON GROUP self.groupBox = QButtonGroup(self) self.radio1 = QRadioButton('Output file name from Folder name', self) self.radio2 = QRadioButton('Output file name from File name', self) self.radio1.setChecked(True) self.groupBox.addButton(self.radio1,1) self.groupBox.addButton(self.radio2,2) self.groupBox.buttonClicked[int].connect(self.radioBtnState) self.gridLayoutControlls.addWidget(self.radio1, 2, 0, 1, 2) self.gridLayoutControlls.addWidget(self.radio2, 2, 2, 1, 2) #LINE self.line = QFrame(self) self.line.setLineWidth(2) self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.gridLayoutControlls.addWidget(self.line, 3, 0, 1, 4) #PROGRESS BAR self.gridLayoutProgress = QGridLayout() self.gridLayoutProgress.setSizeConstraint(QLayout.SetNoConstraint) self.removeGroupBox = QButtonGroup(self) for i in range(len(self.inList)): self.tempCheckBox = QCheckBox(self) self.tempCheckBox.setChecked(True) self.tempRemoveButton = QPushButton(self) self.tempRemoveButton.setIcon(self.removeIcon) self.tempRemoveButton.setFlat(True) self.tempRemoveButton.setIconSize(QSize(19,19)) self.tempRemoveButton.setFixedSize(QSize(21,21)) self.tempPB = QProgressBar(self) self.tempPB.setMinimum(0) self.tempPB.setMaximum(100) self.tempPB.setTextVisible(True) self.tempPB.setFormat(str(self.inList[i])+" %p%") self.tempPB.setAlignment(Qt.AlignCenter) self.tempPB.setValue(0) if i==0: self.defaulStyle = self.tempPB.styleSheet() self.tempStatusLabel = QLabel(self) self.tempStatusLabel.setPixmap(self.okPix) self.gridLayoutProgress.addWidget(self.tempCheckBox, i, 0, 1, 1) self.gridLayoutProgress.addWidget(self.tempPB, i, 1, 1, 1) self.gridLayoutProgress.addWidget(self.tempStatusLabel, i, 2, 1, 1) self.gridLayoutProgress.addWidget(self.tempRemoveButton, i, 3, 1, 1) self.removeGroupBox.addButton(self.tempRemoveButton,i) self.pbList.append(self.tempPB) self.chList.append(self.tempCheckBox) self.lblList.append(self.tempStatusLabel) self.rmbList.append(self.tempRemoveButton) self.removeGroupBox.buttonClicked[int].connect(self.removeButtonClicked) #ADD MORE AREA self.gridLayoutAddMore = QGridLayout() self.gridLayoutAddMore.setContentsMargins(0, 0, 0, 0) self.dragAndDropLabel = QLabel("Drag and Drop more folders here", self) self.dragAndDropLabel.setMinimumSize(QSize(0, 40)) self.dragAndDropLabel.setAlignment(Qt.AlignCenter) self.gridLayoutAddMore.addWidget(self.dragAndDropLabel, 1, 0, 1, 1) #DEBUG AREA self.gridLayoutDebug = QGridLayout() self.line_2 = QFrame(self) self.line_2.setLineWidth(2) self.line_2.setFrameShape(QFrame.HLine) self.line_2.setFrameShadow(QFrame.Sunken) self.debugLabel = QLabel('Debug:',self) self.debugLabel.setMinimumSize(QSize(0, 20)) self.debugLabel.setAlignment(Qt.AlignBottom|Qt.AlignLeading|Qt.AlignLeft) self.errorText = QPlainTextEdit('',self) self.errorText.setReadOnly(True) self.gridLayoutDebug.addWidget(self.line_2, 0, 0, 1, 1) self.gridLayoutDebug.addWidget(self.debugLabel, 1, 0, 1, 1) self.gridLayoutDebug.addWidget(self.errorText, 2, 0, 1, 1) self.verticalLayout.addLayout(self.gridLayoutControlls) self.verticalLayout.addLayout(self.gridLayoutProgress) self.verticalLayout.addLayout(self.gridLayoutAddMore) self.verticalLayout.addLayout(self.gridLayoutDebug) # Enable dragging and dropping onto the GUI self.setAcceptDrops(True) #QtCore.QMetaObject.connectSlotsByName(self) self.show() self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(1) print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
class Reset_dialog(QDialog): def __init__(self, parent, inst_list): super().__init__(parent) # constants self.inst_list = inst_list self.setupUi() def setupUi(self): self.stopButton = QPushButton("STOP RESET", self) self.stopButton.setFixedHeight(35) self.stopButton.setFixedWidth(150) self.stopButton.setEnabled(False) self.startButton = QPushButton("Start reset", self) self.startButton.setFixedHeight(35) self.startButton.setFixedWidth(150) self.startButton.setEnabled(True) lbl0 = QLabel("Statusbyte returned from the SMC100PP:\t", self) self.lbl_st = QLabel("", self) self.lbl_st.setStyleSheet("color: blue") grid_0 = QHBoxLayout() grid_0.addWidget(self.startButton) grid_0.addWidget(self.stopButton) grid_1 = QHBoxLayout() grid_1.addWidget(lbl0) grid_1.addWidget(self.lbl_st) grid_2 = QVBoxLayout() grid_2.addLayout(grid_0) grid_2.addLayout(grid_1) # cancel the script run self.startButton.clicked.connect(self.start) self.stopButton.clicked.connect(self.abort) self.threadpool = QThreadPool() self.setLayout(grid_2) self.setWindowTitle("Reset dialog for SMC100PP stepper") # re-adjust/minimize the size of the e-mail dialog # depending on the number of attachments grid_2.setSizeConstraint(grid_2.SetFixedSize) def abort(self): self.worker.abort() def start(self): reply = QMessageBox.question( self, 'Message', "The stepper will RESET and MOVE to the home position. Remove all components from the stepper head!", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: self.worker = Reset_Thread(self.inst_list) self.worker.signals.finished.connect(self.finished) self.worker.signals.info.connect(self.info) self.worker.signals.statusbyte.connect(self.statusbyte) # Execute self.threadpool.start(self.worker) ################################################# self.startButton.setEnabled(False) self.startButton.setText("..reseting..") def finished(self): self.startButton.setEnabled(True) self.startButton.setText("Start reset") self.stopButton.setEnabled(False) self.setWindowTitle("Reset dialog for SMC100PP stepper") def statusbyte(self, sb): self.lbl_st.setText(sb) def info(self, mystr): if mystr == "go_home": self.setWindowTitle("homing the stepper, please wait!") self.stopButton.setEnabled(True) elif mystr == "reset": self.setWindowTitle("reseting the stepper, please wait!") self.stopButton.setEnabled(False) else: self.stopButton.setEnabled(False) def about(self, mystr): QMessageBox.about(self, 'Message', mystr) def warning(self, mystr): QMessageBox.warning(self, 'Message', mystr) def critical(self, mystr): QMessageBox.critical(self, 'Message', mystr) def closeEvent(self, event): event.accept()
def __init__(self, base): QWidget.__init__(self) self.base = base self.setWindowIcon(QIcon('icon.ico')) self.setWindowTitle(TITLE) self.setBackgroundColor(Qt.white) self.processHeaderWidget = QWidget() self.processHeaderLayout = QHBoxLayout(self.processHeaderWidget) self.processHeaderLayout.setContentsMargins(0, 0, 0, 0) self.processLabel = QLabel('Available processes:') self.githubLabel = QLabel('<a href="https://github.com/darktohka/p3dephaser">GitHub</a>') self.githubLabel.setOpenExternalLinks(True) self.refreshButton = QPushButton('Refresh') self.refreshButton.clicked.connect(self.refreshProcesses) self.refreshButton.setFixedSize(100, 23) self.multifileWidget = QWidget() self.multifileLayout = QHBoxLayout(self.multifileWidget) self.multifileLayout.setContentsMargins(0, 0, 0, 0) self.multifileLabel = QLabel('Requested multifile names:') self.multifileBox = QLineEdit(self) self.multifileBox.returnPressed.connect(self.beginScan) self.multifileLayout.addWidget(self.multifileLabel) self.multifileLayout.addWidget(self.multifileBox) self.scanButton = QPushButton('Scan') self.scanButton.clicked.connect(self.beginScan) self.processListBox = QListWidget() self.processHeaderLayout.addWidget(self.processLabel) self.processHeaderLayout.addStretch(1) self.processHeaderLayout.addWidget(self.githubLabel) self.processHeaderLayout.addWidget(self.refreshButton) self.resultTable = QTableWidget() self.resultTable.setColumnCount(3) self.resultTable.horizontalHeader().setStretchLastSection(True) self.resultTable.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) for i, header in enumerate(('Process', 'Multifile', 'Password')): self.resultTable.setHorizontalHeaderItem(i, QTableWidgetItem(header)) self.baseLayout = QVBoxLayout(self) self.baseLayout.setContentsMargins(15, 15, 15, 15) self.baseLayout.addWidget(self.processHeaderWidget) self.baseLayout.addWidget(self.processListBox) self.baseLayout.addWidget(self.multifileWidget) self.baseLayout.addWidget(self.scanButton) self.baseLayout.addWidget(self.resultTable) self.refreshProcesses() self.threadPool = QThreadPool() self.worker = None self.processName = None self.nextClick = 0 self.stopEvent = threading.Event()
class ProgressController: current_method = 0 current_iteration = 0 _max_iterations: int _max_methods: int _current_progress = 0 _thread_pool: QThreadPool _ui: Ui_MainWindow _time_start: datetime _time_current: datetime _timer: QTimer _interval: int def __init__(self, ui): self._ui = ui self._thread_pool = QThreadPool() print("Multithreading with maximum %d threads" % self._thread_pool.maxThreadCount()) self._max_iterations = 0 self._max_methods = 0 self._interval = 1000 self._timer = QTimer() self._timer.setInterval(self._interval) self._timer.timeout.connect(self._timer_tick) def init_progress(self, progress_fn): worker = Worker(progress_fn) worker.signals.result.connect(self._print_output) worker.signals.finished.connect(self._thread_complete) worker.signals.progress.connect(self._update_progress) self._thread_pool.start(worker) def _print_output(self, s): print(s) def _thread_complete(self): self.stop() print("THREAD COMPLETE!") def start(self): self._reset() self._update_progress() self._timer.start() def _reset(self): ProgressController.reset_iteration() ProgressController.reset_current_method() self._time_start = datetime.datetime.now() self._time_current = self._time_start def stop(self): self._timer.stop() def _update_progress(self): progress = self._get_progress_percent() self._ui.progress_bar.setValue(progress) text = self._get_progress_text() self._ui.progress_info.setText(text) def _get_progress_percent(self): total = self._max_methods * self._max_iterations progress = (ProgressController.current_method - 1) * self._max_iterations + ProgressController.current_iteration return int(progress / total * 100) def _get_progress_text(self): time_diff = self._time_current - self._time_start format_str = "{:d}. Method / {:d} of {:d} Iterations / {:.0f}s" text = format_str.format( ProgressController.current_method, ProgressController.current_iteration, self._max_iterations, time_diff.total_seconds() ) return text def _timer_tick(self): self._time_current = datetime.datetime.now() self._update_progress() def set_max_iterations(self, iterations): self._max_iterations = iterations @staticmethod def inc_iteration(): ProgressController.current_iteration += 1 @staticmethod def reset_iteration(): ProgressController.current_iteration = 0 def set_max_methods(self, max_methods): self._max_methods = max_methods @staticmethod def inc_method_counter(): ProgressController.current_method += 1 @staticmethod def reset_current_method(): ProgressController.current_method = 0
class MainWidget(QWidget): def __init__(self, base): QWidget.__init__(self) self.base = base self.setWindowIcon(QIcon('icon.ico')) self.setWindowTitle(TITLE) self.setBackgroundColor(Qt.white) self.processHeaderWidget = QWidget() self.processHeaderLayout = QHBoxLayout(self.processHeaderWidget) self.processHeaderLayout.setContentsMargins(0, 0, 0, 0) self.processLabel = QLabel('Available processes:') self.githubLabel = QLabel('<a href="https://github.com/darktohka/p3dephaser">GitHub</a>') self.githubLabel.setOpenExternalLinks(True) self.refreshButton = QPushButton('Refresh') self.refreshButton.clicked.connect(self.refreshProcesses) self.refreshButton.setFixedSize(100, 23) self.multifileWidget = QWidget() self.multifileLayout = QHBoxLayout(self.multifileWidget) self.multifileLayout.setContentsMargins(0, 0, 0, 0) self.multifileLabel = QLabel('Requested multifile names:') self.multifileBox = QLineEdit(self) self.multifileBox.returnPressed.connect(self.beginScan) self.multifileLayout.addWidget(self.multifileLabel) self.multifileLayout.addWidget(self.multifileBox) self.scanButton = QPushButton('Scan') self.scanButton.clicked.connect(self.beginScan) self.processListBox = QListWidget() self.processHeaderLayout.addWidget(self.processLabel) self.processHeaderLayout.addStretch(1) self.processHeaderLayout.addWidget(self.githubLabel) self.processHeaderLayout.addWidget(self.refreshButton) self.resultTable = QTableWidget() self.resultTable.setColumnCount(3) self.resultTable.horizontalHeader().setStretchLastSection(True) self.resultTable.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) for i, header in enumerate(('Process', 'Multifile', 'Password')): self.resultTable.setHorizontalHeaderItem(i, QTableWidgetItem(header)) self.baseLayout = QVBoxLayout(self) self.baseLayout.setContentsMargins(15, 15, 15, 15) self.baseLayout.addWidget(self.processHeaderWidget) self.baseLayout.addWidget(self.processListBox) self.baseLayout.addWidget(self.multifileWidget) self.baseLayout.addWidget(self.scanButton) self.baseLayout.addWidget(self.resultTable) self.refreshProcesses() self.threadPool = QThreadPool() self.worker = None self.processName = None self.nextClick = 0 self.stopEvent = threading.Event() def setBackgroundColor(self, color): self.setAutoFillBackground(True) palette = self.palette() palette.setColor(self.backgroundRole(), color) self.setPalette(palette) def getProcesses(self): processes = [] for proc in psutil.process_iter(): processes.append(proc.as_dict(attrs=['pid', 'name'])) processes.sort(key=lambda process: (process['name'].lower(), process['pid'])) return processes def refreshProcesses(self): self.processListBox.clear() processes = self.getProcesses() for process in processes: name = process['name'] pid = process['pid'] self.processListBox.addItem(f'{name} (PID {pid})') def beginScan(self): if self.worker: self.stopEvent.set() self.scanButton.setEnabled(False) return items = self.processListBox.selectedItems() if not items: QMessageBox.warning(self, TITLE, 'Please choose a process from the list!') return process = items[0].text()[:-1].split(' ') self.processName = ' '.join(process[:-2]) pid = int(process[-1]) multifiles = self.multifileBox.text().split() if not multifiles: QMessageBox.warning(self, TITLE, 'Please choose some multifiles to target!') return multifile_names = '\n'.join([f'- {multifile}' for multifile in multifiles]) question = f'Do you really want to scan {self.processName} for the following multifiles?\n\n{multifile_names}' if QMessageBox.question(self, TITLE, question, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) != QMessageBox.Yes: return self.count = 0 self.setWindowTitle(f'{TITLE} - Scanning...') self.scanButton.setText('Stop') self.worker = ScanWorker(self, pid, multifiles) self.worker.signals.finished.connect(self.scanOver) self.worker.signals.error.connect(self.errorOccurred) self.worker.signals.progress.connect(self.reportProgress) self.threadPool.start(self.worker) def scanOver(self): self.worker = None self.stopEvent.clear() self.scanButton.setText('Scan') self.scanButton.setEnabled(True) self.setWindowTitle(TITLE) QMessageBox.information(self, TITLE, f'Scan complete!\n\n{self.count} password{"s have" if self.count != 1 else " has"} been found.') def errorOccurred(self, error): exc, value, message = error QMessageBox.critical(self, TITLE, f'An error has occurred while trying to scan this process!\n\n{exc} {value}\n\n{message}') def reportProgress(self, multifile, password): self.count += 1 index = self.resultTable.rowCount() self.resultTable.insertRow(index) for i, value in enumerate((self.processName, multifile, password)): self.resultTable.setItem(index, i, QTableWidgetItem(value))
class DownloadDialog(QtWidgets.QDialog): def __init__(self, appctxt, BbRouter, download_dir, modules: List[Tuple[str, str]], last_dialog: QtWidgets.QDialog): """Download Dialog for selecting files to download/ignore Args: appctxt (ApplicationContext): fbs application context BbRouter (str): authentication token download_dir (str): download directory modules (List[Tuple[str, str]]): list of course name and course id tuples last_dialog (QtWidgets.QDialog): last dialog to return on back button press """ super(DownloadDialog, self).__init__() uic.loadUi(appctxt.get_resource("layouts/download.ui"), self) self.logger = Logger(appctxt) self.appctxt = appctxt self.BbRouter = BbRouter self.download_dir = download_dir self.modules = modules self.last_dialog = last_dialog dirLabel = self.findChild(QtWidgets.QLabel, "downloadDirLabel") dirLabel.setText("Downloading to: {}".format(download_dir)) # load icons backIcon = QtGui.QIcon(appctxt.get_resource("images/back.png")) self.folderIcon = QtGui.QIcon( appctxt.get_resource("images/folder.png")) self.videoIcon = QtGui.QIcon(appctxt.get_resource("images/video.png")) self.fileIcon = QtGui.QIcon(appctxt.get_resource("images/file.png")) # load buttons self.backButton = self.findChild(QtWidgets.QToolButton, "backButton") self.backButton.setIcon(backIcon) self.ignoreButton = self.findChild(QtWidgets.QPushButton, "ignoreButton") self.downloadButton = self.findChild(QtWidgets.QPushButton, "downloadButton") self.selectAllButton = self.findChild(QtWidgets.QPushButton, "selectAllButton") self.deselectAllButton = self.findChild(QtWidgets.QPushButton, "deselectAllButton") self.reloadButton = self.findChild(QtWidgets.QPushButton, "reloadButton") self.selectFilesButton = self.findChild(QtWidgets.QPushButton, "selectFilesButton") self.selectVideosButton = self.findChild(QtWidgets.QPushButton, "selectVideosButton") self.backButton.clicked.connect(self.handle_back) self.selectAllButton.clicked.connect(self.handle_select_all) self.deselectAllButton.clicked.connect(self.handle_unselect_all) self.ignoreButton.clicked.connect(self.handle_ignore) self.downloadButton.clicked.connect(self.handle_download) self.reloadButton.clicked.connect(self.handle_reload) self.selectFilesButton.clicked.connect(self.handle_select_files) self.selectVideosButton.clicked.connect(self.handle_select_videos) self.progressBar = self.findChild(QtWidgets.QProgressBar, "progressBar") self.progressBar.setValue(0) self.downloadProgressText = self.findChild(QtWidgets.QLabel, "downloadProgressText") self.downloadProgressText.setText( "Click download to start downloading files") # get download dir from NTU Learn and load tree self.threadPool = QThreadPool() self.tree = self.findChild(QtWidgets.QTreeWidget, "treeWidget") # NOTE do not show tree even though we have data as we want the user to # act on fresh download data self.storage = Storage(download_dir) self.data = self.storage.download_dir # add loading text node = QtWidgets.QTreeWidgetItem(self.tree) node.setText(0, "Click Reload to pull data from NTU Learn") self.show() def closeEvent(self, event): self.storage.save_download_dir(self.data) def handle_back(self): # self.main = ChooseDirDialog(self.appctxt, self.BbRouter) self.main = self.last_dialog(self.appctxt, self.BbRouter) self.main.show() self.close() def handle_select_files(self): self.__handle_select_type(obj_type="file") def handle_select_videos(self): self.__handle_select_type(obj_type="recorded_lecture") def handle_reload(self): """ 1. disable reload button until done fetching data 2. clear tree and add Loading text node 3. in a separate thread make ntu_learn_downloader API call 4. when done update UI """ self.reloadButton.setEnabled(False) self.__clear_tree() node = QtWidgets.QTreeWidgetItem(self.tree) node.setText(0, "Loading...") def get_data(progress_callback) -> List[Dict]: """Get download dir from NTU Learn, WARNING slow, should not be run in main thread Returns list of dicts """ result = [ get_download_dir(self.BbRouter, name, course_id) for name, course_id in self.modules ] return result def save_data(result): self.storage.merge_download_dir(result) self.data = result self.data_to_tree() def finished(): self.reloadButton.setEnabled(True) worker = Worker(get_data) worker.signals.result.connect(save_data) worker.signals.finished.connect(finished) self.threadPool.start(worker) def handle_select_all(self): def traverse(node): node.setCheckState(0, Qt.Checked) for index in range(node.childCount()): traverse(node.child(index)) root = self.tree.invisibleRootItem() traverse(root) def handle_unselect_all(self): def traverse(node): node.setCheckState(0, Qt.Unchecked) for index in range(node.childCount()): traverse(node.child(index)) root = self.tree.invisibleRootItem() traverse(root) def handle_ignore(self): """Dummy files are in the format: .{name} Do not have to get the actual filename """ alert = QtWidgets.QMessageBox() alert.setWindowTitle("Ignore selected files") alert.setIcon(QtWidgets.QMessageBox.Warning) alert.setText( "You are about to ignore some files. This will generate hidden files in your download directory" ) alert.setDetailedText( "This will generate hidden files in the download directory so that ignored " "files will not appear the menu in the future.\n" "To undo, you need to enable show hidden files in your file " "explorer and remove the files you want to download. E.g. if you want to download Tut_4, look for .Tut_4 and remove it" ) alert.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) retval = alert.exec_() if retval == QtWidgets.QMessageBox.Ok: path_and_nodes = self.get_paths_and_selected_nodes() for path, node in path_and_nodes: node_data = node.data(0, Qt.UserRole) create_dummy_file(path, sanitise_filename(node_data["name"])) self.downloadProgressText.setText( "Ignored {} files and recorded lectures".format( len(path_and_nodes))) if retval == QtWidgets.QMessageBox.Cancel: pass def handle_error(self, full_file_name: str, trace: str): """Create a MessageBox with a trace dump and log error to server """ try: self.logger.log_error(trace) except Exception: pass alert = QtWidgets.QMessageBox() alert.setWindowTitle("Failed to get download link") alert.setText( "Failed to get download link for: {}. Please try again later. ". format(full_file_name) + "If the problem persists, please send the trace log below to us.") nonBoldFont = QtGui.QFont() nonBoldFont.setBold(False) alert.setDetailedText(trace) alert.setFont(nonBoldFont) alert.exec_() def handle_download(self): """ 1. Get list of files to download 2. Map predownload links to download links 3. Download files async in background 4. update tree with downloaded items removed """ self.setDownloadIgnoreButtonsEnabled(False) self.downloadProgressText.setText("Getting items to download...") paths_and_nodes = self.get_paths_and_selected_nodes() numFiles = len(paths_and_nodes) self.progressBar.setRange(0, numFiles) def download_from_nodes(progress_callback): """Return tuple (files downloaded, files skipped, download_links) """ numDownloaded, numSkipped = 0, 0 data_deltas = [] for idx, (path, node) in enumerate(paths_and_nodes): node_data = node.data(0, Qt.UserRole) node_type = node_data["type"] save_flag = False # load the download link and file name from API if needed if node_data.get("download_link") is None: save_flag = True try: if node_type == "file": download_link = get_file_download_link( self.BbRouter, node_data["predownload_link"]) filename = get_filename_from_url(download_link) elif node_type == "recorded_lecture": download_link = get_recorded_lecture_download_link( self.BbRouter, node_data["predownload_link"]) filename = node_data["name"] + ".mp4" except Exception: trace = traceback.format_exc() progress_callback.emit((idx + 1, node_data["name"], False, None, None, trace)) data_deltas.append(None) continue else: download_link = node_data.get("download_link") filename = node_data.get("filename") full_file_path = os.path.join(path, sanitise_filename(filename)) if os.path.exists(full_file_path): numSkipped += 1 progress_callback.emit( (idx + 1, filename, False, None, None, None)) else: try: download( self.BbRouter, download_link, full_file_path, lambda bytes_downloaded, total_content_length: progress_callback.emit(( idx + 1, filename, True, bytes_downloaded, total_content_length, None, )), ) except Exception: numSkipped += 1 trace = traceback.format_exc() progress_callback.emit( (idx + 1, filename, False, None, None, trace)) numDownloaded += 1 data_deltas.append((download_link, filename) if save_flag else None) return (numDownloaded, numSkipped, data_deltas) def progress_fn(data): """ Progress text format: [overall_progress] [prefix] [filename] [current_file_progress] """ numDownloaded, filename, was_last_downloaded, bytes_downloaded, total_content_length, stack_trace = ( data) overall_progress = "({}/{})".format(numDownloaded, numFiles) prefix = "Downloading" if was_last_downloaded else "Skipping" current_file_progress = "" if was_last_downloaded: current_file_progress = ("({}/{})".format( convert_size(bytes_downloaded), convert_size(total_content_length), ) if total_content_length else "({})".format( convert_size(bytes_downloaded))) text = "{} {} {} {}".format(overall_progress, prefix, filename, current_file_progress) self.downloadProgressText.setText(text) self.progressBar.setValue(numDownloaded) if stack_trace: self.handle_error(filename, stack_trace) def display_result_and_update_node_data(result): self.setDownloadIgnoreButtonsEnabled(True) numDownloaded, numSkipped, data_deltas = result self.downloadProgressText.setText( "Completed. Downloaded {} files, skipped {} files".format( numDownloaded, numSkipped)) for delta, (_path, node) in zip(data_deltas, paths_and_nodes): if delta is None: continue download_link, filename = delta node_data = node.data(0, Qt.UserRole) node_data["download_link"] = download_link node_data["filename"] = filename node.setData(0, Qt.UserRole, node_data) try: self.logger.log_successful_download(numDownloaded) except Exception: pass worker = Worker(download_from_nodes) worker.signals.result.connect(display_result_and_update_node_data) worker.signals.finished.connect(self.reload_tree) worker.signals.progress.connect(progress_fn) self.threadPool.start(worker) def reload_tree(self): """update self.data based on new tree node data """ self.tree_to_data() self.__clear_tree() self.data_to_tree() def tree_to_data(self): """traverse tree to convert it to data and update self.data """ def traverse(node): node_data = node.data(0, Qt.UserRole) node_type = node_data["type"] if node_type == "folder": node_data["children"] = [ traverse(node.child(idx)) for idx in range(node.childCount()) ] return node_data root = self.tree.invisibleRootItem() self.data = [ traverse(root.child(idx)) for idx in range(root.childCount()) ] def data_to_tree(self): """traverse self.data and generate tree list widget. Files/videos that have already downloaded will not be displayed Raises: Exception: thrown on unknown data type """ if self.data is None: print( "Warning, there is not loaded data, was get_data() not called?" ) return self.__clear_tree() def traverse(data, parent, path): """recursively traverse NTU Learn data and render all file/video nodes, if the file/video already exists, set the node as hidden """ node = QtWidgets.QTreeWidgetItem(parent) # save relevant data fields into node.data node_data = {"name": data["name"], "type": data["type"]} data_type = data["type"] if data_type == "folder": node.setIcon(0, self.folderIcon) node.setFlags(node.flags() | Qt.ItemIsTristate | Qt.ItemIsUserCheckable) next_path = os.path.join(path, sanitise_filename(data["name"]), "") for child in data["children"]: traverse(child, node, next_path) elif data_type == "file" or data_type == "recorded_lecture": # add file/video attributes node_data["predownload_link"] = data["predownload_link"] node_data["download_link"] = data.get("download_link") node_data["filename"] = data.get("filename") # ignore file if dummy file is present is_dummy_file_present = dummy_file_exists( path, sanitise_filename(node_data["name"])) is_file_present = node_data["filename"] and os.path.exists( os.path.join(path, sanitise_filename( node_data["filename"]))) if is_dummy_file_present or is_file_present: node.setHidden(True) node.setIcon( 0, self.fileIcon if data_type == "file" else self.videoIcon) node.setFlags(node.flags() | Qt.ItemIsUserCheckable) else: raise Exception("unknown type", data_type) node.setText(0, data["name"]) node.setCheckState(0, Qt.Unchecked) node.setData(0, Qt.UserRole, node_data) # iterate data (list) for item in self.data: traverse(item, self.tree, self.download_dir) def get_paths_and_selected_nodes( self, files=True, videos=False) -> List[Tuple[str, QtWidgets.QTreeWidgetItem]]: """returns lists of QTreeWidgetItem (nodes) that correspond to files/videos to download/ignore Args: files (bool, optional): whether to download files. Defaults to True. videos (bool, optional): whether to download videos. Defaults to False. Returns: List[Tuple[str, QtWidgets.QTreeWidgetItem]]: list of path, tree nodes tuples Note: Not possible to project down to filename, download link as loading of the downloading link is very slow (3s) for lecture videos """ result = [] def traverse(node, path): if node.checkState(0) == Qt.Unchecked or node.isHidden(): return node_data = node.data(0, Qt.UserRole) node_type = node_data["type"] if node_type == "file" or node_type == "recorded_lecture": result.append((path, node)) else: next_path = os.path.join(path, sanitise_filename(node_data["name"])) for index in range(node.childCount()): traverse(node.child(index), next_path) root = self.tree.invisibleRootItem() for idx in range(root.childCount()): traverse(root.child(idx), self.download_dir) return result def setDownloadIgnoreButtonsEnabled(self, flag: bool): self.downloadButton.setEnabled(flag) self.ignoreButton.setEnabled(flag) def __clear_tree(self): self.tree.clear() def __handle_select_type(self, obj_type: str): assert obj_type in [ "file", "recorded_lecture", ], "unexpected obj_type: {}".format(obj_type) def is_same_type(node, obj_type: str) -> bool: node_data = node.data(0, Qt.UserRole) node_type = isinstance(node_data, dict) and node_data["type"] node_name = isinstance(node_data, dict) and node_data["name"] if obj_type == "file": return (node_type == obj_type and isinstance(node_name, str) and not node_name.endswith(".mp4")) else: return node_type == "recorded_lecture" or (isinstance( node_name, str) and node_name.endswith(".mp4")) def traverse(node): if is_same_type(node, obj_type): node.setCheckState(0, Qt.Checked) else: for index in range(node.childCount()): traverse(node.child(index)) root = self.tree.invisibleRootItem() traverse(root)
class RelialokMainWindow(object): def __init__(self, MainWindow, *args, **kwargs): ''' Main front-end GUI class. Defines backend functions for hardware interface and front-end widgets for control and monitoring of interlock. :param MainWindow: :param args: MainWindow (QtWidgets.QMainWindow()) :param kwargs: None ''' # Class variables and flags self.connection_open = False self.com_ports = [ comport.device for comport in serial.tools.list_ports.comports() ] self.serial = None self.setup_ui(MainWindow) def setup_ui(self, MainWindow): ''' Definition of GUI widgets, positioning, and framing. Also connects functions to widget hooks. ''' # Gui definition MainWindow.setObjectName("MainWindow") MainWindow.resize(933, 725) MainWindow.setMinimumSize(QtCore.QSize(933, 725)) MainWindow.setMaximumSize(QtCore.QSize(934, 726)) MainWindow.setAutoFillBackground(True) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") # Define GUI button positions self.pushButton = QtWidgets.QPushButton(self.centralwidget) self.pushButton.setGeometry(QtCore.QRect(670, 650, 75, 23)) self.pushButton.setObjectName("pushButton") self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget) self.pushButton_2.setGeometry(QtCore.QRect(750, 650, 75, 23)) self.pushButton_2.setAutoFillBackground(True) self.pushButton_2.setObjectName("pushButton_2") self.pushButton_7 = QtWidgets.QPushButton(self.centralwidget) self.pushButton_7.setGeometry(QtCore.QRect(830, 650, 75, 23)) self.pushButton_7.setObjectName("pushButton_7") # Define text box self.textBox = QtWidgets.QTextEdit(self.centralwidget) self.textBox.setGeometry(QtCore.QRect(20, 240, 881, 381)) self.textBox.setObjectName("textBox") # Define cursor in text box self.cursor = QTextCursor(self.textBox.document()) # Define GUI structure & grid layout self.line = QtWidgets.QFrame(self.centralwidget) self.line.setGeometry(QtCore.QRect(20, 160, 321, 16)) self.line.setFrameShape(QtWidgets.QFrame.HLine) self.line.setFrameShadow(QtWidgets.QFrame.Sunken) self.line.setObjectName("line") self.line_2 = QtWidgets.QFrame(self.centralwidget) self.line_2.setGeometry(QtCore.QRect(420, 160, 321, 20)) self.line_2.setFrameShape(QtWidgets.QFrame.HLine) self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) self.line_2.setObjectName("line_2") self.label_9 = QtWidgets.QLabel(self.centralwidget) self.label_9.setGeometry(QtCore.QRect(360, 160, 47, 16)) self.gridLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget) self.gridLayoutWidget_2.setGeometry(QtCore.QRect(759, 30, 141, 201)) self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2") self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_2) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setObjectName("gridLayout_2") self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget) self.horizontalLayoutWidget.setGeometry(QtCore.QRect(20, 190, 721, 41)) self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") self.horizontalLayout = QtWidgets.QHBoxLayout( self.horizontalLayoutWidget) self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setObjectName("horizontalLayout") self.gridLayoutWidget = QtWidgets.QWidget(self.centralwidget) self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 29, 721, 111)) self.gridLayoutWidget.setObjectName("gridLayoutWidget") self.gridLayout_3 = QtWidgets.QGridLayout(self.gridLayoutWidget) self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setObjectName("gridLayout_3") self.gridLayout_3.setSpacing(20) self.widget = QtWidgets.QWidget(self.gridLayoutWidget_2) self.widget.setObjectName("widget") self.gridLayout_2.addWidget(self.widget, 0, 0, 1, 1) self.comboBox = QtWidgets.QComboBox(self.centralwidget) self.comboBox.setGeometry(QtCore.QRect(20, 650, 69, 22)) self.comboBox.setObjectName("com_ports") self.comboBox.addItems(self.com_ports) font = QtGui.QFont() font.setPointSize(12) font.setBold(True) font.setWeight(75) # Place & configure buttons self.pushButton_4 = QtWidgets.QPushButton(self.gridLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_4.sizePolicy().hasHeightForWidth()) self.pushButton_4.setSizePolicy(sizePolicy) self.pushButton_4.setObjectName("pushButton_4") self.gridLayout_2.addWidget(self.pushButton_4, 1, 0, 1, 1) self.pushButton_5 = QtWidgets.QPushButton(self.gridLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_5.sizePolicy().hasHeightForWidth()) self.pushButton_5.setSizePolicy(sizePolicy) self.pushButton_5.setObjectName("pushButton_5") self.gridLayout_2.addWidget(self.pushButton_5, 3, 0, 1, 1) self.pushButton_6 = QtWidgets.QPushButton(self.gridLayoutWidget_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.pushButton_6.sizePolicy().hasHeightForWidth()) self.pushButton_6.setSizePolicy(sizePolicy) self.pushButton_6.setObjectName("pushButton_6") self.gridLayout_2.addWidget(self.pushButton_6, 2, 0, 1, 1) # Define readout LEDs and place in GUI structure # Power ON light self.led_25 = LedIndicatorWidget.LedIndicator(color='blue') self.gridLayout_2.addWidget(self.led_25, 0, 0, 1, 1) self.led_25.setGeometry(QtCore.QRect(40, 0, 61, 31)) self.led_25.setObjectName("led_25") self.led_25.setChecked(False) # Fault 1 self.led_f1 = LedIndicatorWidget.LedIndicator(color='red') self.led_f1.setObjectName("led_fault_1") self.horizontalLayout.addWidget(self.led_f1) self.led_f1.setChecked(False) # Fault 2 self.led_f2 = LedIndicatorWidget.LedIndicator(color='red') self.led_f2.setObjectName("led_fault_2") self.horizontalLayout.addWidget(self.led_f2) self.led_f2.setChecked(False) # Fault 3 self.led_f3 = LedIndicatorWidget.LedIndicator(color='red') self.led_f3.setObjectName("led_fault_3") self.horizontalLayout.addWidget(self.led_f3) self.led_f3.setChecked(False) # Fault 4 self.led_f4 = LedIndicatorWidget.LedIndicator(color='red') self.led_f4.setObjectName("led_fault_4") self.horizontalLayout.addWidget(self.led_f4) self.led_f4.setChecked(False) # Fault 5 self.led_f5 = LedIndicatorWidget.LedIndicator(color='red') self.led_f5.setObjectName("led_fault_5") self.horizontalLayout.addWidget(self.led_f5) self.led_f5.setChecked(False) # Fault 6 self.led_f6 = LedIndicatorWidget.LedIndicator(color='red') self.led_f6.setObjectName("led_fault_6") self.horizontalLayout.addWidget(self.led_f6) self.led_f6.setChecked(False) # Fault 7 self.led_f7 = LedIndicatorWidget.LedIndicator(color='red') self.led_f7.setObjectName("led_fault_7") self.horizontalLayout.addWidget(self.led_f7) self.led_f7.setChecked(False) # Fault 8 self.led_f8 = LedIndicatorWidget.LedIndicator(color='red') self.led_f8.setObjectName("led_fault_8") self.horizontalLayout.addWidget(self.led_f8) self.led_f8.setChecked(False) # ON-1 self.led_on_1 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_1.setObjectName("led_on_1") self.gridLayout_3.addWidget(self.led_on_1, 1, 0, 1, 1) self.led_on_1.setChecked(False) # OFF-1 self.led_off_1 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_1.setObjectName("led_off_2") self.gridLayout_3.addWidget(self.led_off_1, 2, 0, 1, 1) self.led_off_1.setChecked(False) # ON-2 self.led_on_2 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_2.setObjectName("led_on_2") self.gridLayout_3.addWidget(self.led_on_2, 1, 1, 1, 1) self.led_on_2.setChecked(False) # OFF-2 self.led_off_2 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_2.setObjectName("led_off_2") self.gridLayout_3.addWidget(self.led_off_2, 2, 1, 1, 1) self.led_off_2.setChecked(False) # ON-3 self.led_on_3 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_3.setObjectName("led_on_3") self.gridLayout_3.addWidget(self.led_on_3, 1, 2, 1, 1) self.led_on_3.setChecked(False) # OFF-3 self.led_off_3 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_3.setObjectName("led_off_3") self.gridLayout_3.addWidget(self.led_off_3, 2, 2, 1, 1) self.led_off_3.setChecked(False) # ON-4 self.led_on_4 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_4.setObjectName("led_on_4") self.gridLayout_3.addWidget(self.led_on_4, 1, 3, 1, 1) self.led_on_4.setChecked(False) # OFF-4 self.led_off_4 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_4.setObjectName("led_off_4") self.gridLayout_3.addWidget(self.led_off_4, 2, 3, 1, 1) self.led_off_4.setChecked(False) # ON-5 self.led_on_5 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_5.setObjectName("led_on_5") self.gridLayout_3.addWidget(self.led_on_5, 1, 4, 1, 1) self.led_on_5.setChecked(False) # OFF-5 self.led_off_5 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_5.setObjectName("led_off_5") self.gridLayout_3.addWidget(self.led_off_5, 2, 4, 1, 1) self.led_off_5.setChecked(False) # ON-6 self.led_on_6 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_6.setObjectName("led_on_6") self.gridLayout_3.addWidget(self.led_on_6, 1, 5, 1, 1) self.led_on_6.setChecked(False) # OFF-6 self.led_off_6 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_6.setObjectName("led_off_6") self.gridLayout_3.addWidget(self.led_off_6, 2, 5, 1, 1) self.led_off_6.setChecked(False) # ON-7 self.led_on_7 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_7.setObjectName("led_on_7") self.gridLayout_3.addWidget(self.led_on_7, 1, 6, 1, 1) self.led_on_7.setChecked(False) # OFF-7 self.led_off_7 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_7.setObjectName("led_off_7") self.gridLayout_3.addWidget(self.led_off_7, 2, 6, 1, 1) self.led_off_7.setChecked(False) # ON-8 self.led_on_8 = LedIndicatorWidget.LedIndicator(color='green') self.led_on_8.setObjectName("led_on_8") self.gridLayout_3.addWidget(self.led_on_8, 1, 7, 1, 1) self.led_on_8.setChecked(False) # OFF-8 self.led_off_8 = LedIndicatorWidget.LedIndicator(color='red') self.led_off_8.setObjectName("led_off_8") self.gridLayout_3.addWidget(self.led_off_8, 2, 7, 1, 1) self.led_off_8.setChecked(False) # Define labels & place self.label_6 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_6.setAlignment(QtCore.Qt.AlignCenter) self.label_6.setObjectName("label_6") self.gridLayout_3.addWidget(self.label_6, 0, 5, 1, 1) self.label = QtWidgets.QLabel(self.gridLayoutWidget) self.label.setAlignment(QtCore.Qt.AlignCenter) self.label.setObjectName("label") self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1) self.label_2 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setObjectName("label_2") self.gridLayout_3.addWidget(self.label_2, 0, 1, 1, 1) self.label_3 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_3.setAlignment(QtCore.Qt.AlignCenter) self.label_3.setObjectName("label_3") self.gridLayout_3.addWidget(self.label_3, 0, 2, 1, 1) self.label_4 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_4.setAlignment(QtCore.Qt.AlignCenter) self.label_4.setObjectName("label_4") self.gridLayout_3.addWidget(self.label_4, 0, 3, 1, 1) self.label_5 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_5.setAlignment(QtCore.Qt.AlignCenter) self.label_5.setObjectName("label_5") self.gridLayout_3.addWidget(self.label_5, 0, 4, 1, 1) self.label_7 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_7.setAlignment(QtCore.Qt.AlignCenter) self.label_7.setObjectName("label_7") self.gridLayout_3.addWidget(self.label_7, 0, 6, 1, 1) self.label_8 = QtWidgets.QLabel(self.gridLayoutWidget) self.label_8.setAlignment(QtCore.Qt.AlignCenter) self.label_8.setObjectName("label_8") self.gridLayout_3.addWidget(self.label_8, 0, 7, 1, 1) self.label_9.setFont(font) self.label_9.setObjectName("label_9") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 933, 21)) self.menubar.setObjectName("menubar") self.menuOctolok = QtWidgets.QMenu(self.menubar) self.menuOctolok.setObjectName("menuOctolok") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.menubar.addAction(self.menuOctolok.menuAction()) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) # Define function dispatcher to set state of of LED self.dispatcher = { 'FAULT': [ self.led_f1.setChecked, self.led_f2.setChecked, self.led_f3.setChecked, self.led_f4.setChecked, self.led_f5.setChecked, self.led_f6.setChecked, self.led_f7.setChecked, self.led_f8.setChecked ], 'ON-OFF': [[self.led_on_1.setChecked, self.led_off_1.setChecked], [self.led_on_2.setChecked, self.led_off_2.setChecked], [self.led_on_3.setChecked, self.led_off_3.setChecked], [self.led_on_4.setChecked, self.led_off_4.setChecked], [self.led_on_5.setChecked, self.led_off_5.setChecked], [self.led_on_6.setChecked, self.led_off_6.setChecked], [self.led_on_7.setChecked, self.led_off_7.setChecked], [self.led_on_8.setChecked, self.led_off_8.setChecked]] } # Button hooks self.pushButton.clicked.connect(self.connect) self.pushButton_2.clicked.connect(self.disconnect) self.pushButton_4.clicked.connect(self.get_status) self.pushButton_6.clicked.connect(lambda: self.decode('DISABLE ')) self.pushButton_7.clicked.connect(self.close) def retranslateUi(self, MainWindow): ''' Redefines label and button names for front-end usages. :param MainWindow: QtWidgets.QMainWindow() :return: None ''' _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.pushButton.setText(_translate("MainWindow", "Connect")) self.pushButton_2.setText(_translate("MainWindow", "Disconnect")) self.pushButton_4.setText(_translate("MainWindow", "Status")) self.pushButton_5.setText(_translate("MainWindow", "Reset")) self.pushButton_6.setText(_translate("MainWindow", "Disable")) self.pushButton_7.setText(_translate("MainWindow", "Close")) self.label_5.setText(_translate("MainWindow", "5")) self.label_6.setText(_translate("MainWindow", "6")) self.label.setText(_translate("MainWindow", "1")) self.label_3.setText(_translate("MainWindow", "3")) self.label_7.setText(_translate("MainWindow", "7")) self.label_2.setText(_translate("MainWindow", "2")) self.label_8.setText(_translate("MainWindow", "8")) self.label_4.setText(_translate("MainWindow", "4")) self.label_9.setText(_translate("MainWindow", "Fault")) self.menuOctolok.setTitle(_translate("MainWindow", "Octolok")) # Start thread self.threadpool = QThreadPool() print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) def connect(self): ''' Create instance of serial port handler, SerialPort(). Passes combo boxx selection containing current serial port. :return: None ''' self.print_output( "Initializing serial connection on port: {port}".format( port=self.comboBox.currentText())) self.serial = SerialPort(self.comboBox.currentText()) self.connection_open = True self.listen() def close(self): ''' Ends MainWindow session. :return: None ''' self.threadpool.waitForDone(2000) self.serial.disconnect() MainWindow.close() def disconnect(self): ''' Waits for completion of current thread in threadpool and closes serial port connection. If current connection doesn't exist, function passes and does nothing. :return: None ''' if self.connection_open: try: self.print_output( "Disconnecting serial connection on port: {port}".format( port=self.comboBox.currentText())) self.threadpool.waitForDone(2000) self.threadpool.clear() self.serial.disconnect() self.led_25.setChecked(False) except Exception as ex: pass else: self.print_output('Connection not active. Please connect.') def print_output(self, s): ''' Wrapper function to print to front-end text box. :param s: string-type to send to text box. :return: None ''' self.cursor.setPosition(0) self.textBox.setTextCursor(self.cursor) self.textBox.insertPlainText('[{ts}] {msg}\n'.format(ts=time.ctime( time.time())[11:-5], msg=s)) def decode(self, message): ''' Parses and decodes incoming communication from hardware device. :param reply: string-type reply or interrupt message from hardware. :return: None ''' self.print_output('Decoding response ...') try: type, reply = message.split(' ') reply = reply.strip() self.status = reply[::2] self.state = reply[1::2] self.status = [ bool(int(self.status[i])) for i in range(len(self.status)) ] self.state = [ bool(int(self.state[i])) for i in range(len(self.state)) ] if type == 'STATUS': for (on, off), i in zip(self.dispatcher['ON-OFF'], range(len(self.dispatcher['ON-OFF']))): if not self.status[i]: on(False) off(False) else: on(self.state[i]) off(not self.state[i]) elif type == 'DISABLE': for (on, off), i in zip(self.dispatcher['ON-OFF'], range(len(self.dispatcher['ON-OFF']))): on(False) off(False) elif type == 'FAULT': self.fault = [ bool(int(self.reply[i])) for i in range(len(self.reply)) ] for fault, i in zip(self.dispatcher['FAULT'], range(len(self.dispatcher['FAULT']))): fault(self.fault[i]) else: pass except AttributeError as ex: pass def data_received(self, data): print('Data at COM port:') self.print_output(data) def listen(self): ''' Spanwns polling thread to look for interrupt communication form hardware device at COM port. Function has secondary priority to serial port resource. :return: None ''' if self.connection_open: try: self.led_25.setChecked(True) # Pass the function to execute worker = WorkerThreading.Worker(self.serial.listen) worker.signals.result.connect(self.decode) # worker.signals.finished.connect(self.thread_complete) worker.signals.progress.connect(self.data_received) # Execute self.threadpool.start(worker, priority=3) except Exception as ex: self.print_output( 'Error listening on port: {0}. Check log for more details.' .format(ex)) else: self.print_output('Connection not active. Please connect.') return def get_status(self): ''' Spawns thread for sending command to hardware. Sends STATUS? command to hardware ans then waits for reply. Reply is sent to decode fucntion. Has top priority to serial port resource. :param cmd: :return: ''' if self.connection_open: try: # Pass the function to execute worker = WorkerThreading.Worker(self.serial.send, command='STATUS?') worker.signals.result.connect(self.decode) # worker.signals.finished.connect(self.thread_complete) # Execute self.threadpool.start(worker, priority=4) except Exception as ex: self.print_output( 'Error reading on port: {0}. Check log for more details.'. format(ex)) else: self.print_output('Connection not active. Please connect.') return
# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool from functools import wraps import traceback threadpool = QThreadPool() class Runner(QRunnable): def __init__(self, func, *args, **kwargs): QRunnable.__init__(self) self.func = func self.args = args self.kwargs = kwargs @pyqtSlot() def run(self): try: setattr(self.args[0], 'separate_thread_running', True) except: pass
class ImageViewerWidget(QWidget,Ui_Image_Viewer_Widget): def __init__(self,parent=None): super(ImageViewerWidget,self).__init__(parent) self.setupUi(self) self.viewer=ImageViewer() self.viewer.scene().itemAdded.connect(self._scene_item_added) self.center_layout.addWidget(self.viewer,0,0) # self._label_background=QLabel() # self._label_background.setFixedHeight(40) # image = GUIUtilities.get_image("label.png") # self._label_background.setPixmap(image.scaledToHeight(40)) # self.center_layout.addWidget(self._label_background,0,0,QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self._label=QLabel() self._label.setVisible(False) self._label.setMargin(5) self._label.setStyleSheet(''' QLabel{ font: 12pt; border-radius: 25px; margin: 10px; color: black; background-color: #FFFFDC; } ''') shadow=QGraphicsDropShadowEffect(self) shadow.setBlurRadius(8) # shadow.setColor(QtGui.QColor(76,35,45).lighter()) shadow.setColor(QtGui.QColor(94, 93, 90).lighter()) shadow.setOffset(2) self._label.setGraphicsEffect(shadow) self.center_layout.addWidget(self._label,0,0,QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self.actions_layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.actions_layout.setContentsMargins(0,5,0,0) self._ds_dao = DatasetDao() self._hub_dao = HubDao() self._labels_dao = LabelDao() self._annot_dao = AnnotaDao() self._thread_pool=QThreadPool() self._loading_dialog=QLoadingDialog() self._source = None self._image = None self.images_list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection ) #self.images_list_widget.setSelectionMode(QAbstractItemView.SingleSelection) self.images_list_widget.currentItemChanged.connect(self.image_list_sel_changed_slot) self.images_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.images_list_widget.customContextMenuRequested.connect(self.image_list_context_menu) self.treeview_models=ModelsTreeview() self.treeview_models.setColumnWidth(0,300) self.tree_view_models_layout.addWidget(self.treeview_models) self.treeview_models.action_click.connect(self.trv_models_action_click_slot) self.treeview_labels = LabelsTableView() self.treeview_labels.action_click.connect(self.trv_labels_action_click_slot) self.tree_view_labels_layout.addWidget(self.treeview_labels) self.treeview_labels.selectionModel().selectionChanged.connect(self.default_label_changed_slot) #window = GUIUtilities.findMainWindow() #window.keyPressed.connect(self.window_keyPressEvent) self.create_actions_bar() def image_list_context_menu(self, pos: QPoint): menu=QMenu() result=self._labels_dao.fetch_all(self.source.dataset) if len(result) > 0: labels_menu=menu.addMenu("labels") for vo in result: action=labels_menu.addAction(vo.name) action.setData(vo) action=menu.exec_(QCursor.pos()) if action and isinstance(action.data(),LabelVO): label=action.data() self.change_image_labels(label) def change_image_labels(self, label: LabelVO): items = self.images_list_widget.selectedItems() selected_images = [] for item in items: vo = item.tag selected_images.append(vo) @work_exception def do_work(): self._ds_dao.tag_entries(selected_images, label) return 1,None @gui_exception def done_work(result): status, err = result if err: raise err worker = Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) def default_label_changed_slot(self, selection: QItemSelection): selected_rows =self.treeview_labels.selectionModel().selectedRows(2) if len(selected_rows) > 0: index: QModelIndex = selected_rows[0] current_label: LabelVO=self.treeview_labels.model().data(index) self.viewer.current_label = current_label def image_list_sel_changed_slot(self,curr: CustomListWidgetItem,prev: CustomListWidgetItem): if curr: self.source = curr.tag @property def image(self): return self._image @property def source(self)-> DatasetEntryVO: return self._source @source.setter def source(self, value): if not isinstance(value, DatasetEntryVO): raise Exception("Invalid source") self._source= value image_path = self._source.file_path self._image=Image.open(image_path) self.viewer.pixmap=QPixmap(image_path) self.load_image_annotations() self.load_image_label() @gui_exception def load_images(self): @work_exception def do_work(): entries=self._ds_dao.fetch_entries(self.source.dataset) return entries,None @gui_exception def done_work(result): data,error=result selected_item = None for vo in data: item = CustomListWidgetItem(vo.file_path) item.setIcon(GUIUtilities.get_icon("image.png")) item.tag = vo if vo.file_path == self.source.file_path: selected_item = item self.images_list_widget.addItem(item) self.images_list_widget.setCursor(QtCore.Qt.PointingHandCursor) self.images_list_widget.setCurrentItem(selected_item) worker=Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) @gui_exception def load_models(self): @work_exception def do_work(): results = self._hub_dao.fetch_all() return results, None @gui_exception def done_work(result): result, error = result if result: for model in result: self.treeview_models.add_node(model) worker=Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) @gui_exception def load_labels(self): @work_exception def do_work(): results = self._labels_dao.fetch_all(self.source.dataset) return results, None @gui_exception def done_work(result): result, error = result if error is None: for entry in result: self.treeview_labels.add_row(entry) worker=Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) @gui_exception def load_image_annotations(self): @work_exception def do_work(): results=self._annot_dao.fetch_all(self.source.id) return results,None @gui_exception def done_work(result): result,error=result if error: raise error img_bbox: QRectF=self.viewer.pixmap.sceneBoundingRect() offset=QPointF(img_bbox.width()/2,img_bbox.height()/2) for entry in result: try: vo : AnnotaVO = entry points = map(float,vo.points.split(",")) points = list(more_itertools.chunked(points, 2)) if vo.kind == "box": x = points[0][0] - offset.x() y= points[0][1] - offset.y() w = math.fabs(points[0][0] - points[1][0]) h = math.fabs(points[0][1] - points[1][1]) roi : QRectF = QRectF(x,y,w,h) rect=EditableBox(roi) rect.label = vo.label self.viewer.scene().addItem(rect) elif vo.kind == "polygon": polygon = EditablePolygon() polygon.label = vo.label self.viewer.scene().addItem(polygon) for p in points: polygon.addPoint(QPoint(p[0] - offset.x(), p[1] - offset.y())) except Exception as ex: print(ex) worker=Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) @gui_exception def load_image_label(self): @work_exception def do_work(): label=self._annot_dao.get_label(self.source.id) return label,None @gui_exception def done_work(result): label_name,error=result if error: raise error if label_name: self._label.setVisible(True) self._label.setText(label_name) else: self._label.setVisible(False) self._label.setText("") worker=Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) @gui_exception def add_repository(self): @work_exception def do_work(repo): hub_client=HubClientFactory.create(Framework.PyTorch) hub=hub_client.fetch_model(repo,force_reload=True) self._hub_dao.save(hub) return hub,None @gui_exception def done_work(result): self._loading_dialog.close() data,error=result if error is None: self.treeview_models.add_node(data) form=NewRepoForm() if form.exec_() == QDialog.Accepted: repository=form.result worker=Worker(do_work, repository) worker.signals.result.connect(done_work) self._thread_pool.start(worker) self._loading_dialog.exec_() def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: row = self.images_list_widget.currentRow() last_index = self.images_list_widget.count() - 1 if event.key() == QtCore.Qt.Key_A: self.save_annotations() if row > 0: self.images_list_widget.setCurrentRow(row-1) else: self.images_list_widget.setCurrentRow(last_index) if event.key() == QtCore.Qt.Key_D: self.save_annotations() if row < last_index: self.images_list_widget.setCurrentRow(row+1) else: self.images_list_widget.setCurrentRow(0) if event.key() == QtCore.Qt.Key_W: self.viewer.selection_mode=SELECTION_MODE.POLYGON if event.key() == QtCore.Qt.Key_S: self.viewer.selection_mode=SELECTION_MODE.BOX super(ImageViewerWidget,self).keyPressEvent(event) @gui_exception def trv_models_action_click_slot(self, action: QAction): if action.text() == self.treeview_models.CTX_MENU_NEW_DATASET_ACTION: self.add_repository() elif action.text() == self.treeview_models.CTX_MENU_AUTO_LABEL_ACTION: current_node = action.data() # model name parent_node = current_node.parent # repo repo, model = parent_node.get_data(0),current_node.get_data(0) self.autolabel(repo, model) @gui_exception def trv_labels_action_click_slot(self,action: QAction): model = self.treeview_labels.model() if action.text() == self.treeview_labels.CTX_MENU_ADD_LABEL: form=NewLabelForm() if form.exec_() == QDialog.Accepted: label_vo: LabelVO=form.result label_vo.dataset=self.source.dataset label_vo = self._labels_dao.save(label_vo) self.treeview_labels.add_row(label_vo) elif action.text() == self.treeview_labels.CTX_MENU_DELETE_LABEL: index : QModelIndex = action.data() if index: label_vo=model.index(index.row(),2).data() self._labels_dao.delete(label_vo.id) self.viewer.remove_annotations_by_label(label_vo.name) model.removeRow(index.row()) def autolabel(self, repo, model_name): def do_work(): try: print(repo, model_name) from PIL import Image from torchvision import transforms import torch model=torch.hub.load(repo,model_name,pretrained=True) model.eval() input_image=Image.open(self.source.file_path) preprocess=transforms.Compose([ transforms.Resize(480), transforms.ToTensor(), transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225]), ]) input_tensor=preprocess(input_image) input_batch=input_tensor.unsqueeze(0) # create a mini-batch as expected by the model # move the param and model to GPU for speed if available if torch.cuda.is_available(): input_batch=input_batch.to('cuda') model.to('cuda') with torch.no_grad(): output=model(input_batch)['out'][0] output_predictions=output.argmax(0) # create a color pallette, selecting a color for each class palette=torch.tensor([2 ** 25-1,2 ** 15-1,2 ** 21-1]) colors=torch.as_tensor([i for i in range(21)])[:,None]*palette colors=(colors%255).numpy().astype("uint8") # plot the semantic segmentation predictions of 21 classes in each color predictions_array: np.ndarray=output_predictions.byte().cpu().numpy() predictions_image=Image.fromarray(predictions_array).resize(input_image.size) predictions_image.putpalette(colors) labels_mask=np.asarray(predictions_image) classes=list(filter(lambda x: x != 0,np.unique(labels_mask).tolist())) classes_map={c: [] for c in classes} for c in classes: class_mask=np.zeros(labels_mask.shape,dtype=np.uint8) class_mask[np.where(labels_mask == c)]=255 contour_list=cv2.findContours(class_mask.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) contour_list=imutils.grab_contours(contour_list) for contour in contour_list: points=np.vstack(contour).squeeze().tolist() classes_map[c].append(points) return classes_map,None except Exception as ex: return None,ex def done_work(result): self._loading_dialog.close() classes_map,err=result if err: return for class_idx,contours in classes_map.items(): for c in contours: points=[] for i in range(0,len(c),10): points.append(c[i]) polygon=EditablePolygon() self.viewer._scene.addItem(polygon) bbox: QRectF=self.viewer.pixmap.boundingRect() offset=QPointF(bbox.width()/2,bbox.height()/2) for point in points: polygon.addPoint(QPoint(point[0] - offset.x(),point[1] - offset.y())) worker=Worker(do_work) worker.signals.result.connect(done_work) self._thread_pool.start(worker) self._loading_dialog.exec_() def create_actions_bar(self): icon_size = QSize(28,28) self.btn_enable_polygon_selection=ImageButton(icon=GUIUtilities.get_icon("polygon.png"),size=icon_size) self.btn_enable_rectangle_selection=ImageButton(icon=GUIUtilities.get_icon("square.png"),size=icon_size) self.btn_enable_free_selection=ImageButton(icon=GUIUtilities.get_icon("highlighter.png"),size=icon_size) self.btn_enable_none_selection=ImageButton(icon=GUIUtilities.get_icon("cursor.png"),size=icon_size) self.btn_save_annotations = ImageButton(icon=GUIUtilities.get_icon("save-icon.png"),size=icon_size) self.btn_clear_annotations=ImageButton(icon=GUIUtilities.get_icon("clean.png"),size=icon_size) self.actions_layout.addWidget(self.btn_enable_rectangle_selection) self.actions_layout.addWidget(self.btn_enable_polygon_selection) self.actions_layout.addWidget(self.btn_enable_free_selection) self.actions_layout.addWidget(self.btn_enable_none_selection) self.actions_layout.addWidget(self.btn_clear_annotations) self.actions_layout.addWidget(self.btn_save_annotations) self.btn_save_annotations.clicked.connect(self.btn_save_annotations_clicked_slot) self.btn_enable_polygon_selection.clicked.connect(self.btn_enable_polygon_selection_clicked_slot) self.btn_enable_rectangle_selection.clicked.connect(self.btn_enable_rectangle_selection_clicked_slot) self.btn_enable_free_selection.clicked.connect(self.btn_enable_free_selection_clicked_slot) self.btn_enable_none_selection.clicked.connect(self.btn_enable_none_selection_clicked_slot) self.btn_clear_annotations.clicked.connect(self.btn_clear_annotations_clicked_slot) def btn_clear_annotations_clicked_slot(self): self.viewer.remove_annotations() def btn_enable_polygon_selection_clicked_slot(self): self.viewer.selection_mode=SELECTION_MODE.POLYGON def btn_enable_rectangle_selection_clicked_slot(self): self.viewer.selection_mode=SELECTION_MODE.BOX def btn_enable_none_selection_clicked_slot(self): self.viewer.selection_mode=SELECTION_MODE.NONE def btn_enable_free_selection_clicked_slot(self): self.viewer.selection_mode=SELECTION_MODE.FREE def save_annotations(self): scene: QGraphicsScene=self.viewer.scene() annot_list=[] for item in scene.items(): img_bbox: QRectF=self.viewer.pixmap.sceneBoundingRect() offset=QPointF(img_bbox.width()/2,img_bbox.height()/2) if isinstance(item,EditableBox): item_box: QRectF=item.sceneBoundingRect() x1=math.floor(item_box.topLeft().x()+offset.x()) y1=math.floor(item_box.topRight().y()+offset.y()) x2=math.floor(item_box.bottomRight().x()+offset.x()) y2=math.floor(item_box.bottomRight().y()+offset.y()) box=AnnotaVO() box.label=item.label.id if item.label else None box.entry=self.source.id box.kind="box" box.points=",".join(map(str,[x1,y1,x2,y2])) annot_list.append(box) elif isinstance(item,EditablePolygon): points=[[math.floor(pt.x()+offset.x()),math.floor(pt.y()+offset.y())] for pt in item.points] points=np.asarray(points).flatten().tolist() poly=AnnotaVO() poly.label=item.label.id if item.label else None poly.entry=self.source.id poly.kind="polygon" poly.points=",".join(map(str,points)) annot_list.append(poly) self._annot_dao.save(self.source.id, annot_list) def btn_save_annotations_clicked_slot(self): self.save_annotations() def _scene_item_added(self, item: QGraphicsItem): item.tag = self.source def bind(self): self.load_images() self.load_models() self.load_labels()
def addSongs(self, songs): d = DBRunnable(self, songs) QThreadPool.globalInstance().start(d)
def __init__(self,parent=None): super(ImageViewerWidget,self).__init__(parent) self.setupUi(self) self.viewer=ImageViewer() self.viewer.scene().itemAdded.connect(self._scene_item_added) self.center_layout.addWidget(self.viewer,0,0) # self._label_background=QLabel() # self._label_background.setFixedHeight(40) # image = GUIUtilities.get_image("label.png") # self._label_background.setPixmap(image.scaledToHeight(40)) # self.center_layout.addWidget(self._label_background,0,0,QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self._label=QLabel() self._label.setVisible(False) self._label.setMargin(5) self._label.setStyleSheet(''' QLabel{ font: 12pt; border-radius: 25px; margin: 10px; color: black; background-color: #FFFFDC; } ''') shadow=QGraphicsDropShadowEffect(self) shadow.setBlurRadius(8) # shadow.setColor(QtGui.QColor(76,35,45).lighter()) shadow.setColor(QtGui.QColor(94, 93, 90).lighter()) shadow.setOffset(2) self._label.setGraphicsEffect(shadow) self.center_layout.addWidget(self._label,0,0,QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) self.actions_layout.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter) self.actions_layout.setContentsMargins(0,5,0,0) self._ds_dao = DatasetDao() self._hub_dao = HubDao() self._labels_dao = LabelDao() self._annot_dao = AnnotaDao() self._thread_pool=QThreadPool() self._loading_dialog=QLoadingDialog() self._source = None self._image = None self.images_list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection ) #self.images_list_widget.setSelectionMode(QAbstractItemView.SingleSelection) self.images_list_widget.currentItemChanged.connect(self.image_list_sel_changed_slot) self.images_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.images_list_widget.customContextMenuRequested.connect(self.image_list_context_menu) self.treeview_models=ModelsTreeview() self.treeview_models.setColumnWidth(0,300) self.tree_view_models_layout.addWidget(self.treeview_models) self.treeview_models.action_click.connect(self.trv_models_action_click_slot) self.treeview_labels = LabelsTableView() self.treeview_labels.action_click.connect(self.trv_labels_action_click_slot) self.tree_view_labels_layout.addWidget(self.treeview_labels) self.treeview_labels.selectionModel().selectionChanged.connect(self.default_label_changed_slot) #window = GUIUtilities.findMainWindow() #window.keyPressed.connect(self.window_keyPressEvent) self.create_actions_bar()
class SomeObjectToDoComplicatedStuff(QRunnable): def __init__(self, name): QRunnable.__init__(self) self.name = name def run(self): print('running', self.name) a = 10 b = 30 c = 0 for i in range(5000000): c += a**b print('done', self.name) pool = QThreadPool.globalInstance() pool.setMaxThreadCount(10) batch_size = 100 workers = [None] * batch_size for i in range(batch_size): worker = SomeObjectToDoComplicatedStuff('object ' + str(i)) workers[i] = worker pool.start(worker) print('All cued') pool.waitForDone() # processing the results back
def __init__(self): super().__init__() self.threadpool = QThreadPool() self.threadpool.globalInstance() poolnum = tool.loadJsons(tool.setting_address)[0]['pool_num'] self.threadpool.setMaxThreadCount(int(poolnum))
def deffer_to_thread(self, cb, *args, **kwargs): args = (self, ) + args thread = Thread(self, cb, *args, **kwargs) QThreadPool.globalInstance().start(thread)
def start(self, action, data=None): QThreadPool.globalInstance().start( PugdebugAsyncTask(self, action, data) )