class Downloader(QObject): finished = pyqtSignal() def __init__(self, queue): """ Class that spawns the separate download threads. This is a separate class so it can be moved to its own thread and run simultaneously with post extraction. :param queue: The download queue in which extracted content is placed """ super().__init__() self.queue = queue self.run = True self.settings = QSettings("SomeGuySoftware", "RedditDownloader") self.download_pool = QThreadPool() self.download_pool.setMaxThreadCount( self.settings.value('thread_limit', 4, type=int)) def download(self): """Spawns the download pool threads""" while self.run: post = self.queue.get() if post is not None: self.download_pool.start(post) else: self.run = False self.download_pool.waitForDone() self.finished.emit() def stop(self): self.run = False self.download_pool.clear()
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, # |parent| parent=None): super(AsyncPoolController, self).__init__(parent) if maxThreadCount < 1: self.threadPool = QThreadPool.globalInstance() else: self.threadPool = QThreadPool() self.threadPool.setMaxThreadCount(maxThreadCount) # |_start| def _start(self, future): # Asynchronously invoke ``f``. apw = _AsyncPoolWorker(future) self.threadPool.start(apw) # |terminate| def _terminate(self): self.threadPool.waitForDone() del self.threadPool
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
class runDownload(QThread): finishDownload_signal = pyqtSignal() DownloadNums_signal = pyqtSignal(int) def __init__(self, gui): super(QThread, self).__init__() self.dl = gui.DL self.num = len(self.dl) self.save_path = gui.ui.SaveTextEdit.toPlainText() self.md_dir = gui.ui.checkBox.isChecked() self.FinishedNums = 0 self.ThreadNums = int(gui.ui.ThreadsSpinBox.text()) self.ThreadsPool = QThreadPool() self.ThreadsPool.setMaxThreadCount(int(gui.ui.ThreadsSpinBox.text())) def run(self) -> None: runList = [] for temp in self.dl: while True: if len(runList) < self.ThreadNums: self.inputThreadList(runList, temp) break else: for i in runList: if i.isFinished(): runList.remove(i) while len(runList) > 0: for i in runList: if i.isFinished(): runList.remove(i) self.finishDownload_signal.emit() self.RemoveTempFile() print('start ' + '\'' + self.save_path + '\'') os.startfile(self.save_path) def inputThreadList(self, runList, dl): down = Downloading(dl, self.md_dir, self.save_path) runList.append(down) down.FinishSignal.connect(self.ed_signal) down.start() def RemoveTempFile(self): if self.md_dir: a = os.listdir(self.save_path) for j in a: b = os.listdir(self.save_path + '/' + j) path_temp = self.save_path + '/' + j for i in b: if i[-3:] == 'tmp': os.remove(path_temp + r'/' + i) else: a = os.listdir(self.save_path) for i in a: if i[-3:] == 'tmp': os.remove(self.save_path + '/' + i) def ed_signal(self): self.FinishedNums += 1 self.DownloadNums_signal.emit(int((self.FinishedNums / self.num) * 100))
class TaskManagerSingleton(object): THREAD_POOL_SIZE = 10 def __init__(self): self.tasks = Queue() self.thread_pool = QThreadPool() self.thread_pool.setMaxThreadCount(self.THREAD_POOL_SIZE) def add_task(self, task: Task): self.tasks.put(task) self.thread_pool.start(TaskRunner(self)) def get_task(self) -> Task: return self.tasks.get()
class TaskConsumer(metaclass=SingletonMeta): def __init__(self) -> None: self.lock = threading.Lock() self.q = list() self.thread_pool = QThreadPool() self.thread_pool.setMaxThreadCount(1) def set_thread_pool(self, tp): self.thread_pool = tp def insert_task(self, task: Task) -> None: """ Insert task if whether its not in queue or its status is either done or failed. :param task: :return: """ with self.lock: self.__remove_finished_task() if not any(task.task_type == each.task_type for each in self.q): self.q.append(task) def get_task(self) -> Task: with self.lock: self.__remove_finished_task() if self.q: for each in self.q: if each.task_type == TaskTypes.SERIAL_OPEN: return each return self.q[0] else: return None def consume_task(self) -> None: task = self.get_task() if task and self.thread_pool.activeThreadCount() == 0: self.thread_pool.start(task) def clear_task_queue(self) -> None: self.q.clear() self.thread_pool.clear() def __remove_finished_task(self) -> None: clr_lst = [ index for index, task in enumerate(self.q) if task.status > TaskStatus.RUNNING ] for index in clr_lst: del self.q[index]
class Downloader(QObject): finished = pyqtSignal() download_count_signal = pyqtSignal(int) send_downloaded = pyqtSignal(dict) def __init__(self, queue, thread_limit): """ Class that spawns the separate download threads. This is a separate class so it can be moved to its own thread and run simultaneously with post extraction. :param queue: The download queue in which extracted content is placed """ super().__init__() self.logger = logging.getLogger('DownloaderForReddit.%s' % __name__) self.queue = queue self.download_count = 0 self.run = True self.download_pool = QThreadPool() self.download_pool.setMaxThreadCount(thread_limit) def download(self): """Spawns the download pool threads""" self.logger.info('Downloader started') while self.run: post = self.queue.get() if post is not None: post.download_complete_signal.complete.connect( self.send_download_dict) self.download_pool.start(post) self.download_count += 1 else: self.run = False self.download_pool.waitForDone() self.logger.info('Downloader finished', extra={'download_count': self.download_count}) self.download_count_signal.emit(self.download_count) self.finished.emit() def send_download_dict(self, download_dict): self.send_downloaded.emit(download_dict) def stop(self): self.run = False self.download_pool.clear()
class Tasks(QObject): def __init__(self): super(Tasks, self).__init__() self.pool = QThreadPool() self.pool.setMaxThreadCount(1) def process_result(self, task): print('Receiving', task) def start(self): for task in range(10): worker = Worker(task) worker.signals.result.connect(self.process_result) self.pool.start(worker) self.pool.waitForDone()
class HnQTThreadPool(): thread_list = [] 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 addThread(self, _thread): self.thread_list.append(_thread) def Start(self): for i in self.thread_list: self.threadpool.start(i) self.threadpool.waitForDone() self.thread_list.clear()
class Fuzzer(QObject): communication = None def __init__(self, communication, max_thread_number, inputs, wordlist): super(Fuzzer, self).__init__() self.communication = communication self.max_thread_number = max_thread_number self.pool = QThreadPool() self.pool.globalInstance() self.inputs = inputs self.wordlist = wordlist self.socket_data = [inputs[0], inputs[1], inputs[2]] def start(self): num_of_chunks = len(self.wordlist) // int(self.max_thread_number) new_fuzz_list = list(self.listChunks(self.wordlist, num_of_chunks)) self.pool.setMaxThreadCount(self.max_thread_number) for sub_list in new_fuzz_list: QCoreApplication.processEvents() for word in sub_list: QCoreApplication.processEvents() if self.pool.activeThreadCount() < self.pool.maxThreadCount(): fuzz_thread = Fuzz(self.communication, self.socket_data, self.inputs[3], str(word), self.inputs[5]) fuzz_thread.setAutoDelete(True) self.pool.start(fuzz_thread) else: self.pool.waitForDone() def __del__(self): pass def listChunks(self, myList, numOfChunks): for i in range(0, len(myList), numOfChunks): yield myList[i:i + numOfChunks]
class Server(QTcpServer): """ create tcp server and listen for new client connection add new connection to ThreadPool for communicate with that """ def __init__(self, parent=None): """ initialize Queue, Manager and ThreadPool for work with that """ super().__init__(parent) self.pool = QThreadPool(self) self.pool.setMaxThreadCount(5000) self.queues = {'HQueue': PGQ('HQueue'), 'MQueue': PGQ('MQueue'), 'LQueue': PGQ('LQueue')} def start(self): """ start listening on specific tcp ip/port and connect to TcpServer error signal """ self.setMaxPendingConnections(5000) if self.listen(QHostAddress.AnyIPv4, 8001): print("Server: started") else: raise Exception(self.errorString()) self.acceptError.connect(self.error) def incomingConnection(self, socketDescriptor: int): """ this method called when new connection will be available initialize Handler and add it to ThreadPool """ handle = Handler(socketDescriptor, self.queues) handle.setAutoDelete(True) self.pool.start(handle) @staticmethod def error(e): """ called with TcpServer error signal and handle received error """ print(e.errorString())
class PackageMonitor(QThread): signal = pyqtSignal() def __init__(self): super().__init__() self.pool = QThreadPool() self.pool.globalInstance() self.pool.setMaxThreadCount(3) def run(self): flag = True while flag: count = self.pool.activeThreadCount() if count <= 0: # 打包完成,发完成信号,重置flag,终止线程 self.signal.emit() flag = False else: # 打包进行中,线程休眠 QThread.sleep(3) def add_runnable(self, runnable): self.pool.start(runnable) def clear(self): self.pool.clear()
class Tasks(QObject): def __init__(self): super(Tasks, self).__init__() self.pool = QThreadPool() self.pool.setMaxThreadCount(1) self.tasks = [] self.data = [] self.signals = TaskSignals() def add_task(self, task): self.tasks.append(task) def remove_task(self, task): self.tasks.remove(task) def clear(self): self.tasks.clear() def get_data(self): self.pool.waitForDone() return self.data def clear_data(self): self.data = [] def start(self): for task in self.tasks: self.pool.start(task) self.pool.waitForDone() for task in self.tasks: self.data.append(task.exp) self.remove_task(task) self.signals.finished.emit(self.data)
class MainWidget(QWidget, Ui_MainWidget): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.setupUi(self) self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(1) images = Images([]) self.images_vm: ImagesViewModel = ImagesViewModel([], images) self.images_vm.subscribe(self.on_images_updated) self.calculation_service: Optional[CalculationService] = None # noinspection PyUnusedLocal def on_start_required(self, play_enabled: bool) -> None: if not self.images_vm.count: self.reset_play_button() return service = self.calculation_service if service is not None and not service.completed(): if not service.stopped(): self.on_pause_required() return service = CalculationService(self.images_vm) service.signals.started.connect(self.on_calculation_started) service.signals.completed.connect(self.on_calculation_completed) service.signals.progress.connect(self.on_calculation_progress) service.signals.failed.connect(self.on_calculation_failed) service.signals.cancelled.connect(self.on_calculation_cancelled) self.calculation_service: CalculationService = service self.threadpool.start(self.calculation_service) def on_calculation_started(self) -> None: self.lock_interface() def on_calculation_completed(self) -> None: self.unlock_interface() self.reset_play_button() show_success_dialog(self, "Расчёт успешно завершён!") def on_calculation_progress(self, current: int, count: int) -> None: self.progressBar.setMaximum(count) self.progressBar.setValue(current) def on_calculation_failed(self, message: str) -> None: show_error_dialog(self, f"Не удалось закончить расчёт: {message}") self.unlock_interface() self.reset_play_button() def on_calculation_cancelled(self) -> None: self.unlock_interface() self.reset_play_button() def on_pause_required(self) -> None: service = self.calculation_service if service is None: return if service.paused(): self.calculation_service.resume() else: self.calculation_service.pause() def on_stop_required(self) -> None: if self.calculation_service is None: return self.calculation_service.stop() self.reset_play_button() def reset_play_button(self) -> None: self.startButton.blockSignals(True) self.startButton.setChecked(False) self.startButton.blockSignals(False) def on_save_required(self) -> None: filepath = save_file_dialog(self, "Сохранить таблицу", "*.csv") if not filepath.name: return self._save_table(filepath) def _save_table(self, filepath: Path) -> None: header, rows = self.tableWidget.dump() try: with open(filepath.as_posix(), mode="w", encoding="utf-8", newline="") as file: writer = csv.DictWriter(file, fieldnames=header, delimiter=";") writer.writeheader() writer.writerows(rows) show_success_dialog( self, text=f"Таблица {filepath.name} успешно сохранена!") except Exception as ex: show_error_dialog(self, text=f"Ошибка сохранения таблицы: {ex}") def dragEnterEvent(self, event: QDragEnterEvent) -> None: mime_data: QMimeData = event.mimeData() if mime_data.hasUrls(): event.accept() def dropEvent(self, event: QDropEvent) -> None: mime_data: QMimeData = event.mimeData() urls: List[QUrl] = mime_data.urls() if not len(urls): event.ignore() return paths = [] for each in urls: filepath: Path = Path(each.toLocalFile()) if not image_filepath_valid(filepath): continue paths.append(filepath) self._load_paths(paths) def _load_paths(self, paths: List[Path]) -> None: for each in paths: image = Image(each) self.images_vm.add(image) def on_images_updated(self, vm: ImagesViewModel, event: Event) -> None: if event is Event.ImageAdded: self.tableWidget.add_image(vm.last_image_vm) self.countLabel.setText(f"{vm.count} шт.") elif event is Event.ImageRemoved: self.countLabel.setText(f"{vm.count} шт.") def set_interface_enabled(self, enabled: bool = True) -> None: self.saveButton.setEnabled(enabled) self.tableWidget.setEnabled(enabled) def lock_interface(self) -> None: self.set_interface_enabled(False) def unlock_interface(self) -> None: self.set_interface_enabled(True) def on_images_removing_started(self) -> None: ... def on_images_removed(self, images_vm: List[ImageViewModel]) -> None: for image_vm in images_vm: self.images_vm.remove_vm(image_vm) def on_open_images_required(self) -> None: filepaths = open_files_dialog( self, caption="Загрузка изображений", filters="Изображение (*.png *.jpg, *.jpeg)", ) self._load_paths(filepaths)
class Gaitmenu(QtWidgets.QMainWindow): def __init__(self): super(self.__class__, self).__init__() # load user interface made with designer uifile = resource_filename('gaitutils', 'gui/gaitmenu.ui') uic.loadUi(uifile, self) # disable editing of log widget text self.txtOutput.setReadOnly(True) if (not cfg.general.allow_multiple_menu_instances and ulstools.env.already_running('gaitmenu')): qt_message_dialog('Another instance of the menu seems to be ' 'running. Please use that instance or ' 'stop it before starting a new one.') sys.exit() if cfg.general.git_autoupdate: if envutils._git_autoupdate(): qt_message_dialog( 'The package was automatically updated. Restarting...') os.execv(sys.executable, ['python'] + sys.argv) self._web_report_dialog = WebReportDialog(self) # connect ui widgets self.btnAddSession.clicked.connect(self._add_session_dialog) self.btnAddTrials.clicked.connect(self._add_trials_dialog) self.btnAddNexusTrial.clicked.connect(self._add_nexus_trial) self.btnSelectAll.clicked.connect(self._select_all_trials) self.btnClearSelected.clicked.connect(self._remove_selected_trials) self.btnPlotTrials.clicked.connect(self._plot_selected_trials) self.btnAveragePlot.clicked.connect(self._average_trials) self.actionCreate_PDF_report.triggered.connect( lambda ev: self._create_pdf_report()) self.actionWeb_reports.triggered.connect(self._web_report_dialog.show) self.actionQuit.triggered.connect(self.close) self.actionOpts.triggered.connect(self._options_dialog) self.actionTardieu_analysis.triggered.connect(self._tardieu) self.actionAutoprocess_session.triggered.connect( self._autoproc_session) self.actionAutoprocess_single_trial.triggered.connect( self._autoproc_trial) self.actionPDF_report_from_Nexus_session.triggered.connect( self._create_pdf_report_nexus) self.actionWeb_report_from_Nexus_session.triggered.connect( self._create_web_report_nexus) self.actionRun_postprocessing_pipelines.triggered.connect( self._postprocess_session) self.actionTrial_median_velocities.triggered.connect( self._plot_trial_median_velocities) self.actionTrial_timedep_velocities.triggered.connect( self._plot_trial_timedep_velocities) self.actionConvert_session_videos_to_web_format.triggered.connect( self._convert_session_videos) self.actionTime_distance_average.triggered.connect( self._plot_timedist_average) # XXX: these get run in the main thread self.actionCopy_session_videos_to_desktop.triggered.connect( copy_session_videos) self.actionAutomark_events.triggered.connect(self._automark_trial) # trials table settings # force "item selected" style, otherwise it will depend on focus; set font size table_sheet = "QTableView{ selection-background-color: rgba(0, 0, 255, 50%); font-size: 8pt; }" self.tableTrials.setStyleSheet(table_sheet) self.tableTrials.cellDoubleClicked.connect(self._cell_doubleclicked) # set up radio buttons self.rb_map_backend = { 'plotly': self.rbPlotly, 'matplotlib': self.rbMatplotlib } rb_backend_active = self.rb_map_backend[cfg.plot.backend] rb_backend_active.setChecked(True) def _colorstylevar_changed(self): logger.debug('callback') # set up color/style comboboxes self.cbColorStyleVar.currentIndexChanged.connect( self._colorstylevar_changed) # this callback only needs to fire when user activates the combobox self.cbColorStyleBy.activated.connect(self._colorstyleby_changed) # dynamically create combobox entries according to cfg for vartype, col_choice in cfg.plot.color_by.items(): self.cbColorStyleVar.addItem('Set %s trace color by:' % vartype, userData=('color', vartype)) for vartype, sty_choice in cfg.plot.style_by.items(): self.cbColorStyleVar.addItem('Set %s trace style by:' % vartype, userData=('style', vartype)) # add plot layouts to layout combobox cb_items = sorted( configdot.get_description(lo) or loname for loname, lo in cfg['layouts']) self.cbLayout.addItems(cb_items) # set default option to PiG lower body (if it's on the list) try: default_index = cb_items.index('PiG lower body kinematics') except ValueError: default_index = 0 self.cbLayout.setCurrentIndex(default_index) # map descriptions to layout names self.layouts_map = {(configdot.get_description(lo) or loname): loname for loname, lo in cfg['layouts']} XStream.stdout().messageWritten.connect(self._log_message) XStream.stderr().messageWritten.connect(self._log_message) self.threadpool = QThreadPool() # we need a thread for each web server plus one worker thread self.threadpool.setMaxThreadCount(cfg.web_report.max_reports + 1) # keep refs to tardieu+mpl windows so they don't get garbage collected self._tardieuwin = None self._mpl_windows = list() # progress bar self.prog = None def _colorstylevar_changed(self, _idx): """Callback for color/style variable selection combobox""" # get current style/color choice and show it in the other combobox mode, vartype = self.cbColorStyleVar.currentData() cfg_item = { 'color': cfg.plot.color_by, 'style': cfg.plot.style_by }[mode] choice = cfg_item[vartype] if choice is None: choice = 'none' idx = self.cbColorStyleBy.findText(choice) self.cbColorStyleBy.setCurrentIndex(idx) # don't allow styling for EMG variables enabled = not (vartype == 'emg' and mode == 'style') self.cbColorStyleBy.setEnabled(enabled) def _colorstyleby_changed(self, _idx): """Callback for color/style selection combobox""" mode, vartype = self.cbColorStyleVar.currentData() # set the appropriate cfg item according to choice cfg_item = { 'color': cfg.plot.color_by, 'style': cfg.plot.style_by }[mode] colorstyleby_choice = self.cbColorStyleBy.currentText() # Py2: convert unicode choices to str # (unicode prefix looks weird in Options interface) if sys.version_info.major == 2: colorstyleby_choice = str(colorstyleby_choice) if colorstyleby_choice == 'none': colorstyleby_choice = None cfg_item[vartype] = colorstyleby_choice def _get_plotting_backend_ui(self): """Get backend selection from UI""" for backend, rb in self.rb_map_backend.items(): if rb.isChecked(): return backend def _get_trial_sel(self): """Get trials selection from UI""" for trial_sel, rb in self.rb_map_trials.items(): if rb.isChecked(): return trial_sel def _automark_trial(self): session = _get_nexus_sessionpath() if session is None: return self._run_in_thread(automark_trial, finished_func=self._enable_main_ui) def _autoproc_session(self): """Wrapper to run autoprocess for Nexus session""" session = _get_nexus_sessionpath() if session is None: return c3ds = sessionutils.get_c3ds(session, trial_type='DYNAMIC') if c3ds: reply = qt_yesno_dialog('Some of the dynamic trials have been ' 'processed already. Are you sure you want ' 'to run autoprocessing?') if reply == QtWidgets.QMessageBox.NoRole: return self.prog = ProgressBar('Running autoprocessing...') signals = ProgressSignals() signals.progress.connect(lambda text, p: self.prog.update(text, p)) self.prog._canceled.connect(signals.cancel) self._run_in_thread(autoproc_session, finished_func=self._enable_main_ui, signals=signals) def _autoproc_trial(self): """Wrapper to run autoprocess for Nexus trial""" self.prog = ProgressBar('Running autoprocessing...') signals = ProgressSignals() signals.progress.connect(lambda text, p: self.prog.update(text, p)) self.prog._canceled.connect(signals.cancel) self._run_in_thread(autoproc_trial, finished_func=self._enable_main_ui, signals=signals) def _plot_timedist_average(self): """Plot time-distance average""" session = _get_nexus_sessionpath() if session is None: return backend = self._get_plotting_backend_ui() self._run_in_thread( do_session_average_plot, finished_func=self._enable_main_ui, result_func=self._show_plots, session=session, backend=backend, ) def _plot_trial_median_velocities(self): """Trial velocity plot from current Nexus session""" session = _get_nexus_sessionpath() if session is None: return backend = self._get_plotting_backend_ui() self._run_in_thread( plot_trial_velocities, finished_func=self._enable_main_ui, result_func=self._show_plots, session=session, backend=backend, ) def _plot_trial_timedep_velocities(self): """Trial velocity plot from current Nexus session""" session = _get_nexus_sessionpath() if session is None: return backend = self._get_plotting_backend_ui() self._run_in_thread( plot_trial_timedep_velocities, finished_func=self._enable_main_ui, result_func=self._show_plots, session=session, backend=backend, ) def _add_trial_to_table(self, tr): """Adds a trial to the trials table""" nrows = self.tableTrials.rowCount() self.tableTrials.insertRow(nrows) texts = ( tr.trialname, tr.eclipse_data['DESCRIPTION'], tr.eclipse_data['NOTES'], tr.sessionpath, ) for k, txt in enumerate(texts): item_ = QtWidgets.QTableWidgetItem(txt) if k == 0: # actual Trial instances are stored as userdata of # QTableWidgetItems on each rows 1st column; thus # we don't have to keep separate references to them item_.setData(QtCore.Qt.UserRole, tr) self.tableTrials.setItem(nrows, k, item_) def _add_c3dfiles(self, c3dfiles): """Add given c3d files to trials list""" c3dfiles = (op.normpath(fn) for fn in c3dfiles) self._disable_main_ui() # in case it takes a while for c3dfile in c3dfiles: try: tr = trial.Trial(c3dfile) except GaitDataError as e: title = 'Could not load trial %s. Details:' % op.split( c3dfile)[-1] _report_exception(e, title=title) else: self._add_trial_to_table(tr) self.tableTrials.resizeColumnsToContents() finally: self._enable_main_ui() def _add_nexus_trial(self): """Add directly from Nexus""" try: tr = trial.nexus_trial(from_c3d=cfg.trial.load_from_c3d) except GaitDataError as e: _report_exception(e) else: self._add_trial_to_table(tr) self.tableTrials.resizeColumnsToContents() def _add_session_dialog(self): """Show the add session dialog and add trials to list""" dlg = AddSessionDialog(self) if dlg.exec_(): self._add_c3dfiles(dlg.c3ds) def _add_trials_dialog(self): """Add individual trials to list""" fout = QtWidgets.QFileDialog.getOpenFileNames(self, 'Load C3D files', None, 'C3D files (*.c3d)') if fout[0]: self._add_c3dfiles(fout[0]) def _select_all_trials(self): """Select all trials""" self.tableTrials.selectAll() @property def _selected_rows(self): """Return indices of selected rows""" return list( set(idx.row() for idx in self.tableTrials.selectedIndexes())) def _cell_doubleclicked(self, row, col): """Plot trial on double click""" tr = self.tableTrials.item(row, 0).data(QtCore.Qt.UserRole) self._plot_trials([tr]) @property def _selected_trials(self): """Return list of trials that are currently selected""" sel_items = (self.tableTrials.item(row, 0) for row in self._selected_rows) return list(item.data(QtCore.Qt.UserRole) for item in sel_items) def _remove_selected_trials(self): """Remove selected trials from list""" while self._selected_rows: # this relies on _selected_rows dynamically changing after removals row = self._selected_rows[0] self.tableTrials.removeRow(row) def _plot_selected_trials(self): if not self._selected_rows: return if any(tr.is_static for tr in self._selected_trials): qt_message_dialog( 'One or more trials are static, plotting all trials as unnormalized' ) self._plot_trials(self._selected_trials, normalized=False) else: self._plot_trials(self._selected_trials) def _plot_trials(self, trials, normalized=True): """Plot specified trials, or selected trials from menu""" have_avgtrials = any(isinstance(tr, stats.AvgTrial) for tr in trials) if not normalized or self.xbPlotUnnorm.checkState(): if have_avgtrials: qt_message_dialog('Cannot plot average trials as unnormalized') return cycles = 'unnormalized' else: cycles = None model_normaldata = read_default_normaldata() layout_desc = self.cbLayout.currentText() layout_name = self.layouts_map[layout_desc] backend = self._get_plotting_backend_ui() if self.xbEMGRMS.checkState(): emg_mode = 'rms' elif have_avgtrials and 'EMG' in layout_name.upper(): qt_message_dialog('Averaged EMG can only be plotted in RMS mode') return else: emg_mode = None backend = self._get_plotting_backend_ui() # FIXME: hardcoded legend type self._run_in_thread( plot_trials, finished_func=self._enable_main_ui, result_func=self._show_plots, trials=trials, layout_name=layout_name, backend=backend, cycles=cycles, emg_mode=emg_mode, legend_type='short_name_with_tag_and_cycle', model_normaldata=model_normaldata, auto_adjust_emg_layout=False, ) def _average_trials(self): """Average trials from list, add resulting averaged trial to list""" if len(self._selected_rows) < 2: qt_message_dialog('Need at least 2 trials for averaging') return if any(isinstance(tr, stats.AvgTrial) for tr in self._selected_trials): qt_message_dialog('Cannot include averaged trials in average') return reject_outliers = cfg.trial.outlier_rejection_threshold self._run_in_thread( stats.AvgTrial.from_trials, finished_func=self._enable_main_ui, result_func=self._add_trial_to_table, trials=self._selected_trials, reject_outliers=reject_outliers, ) def _create_web_report_nexus(self): """Create web report based on current Nexus session""" session = _get_nexus_sessionpath() if session: self._web_report_dialog._create_web_report(sessions=[session]) def _show_plots(self, fig, backend=None): """Shows fig""" if backend is None: backend = self._get_plotting_backend_ui() if backend == 'matplotlib': _mpl_win = qt_matplotlib_window(fig) self._mpl_windows.append(_mpl_win) elif backend == 'plotly': _show_plotly_fig(fig) def _convert_vidfiles(self, vidfiles, signals): """Convert given list of video files""" # get the converter processes procs = convert_videos(vidfiles=vidfiles) if not procs: logger.warning('video converter processes could not be started') return completed = False # wait in sleep loop until all converter processes have finished while not completed: if signals.canceled: logger.debug('canceled, killing video converter processes') for p in procs: p.kill() break n_complete = len([p for p in procs if p.poll() is not None]) prog_txt = 'Converting videos: %d of %d files done' % ( n_complete, len(procs), ) prog_p = 100 * n_complete / float(len(procs)) signals.progress.emit(prog_txt, prog_p) time.sleep(0.1) completed = n_complete == len(procs) def _convert_session_videos(self): """Convert Nexus session videos to web format""" session = _get_nexus_sessionpath() if session is None: return try: vidfiles = _collect_session_videos(session, tags=cfg.eclipse.tags) except GaitDataError as e: qt_message_dialog(_exception_msg(e)) return if not vidfiles: qt_message_dialog('Cannot find any video files for session %s' % session) return if convert_videos(vidfiles, check_only=True): reply = qt_yesno_dialog( 'It looks like the session videos have already been converted. Redo?' ) if reply == QtWidgets.QMessageBox.NoRole: return self._disable_main_ui() self.prog = ProgressBar('Converting session videos...') signals = ProgressSignals() signals.progress.connect(lambda text, p: self.prog.update(text, p)) self.prog._canceled.connect(signals.cancel) self._convert_vidfiles(vidfiles, signals) self._enable_main_ui() def _postprocess_session(self): """Run additional postprocessing pipelines for tagged trials""" def _run_postprocessing(): """Helper function that will be run in a separate thread""" nexus.close_trial() for k, tr in enumerate(trials, 1): trbase = op.splitext(tr)[0] vicon.OpenTrial(trbase, cfg.autoproc.nexus_timeout) nexus.run_pipelines_multiprocessing( cfg.autoproc.postproc_pipelines) prog_txt = 'Running postprocessing pipelines: %s for %d trials' % ( cfg.autoproc.postproc_pipelines, len(trials), ) prog_p = 100 * k / float(len(trials)) signals.progress.emit(prog_txt, prog_p) if signals.canceled: logger.debug('postprocessing pipelines were canceled') return session = _get_nexus_sessionpath() if session is None: return # XXX: run for tagged + static - maybe this should be configurable trials = sessionutils.get_c3ds(session, tags=cfg.eclipse.tags, trial_type='dynamic') trials += sessionutils.get_c3ds(session, trial_type='static') if trials and cfg.autoproc.postproc_pipelines: logger.debug('running postprocessing for %s' % trials) vicon = nexus.viconnexus() self.prog = ProgressBar('Running postprocessing pipelines...') self.prog.update( 'Running postprocessing pipelines: %s for %d ' 'trials' % (cfg.autoproc.postproc_pipelines, len(trials)), 0, ) signals = ProgressSignals() signals.progress.connect(lambda text, p: self.prog.update(text, p)) self.prog._canceled.connect(signals.cancel) self._run_in_thread(_run_postprocessing, block_ui=True, finished_func=self._enable_main_ui) elif not trials: qt_message_dialog('No trials in session to run postprocessing for') elif not cfg.autoproc.postproc_pipelines: qt_message_dialog('No postprocessing pipelines defined') def closeEvent(self, event): """ Confirm and close application. """ if self._web_report_dialog.active_reports: reply = qt_yesno_dialog('There are active web reports which ' 'will be closed. Are you sure you ' 'want to quit?') if reply == QtWidgets.QMessageBox.YesRole: self._web_report_dialog.shutdown() self._close_mpl_windows() event.accept() else: event.ignore() else: self._close_mpl_windows() event.accept() def _close_mpl_windows(self): for win in self._mpl_windows: win.close() def _options_dialog(self): """Show the options dialog""" dlg = OptionsDialog(self) dlg.exec_() def _create_pdf_report_nexus(self): session = _get_nexus_sessionpath() if session is None: return self._create_pdf_report([session]) def _create_pdf_report(self, sessions=None): """Create comparison or single session pdf report""" if sessions is None: dlg = ChooseSessionsDialog() if not dlg.exec_(): return sessions = dlg.sessions comparison = len(sessions) > 1 if comparison: qt_message_dialog('PDF comparison report not supported right now') return session = sessions[0] info = sessionutils.load_info(session) if info is None: info = sessionutils.default_info() prompt_ = 'Please give additional subject information:' dlg_info = PdfReportDialog(info, prompt=prompt_) if not dlg_info.exec_(): return new_info = dict( hetu=dlg_info.hetu, fullname=dlg_info.fullname, session_description=dlg_info.session_description, ) info.update(new_info) sessionutils.save_info(session, info) # create the report if comparison: self._run_in_thread( pdf.create_comparison_report, finished_func=self._enable_main_ui, sessions=sessions, ) else: self._run_in_thread( pdf.create_report, finished_func=self._enable_main_ui, sessionpath=sessions[0], info=info, pages=dlg_info.pages, ) def _log_message(self, msg): """Logs a message to the log widget""" c = self.txtOutput.textCursor() c.movePosition(QtGui.QTextCursor.End) self.txtOutput.setTextCursor(c) self.txtOutput.insertPlainText(msg) self.txtOutput.ensureCursorVisible() def _disable_main_ui(self): """ Disable all operation buttons """ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) self.setEnabled(False) # disables whole main window # update display immediately in case thread gets blocked QtWidgets.QApplication.processEvents() def _enable_main_ui(self): """Enable all operation buttons, close progress bar if any and restore cursor.""" if self.prog is not None: self.prog.reset() QtWidgets.QApplication.restoreOverrideCursor() self.setEnabled(True) def _tardieu(self): """Open the Tardieu window if it is not currently open""" if self._tardieuwin is None or not self._tardieuwin.isVisible(): self._tardieuwin = _tardieu.TardieuWindow() self._tardieuwin.show() def _run_in_thread(self, fun, block_ui=True, finished_func=None, result_func=None, **kwargs): """Run function fun with args kwargs in a worker thread. If block_ui==True, disable main ui until worker thread is finished. finished_func will be called when thread is finished. result_func will be called with the function return value as its single argument, unless an exception is raised during thread execution.""" fun_ = partial(fun, **kwargs) if block_ui: self._disable_main_ui() self.runner = Runner(fun_) if finished_func: self.runner.signals.finished.connect(finished_func) if result_func: self.runner.signals.result.connect(lambda r: result_func(r)) self.runner.signals.error.connect(lambda e: _report_exception(e)) self.threadpool.start(self.runner)
class Ui(QtWidgets.QMainWindow, ihesync_app.Ui_MainWindow): def __init__(self, context, parent=None): super(Ui, self).__init__(parent) self.logger = logging.getLogger() self.network_available = False self.network_watchdog = None self.setupUi(self) self.context = context self.changed = False self.label_ihewebsite.setText('Visit IHE Website : <a href="http://www.ihe.net">www.ihe.net</a>') self.label_ihewebsite.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) self.label_ihewebsite.setOpenExternalLinks(True) self.modifed_label = QLabel("Status: No change") self.network_label = QLabel("No network!") self.statusBar().setStyleSheet('border: 0; background-color: #FFF8DC;') self.statusBar().setStyleSheet("QStatusBar::item {border: none;}") self.statusBar().addPermanentWidget(VLine()) self.statusBar().addPermanentWidget(self.network_label) self.statusBar().addPermanentWidget(VLine()) self.statusBar().addPermanentWidget(self.modifed_label) self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(4) self.doc_model = documents_model.DocumentsModel([]) self.tableView.setModel(self.doc_model) self.tableView.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents ) icon_delegate = OpenFolderDelegate(self.tableView) self.tableView.setItemDelegateForColumn(4, icon_delegate) self.tableView.clicked.connect(self.open_documents_folder) self.logger.info("Starts with %d threads" % self.threadpool.maxThreadCount()) self.tabWidget.currentChanged.connect(self.current_tab_changed) if platform.system() in STYLES: self.setStyleSheet(STYLES[platform.system()]) def main(self): conf_loaded = self.context.load_configuration() # default size (w, h) = (800, 700) if platform.system() in MAIN_WINDOW_DEFAULT_SIZE: (w, h) = MAIN_WINDOW_DEFAULT_SIZE[platform.system()] # overwritten size = user prefs if self.context.sync.geometry != (0, 0): (w, h) = self.context.sync.geometry self.centralwidget.parentWidget().resize(w, h) self.show() self.start_network_watchdog() if not conf_loaded: self.change_status("Configuration loading error : check network") self.refresh_configuration() self.refresh_information_counts() self.refresh_domain_list() self.refresh_public_comment() if self.context.local_file_count_ondisk != self.context.local_file_count: self.msgbox_out_of_sync() # -- Messages boxes --- # --------------------- def msgbox_out_of_sync(self): msg = QMessageBox.about( self, "Information...", ( f"Out of sync ! Local files on disk {self.context.local_file_count_ondisk}" f" vs in configuration {self.context.local_file_count}\n" f" Sync needed" ), ) msg.setIcon(QMessageBox.Warning) def msgbox_network_unavailable(self): QMessageBox.critical(self, "Network unavailable", ( f"No network" f" Check\n"), ) # -- Messages boxes --- # -- Check Network def update_network_status(self, data): (ip, self.network_available) = data self.logger.debug(f"network status updated availaible = {self.network_available}") if self.network_available: self.network_label.setText("Connected") self.network_label.setStyleSheet('border: 0; color: green;') else: self.network_label.setText("no Network!") self.network_label.setStyleSheet('border: 0; color: red;') def start_network_watchdog(self): self.network_watchdog = sync_worker.NetworkWorker(ihesync.sync.IHE_URL, self.context.sync.proxy, self.context.sync.ping_delay) self.network_watchdog.signals.progress.connect(self.update_network_status) self.threadpool.start(self.network_watchdog) def stop_network_watchdog(self): self.logger.debug("Stop network watchdog...") self.network_watchdog.abort() # -- Refresh counts def refresh_public_comment(self): state = ( QtCore.Qt.Checked if self.context.sync.public_comment else QtCore.Qt.Unchecked ) self.checkComments.setCheckState(state) def refresh_last_checked(self): """ Refresh date of the last check :return: - """ if self.context.sync.last_check: self.labelLastCheckDate.setText( self.context.sync.last_check.strftime("%Y-%m-%d %H:%M") ) def refresh_domain_list(self) -> None: """ Refresh the domain table - sorted by domain name - get the count of local files :return: """ self.logger.info("refresh_domain_list") self.doc_model.log() self.labelDomainCountValue.setText(str(len(self.context.domains))) self.doc_model.set_documents(None) data = [] for domain in sorted(self.context.domains, key=lambda v: v["name"]): local_count = self.context.sync.count_local_files(domain["name"]) self.logger.debug(f"> domain >> {domain} - local cout = {local_count}") data.append( { "checked": domain["name"] in self.context.selected_domains or domain["checked"], "domain": domain["name"], "title": DOMAIN_DICT[domain["name"]] if domain["name"] in DOMAIN_DICT else "", "down": local_count, "total": domain["files"], "link": local_count > 0, "local": local_count, "error": 0 } ) self.doc_model.set_documents(data) self.tableView.model().layoutChanged.emit() def refresh_documents_directory_geometry(self): # get configuration directory geometry for height (conf_x, conf_y, conf_w, conf_h) = self.textConfDir.geometry().getRect() (x, y, w, h) = self.textDocDir.geometry().getRect() if len(self.context.doc_directory) > 60: h = conf_h * 2 else: h = conf_h self.textDocDir.setGeometry(x, y, w, h) def refresh_configuration(self) -> None: """ Refresh the configuration tab :return: - """ self.textConfDir.setText(str(self.context.conf_directory)) self.textDocDir.setText(str(self.context.doc_directory)) self.refresh_documents_directory_geometry() self.newDocsGroupBox.setVisible(False) self.textLoggingFilename.setText(str(self.context.sync.log_filename)) rad = dict(INFO=self.infoRadioButton, ERROR=self.errorRadioButton, DEBUG=self.debugRadioButton) if self.context.sync.log_level in rad: rad[self.context.sync.log_level].setChecked(True) self.textPingDelay.setText(str(self.context.sync.ping_delay)) if self.context.sync.proxy: self.textProxyAddress.setText(self.context.sync.proxy['address']) self.textProxyPort.setText(self.context.sync.proxy['port']) if self.context.sync.proxy["active"]: self.specificProxyRadioButton.setChecked(self.context.sync.proxy["active"]) self.noProxyRadioButton.setChecked(not self.context.sync.proxy["active"]) self.update_proxy_state() else: self.noProxyRadioButton.setChecked(True) def refresh_information_counts(self) -> None: """ Refresh counters and date last checked :return: """ self.logger.debug("refresh_information_counts") self.newDocsGroupBox.setVisible(False) self.refresh_last_checked() self.context.scan_local_dirs() self.context.refresh_counts_current() self.labelDocumentCountValue.setText(str(self.context.file_count)) self.labelLocalFilesCountValue.setText(str("{}/{}" .format(self.context.local_file_count_ondisk, self.context.local_file_count))) diff = self.context.check_updates_available() if diff > 0: self.newDocLabel.setText(f"{diff} document changes") self.newDocsGroupBox.setVisible(True) def change_status(self, msg=None, changed=None, duration=3000): if changed: self.changed = changed self.modifed_label.setText("Status: Changed !" if self.changed else "Status: No change") if msg: self.statusbar.showMessage(msg, duration) # -- UI callback @pyqtSlot() def on_checkComments_clicked(self): self.context.sync.public_comment = self.checkComments.checkState() == QtCore.Qt.Checked self.change_status(changed=True) @pyqtSlot() def on_aboutPushButton_clicked(self): dlg = dialogs.AboutDialog(self) dlg.main() @pyqtSlot() def on_infoRadioButton_clicked(self): self.context.sync.update_logger_config(level="INFO") if self.context.sync.log_level != "INFO": self.change_status(changed=True) @pyqtSlot() def on_errorRadioButton_clicked(self): self.context.sync.update_logger_config(level="ERROR") if self.context.sync.log_level != "ERROR": self.change_status(changed=True) @pyqtSlot() def on_debugRadioButton_clicked(self): self.context.sync.update_logger_config(level="DEBUG") if self.context.sync.log_level != "DEBUG": self.change_status(changed=True) @pyqtSlot() def on_noProxyRadioButton_clicked(self): self.change_status(changed=True) self.context.sync.proxy["active"] = False self.textProxyAddress.setDisabled(True) self.textProxyPort.setDisabled(True) @pyqtSlot() def on_specificProxyRadioButton_clicked(self): self.change_status(changed=True) self.context.sync.proxy["active"] = True self.textProxyAddress.setDisabled(False) self.textProxyPort.setDisabled(False) @pyqtSlot() def on_changeProxyPushButton_clicked(self): self.change_status(changed=True) self.context.sync.proxy['address'] = str(self.textProxyAddress.toPlainText()) self.context.sync.proxy['port'] = str(self.textProxyPort.toPlainText()) self.update_proxy_state() @pyqtSlot() def on_changeLogPushButton_clicked(self): self.context.sync.update_logger_config(filename=self.textLoggingFilename.toPlainText()) self.change_status(changed=True) @pyqtSlot() def on_deleteLogPushButton_clicked(self): try: os.remove(self.textLoggingFilename.toPlainText()) except OSError as e: self.logger.error(f"Can't remove {self.textLoggingFilename.toPlainText()} : {str(e)}") @pyqtSlot() def on_openLogPushButton_clicked(self): if os.path.exists(self.textLoggingFilename.toPlainText()): webbrowser.open_new(f"file://{self.textLoggingFilename.toPlainText()}") else: self.logger.error(f"Can't open file {self.textLoggingFilename.toPlainText()} which does not exist!") @pyqtSlot() def on_textConfDir_textChanged(self): self.context.conf_directory = self.textConfDir.toPlainText() self.change_status(changed=True) @pyqtSlot() def on_textDocDir_textChanged(self): self.context.doc_directory = self.textDocDir.toPlainText() self.change_status(changed=True) @pyqtSlot() def on_confSelectButton_clicked(self): previous = self.textConfDir.toPlainText() confdir = QFileDialog.getExistingDirectory(self, "Select Directory", previous, QFileDialog.ShowDirsOnly) if len(confdir): self.context.conf_directory = str(confdir) self.textConfDir.setText(self.context.conf_directory) self.change_status(changed=True) @pyqtSlot() def on_docSelectButton_clicked(self): previous = self.textDocDir.toPlainText() docdir = QFileDialog.getExistingDirectory(self, "Select Directory", previous, QFileDialog.ShowDirsOnly) if len(docdir): self.context.doc_directory = str(docdir) self.textDocDir.setText(self.context.doc_directory) self.change_status(changed=True) self.refresh_documents_directory_geometry() @pyqtSlot() def on_syncButton_clicked(self): if self.network_available: self.prepare_synchronization() else: self.msgbox_network_unavailable() @pyqtSlot() def on_changeConnectionPushButton_clicked(self): # get values before change new_delay = self.context.sync.ping_delay try: new_delay = int(self.textPingDelay.toPlainText()) except ValueError as d_err: self.change_status("Delay value must be numeric !!!") self.logger.error(f"Configuration error while setting non numeric value for delay {d_err}") self.textPingDelay.setText(str(new_delay)) if new_delay != self.context.sync.ping_delay: self.context.sync.ping_delay = new_delay self.stop_network_watchdog() self.start_network_watchdog() self.change_status("Ping informations changed.") @pyqtSlot() def on_synchronize_confirmed(self): self.logger.debug("on_synchronize_confirmed") self.do_synchronization() @pyqtSlot() def on_synchronize_rejected(self): self.context.revert_sync() def current_tab_changed(self, tab_number): if tab_number == 0: self.refresh_information_counts() self.refresh_domain_list() def closeEvent(self, event): # save new data self.logger.debug(f"Close - change ? {self.changed}") if self.changed or self.context.no_config_file: self.context.sync.save_infos() self.context.sync.save_configuration() else: self.logger.info("No changes") event.accept() self.stop_network_watchdog() def resizeEvent(self, event): h = self.centralwidget.parentWidget().size().height() w = self.centralwidget.parentWidget().size().width() self.context.sync.geometry = (w, h) # -- > Actions -- # -------------- def prepare_synchronization(self): """ get the selected domains prepare elements to sync launch the UI dialog showing the compute of sync :return: """ # get selected domains self.logger.debug("prepare_synchronization") domains = self.doc_model.checked() self.logger.info(domains) self.context.prepare_sync(domains) pd = dialogs.ProgressSyncDialog( dialogs.ProgressSyncDialog.REMOTE_INFO_TEXT, parent=self ) worker = sync_worker.PrepareWorker(self.context) worker.signals.finished.connect(pd.accept) worker.signals.finished.connect(self.synchronize_dialog) worker.signals.aborted.connect(pd.reject) pd.main(worker) self.threadpool.start(worker) def synchronize_dialog(self): """ Launch UI for synchro :return: """ if self.network_available: sd = dialogs.SyncDialog(parent=self) sd.confirm_signal.connect(self.on_synchronize_confirmed) sd.reject_signal.connect(self.on_synchronize_rejected) sd.old_domains = self.context.infos["old_domain"] sd.new_domains = self.context.infos["new_domain"] sd.old_docs = self.context.infos["to_del"] sd.new_docs = self.context.infos["to_download"] if len(sd.old_domains) > 0 or len(sd.new_docs) > 0: self.change_status(changed=True) sd.main() else: self.msgbox_network_unavailable() def do_synchronization(self): self.doc_model.log() self.context.sync.display_available_docs() sd = dialogs.ProgressSyncDialog( dialogs.ProgressSyncDialog.SYNC_INFO_TEXT, parent=self ) worker = sync_worker.SyncWorker(self.context) worker.signals.finished.connect(sd.accept) worker.signals.finished.connect(self.sync_finished) worker.signals.progress.connect(self.doc_model.update_documents) worker.signals.aborted.connect(sd.reject) sd.main(worker) self.threadpool.start(worker) self.context.confirm_sync() def sync_finished(self): """ Syncho done. - display information in status bar - refresh informations counts - refresh doc table information """ downloaded, error = self.doc_model.summary() self.change_status(f"{downloaded} download(s), {error} error(s)") self.refresh_information_counts() self.tableView.model().layoutChanged.emit() # -- < Actions def open_documents_folder(self, index: QtCore.QModelIndex) -> None: docinfo = index.model().docs[index.row()] if docinfo['link'] and index.column() == 4: dom = self.context.local_path_domain(docinfo['domain']) webbrowser.open_new(f"file://{dom}") def update_proxy_state(self): self.stop_network_watchdog() self.network_watchdog.set_proxy(self.context.sync.proxy) self.start_network_watchdog()
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): QtWidgets.QMainWindow.__init__(self) Ui_MainWindow.__init__(self) self.setupUi(self) self.model = VentModel() # settings file self.bconfirm.released.connect(self.confirm) self.bstart.released.connect(self.start) self.model.load() insptime, exptime, pressure = self.model.settings self.x1, self.x2, self.x3 = plot_points * [0], plot_points * [ 0 ], plot_points * [0] self.y1, self.y2, self.y3 = plot_points * [0.0], plot_points * [ 0.0 ], plot_points * [0.0] self.holding_registers = 3 * [0] self.coil = False self.tnewinsp.setValue(insptime) self.tnewexp.setValue(exptime) self.newpress.setValue(pressure) self.tcurrinsp.setValue(insptime) self.tcurrexp.setValue(exptime) self.currpress.setValue(pressure) self.graph1 = pg.PlotWidget() self.graph2 = pg.PlotWidget() self.graph3 = pg.PlotWidget() self.vlgraphs.addWidget(self.graph1) self.vlgraphs.addWidget(self.graph2) self.vlgraphs.addWidget(self.graph3) pen = pg.mkPen(color=(255, 0, 0)) self.data_line1 = self.graph1.plot([], [], pen=pen) self.data_line2 = self.graph2.plot([], [], pen=pen) self.data_line3 = self.graph3.plot([], [], pen=pen) self.x1, self.y1 = self.graphInit(self.data_line1, plot_points) self.x1, self.y2 = self.graphInit(self.data_line2, plot_points) self.x1, self.y3 = self.graphInit(self.data_line3, plot_points) self.setStyle(self.graph1, 'Expiration vs time', 'Expiration', 'time (ms)') self.setStyle(self.graph2, 'Inspiration vs time', 'Inspiration', 'time (ms)') self.setStyle(self.graph3, 'Pressure vs time', 'Pressure', 'time (ms)') self.timer = QtCore.QTimer() self.timer.setInterval(time_interval) self.timer.timeout.connect(self.update_plot_data) self.modbuspool = QThreadPool() self.modbuswritepool = QThreadPool() self.modbustimer = QtCore.QTimer() self.modbustimer.setInterval(time_interval // 2) self.modbustimer.timeout.connect(self.modbus_timer) self.modbustimer.start() def start(self): if self.bstart.text() == 'Start': self.timer.start() self.coil = True self.bstart.setText('Stop') self.holding_registers = 3 * [0] self.x1, self.y1 = self.graphInit(self.data_line1, plot_points) self.x2, self.y2 = self.graphInit(self.data_line2, plot_points) self.x3, self.y3 = self.graphInit(self.data_line3, plot_points) elif self.bstart.text() == 'Stop': self.timer.stop() self.coil = False self.bstart.setText('Start') self.modbuswritepool.setMaxThreadCount(1) self.modbuswritetcp = ThreadHandler(self.modbus_write) self.modbuswritepool.start(self.modbuswritetcp) def confirm(self): insptime = self.tnewinsp.value() exptime = self.tnewexp.value() pressure = self.newpress.value() self.tcurrinsp.setValue(insptime) self.tcurrexp.setValue(exptime) self.currpress.setValue(pressure) self.model.settings = [insptime, exptime, pressure] self.model.save() def graphInit(self, dataline, pts): x = list(range(pts)) # 100 time points y = [0 for _ in range(pts)] # 100 data points dataline.setData(x, y) # Update the data. return x, y def update_plot_data(self): self.x1, self.y1 = self.update_points(self.data_line1, self.x1, self.y1) self.x2, self.y2 = self.update_points(self.data_line2, self.x2, self.y2) self.x3, self.y3 = self.update_points(self.data_line3, self.x3, self.y3) try: self.y1[-1] = self.holding_registers[0] / 1000.0 self.y2[-1] = self.holding_registers[1] / 1000.0 self.y3[-1] = self.holding_registers[2] / 1000.0 except TypeError: self.holding_registers = 3 * [0] self.y1[-1] = self.holding_registers[0] / 1000.0 self.y2[-1] = self.holding_registers[1] / 1000.0 self.y3[-1] = self.holding_registers[2] / 1000.0 print("typeerror") def update_points(self, dataline, x, y): x = x[1:] x.append(x[-1] + 1) y = y[1:] y.append(y[-1]) dataline.setData(x, y) return x, y def setStyle(self, graph, title, labelx, labely): graph.setBackground('#000000') graph.setTitle(title, color='#0000FF') graph.setLabel('left', labelx, color='#00FF00') graph.setLabel('bottom', labely, color='#00FF00') def modbus_timer(self): self.modbuspool.setMaxThreadCount(1) self.modbustcp = ThreadHandler(self.modbus_request) self.modbuspool.start(self.modbustcp) def modbus_write(self, progress_callback): c = ModbusClient(host="localhost", auto_open=False) connection_ok = c.open() if connection_ok: try: c.write_single_coil(0, self.coil) c.close() except: print("modbus exception") pass else: print("other") pass finally: #print("passed") pass else: print("could not open") pass def modbus_request(self, progress_callback): c = ModbusClient(host="localhost", auto_open=False) connection_ok = c.open() if connection_ok: try: self.holding_registers = c.read_holding_registers(0, 3) print(self.holding_registers) regs = [ int(10 * self.tcurrinsp.value()), int(10 * self.tcurrexp.value()), int(10 * self.currpress.value()) ] c.write_multiple_registers(3, regs) print(regs) c.close() except: print("modbus exception") pass else: print("other") pass finally: #print("passed") pass else: print("could not open") pass
class UiMainWindow(object): __result = None def __init__(self, main_window_local): grover_circuit_constructor = GroverCircuit() shor_circuit_constructor = ShorCircuit() tokens = list(self.get_config().values()) self.__qiskit = tokens[0] self.__dwave = tokens[1] self.__inspire = tokens[2] self.registration_dialog = RegistrationDialogWindow(init=tokens) self.ibm_backends = None self.dwave_backends = None self.inspire_backends = None self.window = main_window_local self.central_widget = QtWidgets.QWidget(main_window_local) self.central_widget.setObjectName("central_widget") self.repo = DataRepository() saved_results = self.repo.get_all() self.thread_pool = QThreadPool() self.thread_pool.setMaxThreadCount(8) self.inner_thread_pool = QThreadPool() self.inner_thread_pool.setMaxThreadCount(8) self.drawer = PlotDrawer() self.writer = FileWriter() self.tab_infos = [] self.default_tests = [] # quantumRegister = q.QuantumRegister(2) # classicalRegister = q.ClassicalRegister(2) # circuit = q.QuantumCircuit(quantumRegister, classicalRegister) # circuit.h(quantumRegister[0]) # circuit.cx(quantumRegister[0], quantumRegister[1]) # circuit.measure(quantumRegister, classicalRegister) # self.default_tests.append(QuantumTest("BellState", circuit, 1024)) shor_answer = 4 grover_2_answer = 3 grover_3_answer = 6 grover_4_answer = 2 grover_5_answer = 21 circuit3 = q.QuantumCircuit(5, 5) shor_circuit_constructor.add_circuit(circuit3) self.default_tests.append( QuantumTest('Shor_3_5', circuit3, 1024, shor_answer)) circuit4 = q.QuantumCircuit(2, 2) grover_circuit_constructor.set_number(grover_2_answer) grover_circuit_constructor.add_circuit(circuit4) self.default_tests.append( QuantumTest('Grover_2_bits', circuit4, 1024, grover_2_answer)) circuit_new = q.QuantumCircuit(3, 3) grover_circuit_constructor.set_number(grover_3_answer) grover_circuit_constructor.add_circuit(circuit_new) self.default_tests.append( QuantumTest('Grover_3_bits', circuit_new, 1024, grover_3_answer)) circuit5 = q.QuantumCircuit(4, 4) grover_circuit_constructor.set_number(grover_4_answer) grover_circuit_constructor.add_circuit(circuit5) self.default_tests.append( QuantumTest('Grover_4_bits', circuit5, 1024, grover_4_answer)) qubits = q.QuantumRegister(5) bits = q.ClassicalRegister(5) # controls = list() # controls.append(qubits[0]) # controls.append(qubits[1]) # controls.append(qubits[2]) circuit6 = q.QuantumCircuit(qubits, bits) grover_circuit_constructor.set_number(grover_5_answer) grover_circuit_constructor.add_circuit(circuit6) self.default_tests.append( QuantumTest('Grover_5_bits', circuit6, 1024, grover_5_answer)) self.test_info_tab = TestInfoTab(self.default_tests, self.drawer) self.results_info_tab = TestResultTab() self.setup_ui(main_window_local) self.setup_tabs() self.dialog = TestCreationDialogWindow(self.tab_infos, self.default_tests) self.dialog.setWindowTitle('Create test') for res in saved_results: self.add_result_from_db(res) def get_config(self): keys = ['qiskit', 'dwave', 'quantum-inspire'] tokens = dict.fromkeys(keys) try: with open('main\\configuration\\config.ini') as config_file: lines = config_file.readlines() for line in lines: api_name = line.strip().split('=')[0] if api_name in keys: tokens[api_name] = line.strip().split('=')[1] except OSError: print('Config file not found') except: print(sys.exc_info()[0]) print('Corrupted file') return tokens def open_qasm_circuit(self): files_filter = 'QASM files (*.qasm);;All files (*.*)' name = QtWidgets.QFileDialog.getOpenFileName(self.window, 'Open File', filter=files_filter) extension = os.path.splitext(name[0])[1] if extension == '.qasm': try: new_circuit = q.QuantumCircuit().from_qasm_file(name[0]) new_test = QuantumTest( os.path.basename(os.path.splitext(name[0])[0]), new_circuit, 1024) self.default_tests.append(new_test) self.test_info_tab.add_new_test(new_test) self.dialog.add_new_test(new_test) except QasmError: msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Critical) msg.setText('File %s is corrupted!' % os.path.splitext(name[0])[0]) msg.setWindowTitle('Error!') msg.exec_() def delete_results(self): indexes = self.results_info_tab.table_widget.selectionModel( ).selectedRows() for i in range(0, len(indexes)): self.results_info_tab.table_widget.removeRow(indexes[i].row()) self.results_info_tab.table_widget.resizeColumnsToContents() def open_results(self): # TODO files_filter = 'CSV files (*.csv);;All files (*.*)' name = QtWidgets.QFileDialog.getOpenFileName(self.window, 'Open File', filter=files_filter) extension = os.path.splitext(name[0])[1] if extension == '.csv': try: with open(name[0], newline='') as csvfile: csv_reader = csv.reader(csvfile, delimiter=';') headers = next(csv_reader, None) if headers != [ 'Test_name', 'Result', 'Backend_name', 'Job_status', 'Date', 'Shots_taken', 'Time_taken', 'Accuracy', 'Counts' ]: raise RuntimeError('Incorrect file format!') for row in csv_reader: if len(row) == 0: continue self.results_info_tab.table_widget.insertRow( self.results_info_tab.table_widget.rowCount()) dict_data = dict.fromkeys(headers) test = next( (x for x in self.default_tests if x.name == row[0]), QuantumTest(row[0], None, row[5], row[1])) if test not in self.default_tests: self.default_tests.append(test) dict_data[headers[0]] = row[0] self.results_info_tab.table_widget.setItem( self.results_info_tab.table_widget.rowCount() - 1, 0, MyTableWidgetItem(str(row[0]))) for j in range(2, 8): dict_data[headers[j]] = row[j] self.results_info_tab.table_widget.setItem( self.results_info_tab.table_widget.rowCount() - 1, j - 1, MyTableWidgetItem(str(row[j]))) dict_data[headers[8]] = row[8] test.results.append(dict_data) self.results_info_tab.table_widget.resizeColumnsToContents( ) except: msg = QtWidgets.QMessageBox() msg.setIcon(QtWidgets.QMessageBox.Critical) msg.setText("Oops! %s occurred." % sys.exc_info()[0]) msg.setWindowTitle('Error!') msg.exec_() def save_results(self): files_filter = 'CSV files (*.csv);;All files (*.*)' name = QtWidgets.QFileDialog.getSaveFileName(self.window, 'Save File', filter=files_filter) if all(name): func = self.writer.write_result( os.path.basename(os.path.splitext(name[0])[0]), self.default_tests) func() def save_qasm_circuit(self): files_filter = 'QASM files (*.qasm);;All files (*.*)' name = QtWidgets.QFileDialog.getSaveFileName(self.window, 'Save File', filter=files_filter) if all(name): file = open(name[0], 'w') text = self.test_info_tab.get_selected_test().circuit.qasm() file.write(text) file.close() def background_calc(self, cur_circuit, cur_computer): job = q.execute(cur_circuit, backend=cur_computer) job_monitor(job) self.__result = job.result() return job.result() def test_and_print(self, cur_test, computers): print('Starting test and print') worker = Worker(self.test_selected_computers, cur_test, computers) print('Pool start') self.thread_pool.start(worker) def print_s(self): print('s') def add_result_from_db(self, result: Result): self.results_info_tab.table_widget.insertRow( self.results_info_tab.table_widget.rowCount()) res_list = list() res_list.append(result.test_name) res_list.append(result.backend_name) res_list.append(result.job_status) res_list.append(result.date) res_list.append(result.shots) res_list.append(result.time) res_list.append(result.accuracy) for j in range(0, 7): self.results_info_tab.table_widget.setItem( self.results_info_tab.table_widget.rowCount() - 1, j, MyTableWidgetItem(str(res_list[j]))) self.results_info_tab.table_widget.resizeColumnsToContents() def add_new_result(self, result): self.results_info_tab.table_widget.insertRow( self.results_info_tab.table_widget.rowCount()) res_list = list(result.values()) result_value = result['Result'] res_list.remove(result_value) for j in range(0, 7): self.results_info_tab.table_widget.setItem( self.results_info_tab.table_widget.rowCount() - 1, j, MyTableWidgetItem(str(res_list[j]))) self.results_info_tab.table_widget.resizeColumnsToContents() def test_selected_computers(self, cur_test, computers): fields = [ 'Test_name', 'Backend_name', 'Job_status', 'Date', 'Shots_taken', 'Time_taken', 'Counts' ] print('start test_selected') print(str(self)) print(str(cur_test)) print(str(computers)) tabs = [] test = next(x for x in self.default_tests if x.name == cur_test) backend = None for computer in computers: try: backend = next(x for x in self.ibm_backends if str(x) == computer) except StopIteration: for back in self.inspire_backends: if computer == back['name']: backend = back if backend is None: raise RuntimeError('Unknown backend') tab = next(x for x in self.tab_infos if x.name == computer) tabs.append(tab) if computer in [ 'QX single-node simulator', 'QX single-node simulator SurfSara', 'Spin-2', 'Starmon-5' ]: worker = Worker2(test.run_inspire, backend, self.qi) print('Start inner pool') self.inner_thread_pool.start(worker) else: worker = Worker2(test.run_ibm, backend) print('Start inner pool') self.inner_thread_pool.start(worker) self.inner_thread_pool.waitForDone() for i in range(0, len(tabs)): self.drawer.plot_results2(tabs[i], test.not_drawn_res[i]['Counts']) print('Finished after testing work') for result in test.not_drawn_res: self.add_new_result(result) self.repo.insert_result(Result.create_result_from_dict(result)) test.not_drawn_res.clear() def enter_keys(self): self.registration_dialog.show_dialog() if self.registration_dialog.result() == QtWidgets.QDialog.Accepted: qiskit_token, dwave_token, inspire_token = self.registration_dialog.provide_api_tokens( ) is_update_necessary = False if self.__qiskit != qiskit_token: self.__qiskit = qiskit_token is_update_necessary = True if self.__dwave != dwave_token: self.__dwave = dwave_token is_update_necessary = True if self.__inspire != inspire_token: self.__inspire = inspire_token is_update_necessary = True if is_update_necessary: self.setup_tabs() self.dialog = TestCreationDialogWindow(self.tab_infos, self.default_tests) def choose_test(self): self.dialog.show_dialog() if self.dialog.result() == QtWidgets.QDialog.Accepted: test = self.dialog.get_selected_test() computers = self.dialog.get_selected_computers() if computers: self.test_and_print(test, computers) elif self.dialog.result() == QtWidgets.QDialog.Rejected: self.status_label.setText('Rejected') def show_status(self, message): def show(): self.status_label.setText(message) return show def setup_ui(self, main_window_local): main_window_local.setObjectName("main_window") main_window_local.resize(1024, 768) self.gridLayout_2 = QtWidgets.QGridLayout(self.central_widget) self.gridLayout_2.setObjectName("gridLayout_2") main_window_local.setCentralWidget(self.central_widget) self.menubar = QtWidgets.QMenuBar(main_window_local) self.menubar.setGeometry(QtCore.QRect(0, 0, 766, 21)) self.menubar.setObjectName("menubar") self.statusbar = QtWidgets.QStatusBar(main_window_local) self.statusbar.setObjectName("statusbar") main_window_local.setStatusBar(self.statusbar) self.menu_file = QtWidgets.QMenu(self.menubar) self.menu_file.setObjectName("menu_file") self.menu_service = QtWidgets.QMenu(self.menubar) self.menu_service.setObjectName("menu_service") self.menu_help = QtWidgets.QMenu(self.menubar) self.menu_help.setObjectName("menu_help") self.menu_edit = QtWidgets.QMenu(self.menubar) self.menu_edit.setObjectName("menu_edit") self.menu_edit.setStatusTip('Editing') main_window_local.setMenuBar(self.menubar) self.status_label = QtWidgets.QLabel(self.statusbar) self.status_label.setGeometry(5, -5, 500, 20) self.status_label.setObjectName('status_label') self.status_label.setText('') self.action_about = QtWidgets.QAction(main_window_local) self.action_about.setObjectName("action_about") self.action_about.setStatusTip('Подробнее о программе') self.action_registration = QtWidgets.QAction(main_window_local) self.action_registration.setObjectName('action_registration') self.action_registration.setStatusTip('Ввести ключи регистрации') self.action_registration.triggered.connect(self.enter_keys) self.action_new = QtWidgets.QAction(main_window_local) self.action_new.setObjectName("action_new") self.action_new.triggered.connect(self.choose_test) self.action_new.setStatusTip('Создать новый файл') self.test_info_tab.button_import.clicked.connect( self.open_qasm_circuit) self.test_info_tab.button_export.clicked.connect( self.save_qasm_circuit) self.results_info_tab.button_load.clicked.connect(self.open_results) self.results_info_tab.button_delete.clicked.connect( self.delete_results) self.action_open = QtWidgets.QAction(main_window_local) self.action_open.setObjectName("action_open") self.action_open.setStatusTip('Открыть файл результатов') self.action_save = QtWidgets.QAction(main_window_local) self.action_save.setObjectName("action_save") self.action_save.setStatusTip('Сохранить результаты') self.action_save_as = QtWidgets.QAction(main_window_local) self.action_save_as.setObjectName("action_save_as") self.action_save_as.setStatusTip('Сохранить результаты в новый файл') self.action_save_as.triggered.connect(self.save_results) self.action_close = QtWidgets.QAction(main_window_local) self.action_close.setObjectName("action_close") self.action_close.setStatusTip('Выйти из программы') self.action_update = QtWidgets.QAction(main_window_local) self.action_update.setObjectName("action_update") self.action_update.triggered.connect(self.clear_plots) self.action_update.setStatusTip('Очистить графики') self.action_test = QtWidgets.QAction(main_window_local) self.action_test.setObjectName("action_test") self.action_test.setStatusTip('Протестировать компьютеры') self.menu_file.addAction(self.action_new) self.menu_file.addAction(self.action_open) self.menu_file.addSeparator() self.menu_file.addAction(self.action_save) self.menu_file.addAction(self.action_save_as) self.menu_file.addSeparator() self.menu_file.addAction(self.action_close) self.menu_service.addAction(self.action_update) self.menu_service.addAction(self.action_test) self.menu_help.addAction(self.action_about) self.menu_help.addSeparator() self.menu_help.addAction(self.action_registration) self.menubar.addAction(self.menu_file.menuAction()) self.menubar.addAction(self.menu_edit.menuAction()) self.menubar.addAction(self.menu_service.menuAction()) self.menubar.addAction(self.menu_help.menuAction()) self.retranslate_ui(main_window_local) QtCore.QMetaObject.connectSlotsByName(main_window_local) def plot_result(self, cur_circuit, cur_result): def plot(): for tab_info in self.tab_infos: axes = tab_info.figure.add_subplot(111) axes.clear() plot_histogram(self.__result.get_counts(cur_circuit), ax=axes) tab_info.canvas.draw() return plot def clear_plots(self): for tab_info in self.tab_infos: axes = tab_info.figure.add_subplot(111) axes.clear() tab_info.canvas.draw() def plot_circuit(self, cur_circuit): for tab_info in self.tab_infos: axes = tab_info.figure.add_subplot(111) axes.clear() cur_circuit.draw(output='mpl', ax=axes) tab_info.canvas.draw() def retranslate_ui(self, main_window_local): _translate = QtCore.QCoreApplication.translate main_window_local.setWindowTitle( _translate("main_window", "QuantumTester 0.0.1 x64")) self.menu_file.setTitle(_translate("main_window", "Файл")) self.menu_service.setTitle(_translate("main_window", "Сервис")) self.menu_help.setTitle(_translate("main_window", "Помощь")) self.menu_edit.setTitle(_translate("main_window", "Правка")) self.action_about.setText(_translate("main_window", "О программе...")) self.action_registration.setText( _translate("main_window", "Регистрация")) self.action_new.setText(_translate("main_window", "Новый")) self.action_new.setShortcut(_translate("main_window", "Ctrl+N")) self.action_open.setText(_translate("main_window", "Открыть")) self.action_open.setShortcut(_translate("main_window", "Ctrl+O")) self.action_save.setText(_translate("main_window", "Сохранить")) self.action_save.setShortcut(_translate("main_window", "Ctrl+S")) self.action_save_as.setText( _translate("main_window", "Сохранить как...")) self.action_close.setText(_translate("main_window", "Закрыть")) self.action_update.setText(_translate("main_window", "Обновить")) self.action_test.setText(_translate("main_window", "Протестировать")) def setup_tabs(self): _translate = QtCore.QCoreApplication.translate self.ibm_backends = None self.dwave_backends = None self.inspire_backends = None self.rigetti_backends = None self.tab_infos = [] # backend1 = {'name': '9q-square-noisy-qvm', 'number_of_qubits': 9, # 'max_number_of_shots': 65536, 'max_experiments': 'unlimited', # 'description': 'v. 1.17', 'accuracy': 'not accurate', # 'remaining_jobs': 'unlimited'} # backend2 = {'name': '26q-qvm', 'number_of_qubits': 26, # 'max_number_of_shots': 65536, 'max_experiments': 'unlimited', # 'description': 'v. 1.17', 'accuracy': '0', # 'remaining_jobs': 'unlimited'} # self.rigetti_backends = [backend1, backend2] if self.__qiskit is not None: IBMQ.enable_account(self.__qiskit) ibm_provider = IBMQ.get_provider('ibm-q') self.ibm_backends = ibm_provider.backends() if self.__inspire is not None: enable_account(self.__inspire) QI.set_authentication() server_url = r'https://api.quantum-inspire.com' auth = get_token_authentication() self.qi = QuantumInspireAPI(server_url, auth) # backends = [] # for back in self.qi.get_backend_types(): # current_backend = {'name': back.get('name'), 'number_of_qubits': back.get('number_of_qubits'), # 'max_number_of_shots': back.get('max_number_of_shots'), # 'max_experiments': back.get('max_number_of_simultaneous_jobs'), # 'description': back.get('description'), 'accuracy': 'not measured', # 'remaining_jobs': back.get('max_number_of_simultaneous_jobs')} # backends.append(current_backend) self.inspire_backends = self.qi.get_backend_types() if self.ibm_backends is not None: for backend in self.ibm_backends: self.tab_infos.append(ComputerInfoTab(backend)) # if self.inspire_backends is not None: # for backend in self.inspire_backends: # # server = r'https://api.quantum-inspire.com' # # email = '*****@*****.**' # # password = '******' # # auth = HTTPBasicAuth(email, password) # # # # result = requests.get(f'{server}/projects', auth=auth) # # # # print(f'result status: {result.status_code}') # # print(result.json()) # # server = r'https://api.quantum-inspire.com' # # auth = coreapi.auth.TokenAuthentication(self.__inspire) # # client = coreapi.Client(auth=auth) # # content = client.get(f'{server}/backendtypes/1/calibration') # # print(content) # # action = ['projects', 'list'] # # result = client.action(schema, action) # # print(result) # # server = r'https://api.quantum-inspire.com' # # email = '*****@*****.**' # # password = '******' # # auth = HTTPBasicAuth(email, password) # # contents = urllib.request.urlopen(r'https://api.quantum-inspire.com/backendtypes/1/calibration/',).read() # self.tab_infos.append(ComputerInfoTab(backend._QuantumInspireBackend__backend)) if self.inspire_backends is not None: for backend in self.inspire_backends: self.tab_infos.append(ComputerInfoTab(backend)) if self.rigetti_backends is not None: for backend in self.rigetti_backends: self.tab_infos.append(ComputerInfoTab(backend)) self.tab_widget = QtWidgets.QTabWidget(self.central_widget) self.tab_widget.setObjectName("tabWidget") self.tab_widget.addTab(self.test_info_tab.tab, "") self.tab_widget.setTabText( self.tab_widget.indexOf(self.test_info_tab.tab), _translate('main_window', self.test_info_tab.name)) self.tab_widget.addTab(self.results_info_tab.tab, "") self.tab_widget.setTabText( self.tab_widget.indexOf(self.results_info_tab.tab), _translate('main_window', self.results_info_tab.name)) for tab_info in self.tab_infos: self.tab_widget.addTab(tab_info.tab, "") self.tab_widget.setTabText( self.tab_widget.indexOf(tab_info.tab), _translate("main_window", tab_info.tab.objectName())) self.gridLayout_2.addWidget(self.tab_widget, 0, 0, 1, 1) self.tab_widget.setCurrentIndex(0)
class TiledTexture(glh.Texture, metaclass=TiledTextureMeta): ''' Tiled texture implementation for the BlueSky GL gui. ''' class SlotHolder(QObject): ''' Wrapper class for Qt slot, which can only be owned by a QObject-derived parent. We need slots to allow signal receiver to be executed in the receiving (main) thread. ''' def __init__(self, callback): super().__init__() self.cb = callback @pyqtSlot(Tile) def slot(self, *args, **kwargs): self.cb(*args, **kwargs) def __init__(self, glsurface, tilesource='opentopomap'): super().__init__(target=glh.Texture.Target2DArray) self.threadpool = QThreadPool() tileinfo = bs.settings.tile_sources.get(tilesource) if not tileinfo: raise KeyError(f'Tile source {tilesource} not found!') max_dl = tileinfo.get('max_download_workers', bs.settings.max_download_workers) self.maxzoom = tileinfo.get('max_tile_zoom', bs.settings.max_tile_zoom) self.threadpool.setMaxThreadCount( min(bs.settings.max_download_workers, max_dl)) self.tileslot = TiledTexture.SlotHolder(self.load_tile) self.tilesource = tilesource self.tilesize = (256, 256) self.curtileext = (0, 0, 0, 0) self.curtilezoom = 1 self.curtiles = OrderedDict() self.fullscreen = False self.offsetscale = np.array([0, 0, 1], dtype=np.float32) self.bbox = list() self.glsurface = glsurface self.indextexture = glh.Texture(target=glh.Texture.Target2D) self.indexsampler_loc = 0 self.arraysampler_loc = 0 bs.net.actnodedata_changed.connect(self.actdata_changed) Signal('panzoom').connect(self.on_panzoom_changed) def add_bounding_box(self, lat0, lon0, lat1, lon1): ''' Add the bounding box of a textured shape. These bounding boxes are used to determine if tiles need to be downloaded. ''' if abs(lat1 - lat0) >= 178 and abs(lon1 - lon0) >= 358: self.fullscreen = True else: self.bbox.append((lat0, lon0, lat1, lon1)) def create(self): ''' Create this texture in GPU memory. ''' if self.isCreated(): return super().create() # Fetch a temporary tile image to get dimensions tmptile = Tile(self.tilesource, 1, 1, 1, 0, 0) img = tmptile.image self.setFormat(glh.Texture.RGBA8_UNorm) self.tilesize = (img.width(), img.height()) self.setSize(img.width(), img.height()) self.setLayers(bs.settings.tile_array_size) super().bind() self.allocateStorage() self.setWrapMode(glh.Texture.DirectionS, glh.Texture.ClampToBorder) self.setWrapMode(glh.Texture.DirectionT, glh.Texture.ClampToBorder) self.setMinMagFilters(glh.Texture.Linear, glh.Texture.Linear) # Initialize index texture # RG = texcoord offset, B = zoom factor, A = array index itexw = int(np.sqrt(bs.settings.tile_array_size) * 4 / 3 + 10) itexh = int(np.sqrt(bs.settings.tile_array_size) * 3 / 4 + 10) self.indextexture.create() self.indextexture.setFormat(glh.Texture.RGBA32I) self.indextexture.setSize(itexw, itexh) self.indextexture.bind(1) # self.indextexture.allocateStorage(glh.Texture.RGBA_Integer, glh.Texture.Int32) idxdata = np.array(itexw * itexh * [(0, 0, 0, -1)], dtype=np.int32) glh.gl.glTexImage2D_alt(glh.Texture.Target2D, 0, glh.Texture.RGBA32I, itexw, itexh, 0, glh.Texture.RGBA_Integer, glh.Texture.Int32, idxdata.tobytes()) self.indextexture.setWrapMode(glh.Texture.DirectionS, glh.Texture.ClampToBorder) self.indextexture.setWrapMode(glh.Texture.DirectionT, glh.Texture.ClampToBorder) self.indextexture.setMinMagFilters(glh.Texture.Nearest, glh.Texture.Nearest) shader = glh.ShaderSet.get_shader('tiled') self.indexsampler_loc = shader.uniformLocation('tile_index') self.arraysampler_loc = shader.uniformLocation('tile_texture') def bind(self, unit=0): ''' Bind this texture for drawing. ''' # Set sampler locations glh.ShaderProgram.bound_shader.setUniformValue(self.indexsampler_loc, 2) glh.ShaderProgram.bound_shader.setUniformValue(self.arraysampler_loc, 4) # Bind index texture to texture unit 0 self.indextexture.bind(2) # Bind tile array texture to texture unit 1 super().bind(4) def actdata_changed(self, nodeid, nodedata, changed_elems): ''' Update tile buffers when a different node is selected, or when the data of the current node is updated. ''' # Update pan/zoom if 'PANZOOM' in changed_elems: self.on_panzoom_changed(True) def on_panzoom_changed(self, finished=False): ''' Update textures whenever pan/zoom changes. ''' # Check if textures need to be updated viewport = self.glsurface.viewportlatlon() surfwidth_px = self.glsurface.width() # First determine floating-point, hypothetical values # to calculate the required tile zoom level # floating-point number of tiles that fit in the width of the view ntiles_hor = surfwidth_px / self.tilesize[0] # Estimated width in longitude of one tile est_tilewidth = abs(viewport[3] - viewport[1]) / ntiles_hor self.curtilezoom = tilezoom(est_tilewidth, self.maxzoom) # With the tile zoom level get the required number of tiles # Top-left and bottom-right tiles: x0, y0 = latlon2tilenum(*viewport[:2], self.curtilezoom) x1, y1 = latlon2tilenum(*viewport[2:], self.curtilezoom) nx = abs(x1 - x0) + 1 ny = abs(y1 - y0) + 1 self.curtileext = (x0, y0, x1, y1) # Calculate the offset of the top-left tile w.r.t. the screen top-left corner tile0_topleft = np.array(tilenum2latlon(x0, y0, self.curtilezoom)) tile0_bottomright = np.array( tilenum2latlon(x0 + 1, y0 + 1, self.curtilezoom)) tilesize_latlon0 = np.abs(tile0_bottomright - tile0_topleft) offset_latlon0 = viewport[:2] - tile0_topleft tex_y0, tex_x0 = np.abs(offset_latlon0 / tilesize_latlon0) # Calculate the offset of the bottom-right tile w.r.t. the screen bottom right corner tile1_topleft = np.array(tilenum2latlon(x1, y1, self.curtilezoom)) tile1_bottomright = np.array( tilenum2latlon(x1 + 1, y1 + 1, self.curtilezoom)) tilesize_latlon1 = np.abs(tile1_bottomright - tile1_topleft) offset_latlon1 = viewport[2:] - tile1_topleft tex_y1, tex_x1 = np.abs( offset_latlon1 / tilesize_latlon1) + [ny - 1, nx - 1] # Store global offset and scale for shader uniform self.offsetscale = np.array( [tex_x0, tex_y0, tex_x1 - tex_x0, tex_y1 - tex_y0], dtype=np.float32) # Determine required tiles index_tex = [] curtiles = OrderedDict() curtiles_difzoom = OrderedDict() for j, y in enumerate(range(y0, y1 + 1)): for i, x in enumerate(range(x0, x1 + 1)): # Find tile index if already loaded idx = self.curtiles.pop((x, y, self.curtilezoom), None) if idx is not None: # correct zoom, so dx,dy=0, zoomfac=1 index_tex.extend((0, 0, 1, idx)) curtiles[(x, y, self.curtilezoom)] = idx else: if finished: # Tile not loaded yet, fetch in the background task = TileLoader(self.tilesource, self.curtilezoom, x, y, i, j) task.signals.finished.connect(self.tileslot.slot, Qt.QueuedConnection) self.threadpool.start(task) # In the mean time, check if more zoomed-out tiles are loaded that can be used for z in range(self.curtilezoom - 1, max(2, self.curtilezoom - 5), -1): zx, zy, dx, dy = zoomout_tilenum( x, y, z - self.curtilezoom) idx = self.curtiles.pop((zx, zy, z), None) if idx is not None: curtiles_difzoom[(zx, zy, z)] = idx zoomfac = int(2**(self.curtilezoom - z)) dxi = int(round(dx * zoomfac)) dyi = int(round(dy * zoomfac)) # offset zoom, so possible offset dxi, dyi index_tex.extend((dxi, dyi, zoomfac, idx)) break else: # No useful tile found index_tex.extend((0, 0, 0, -1)) # Update curtiles ordered dict curtiles.update(curtiles_difzoom) curtiles.update(self.curtiles) self.curtiles = curtiles data = np.array(index_tex, dtype=np.int32) self.glsurface.makeCurrent() self.indextexture.bind(2) glh.gl.glTexSubImage2D_alt(glh.Texture.Target2D, 0, 0, 0, nx, ny, glh.Texture.RGBA_Integer, glh.Texture.Int32, data.tobytes()) def load_tile(self, tile): ''' Send loaded image data to GPU texture array. This function is called on callback from the asynchronous image load function. ''' # First check whether tile is still relevant # If there's a lot of panning/zooming, sometimes tiles are loaded that # become obsolete before they are even downloaded. if not ((self.curtilezoom == tile.zoom) and (self.curtileext[0] <= tile.tilex <= self.curtileext[2]) and (self.curtileext[1] <= tile.tiley <= self.curtileext[3])): return # Make sure our GL context is current self.glsurface.makeCurrent() # Check if tile is already loaded. This can happen here with multiple # pans/zooms shortly after each other layer = self.curtiles.get((tile.tilex, tile.tiley, tile.zoom), None) if layer is None: # This tile is not loaded yet. Select layer to upload it to layer = len(self.curtiles) if layer >= bs.settings.tile_array_size: # we're exceeding the size of the GL texture array. # Replace the least-recent tile _, layer = self.curtiles.popitem() self.curtiles[(tile.tilex, tile.tiley, tile.zoom)] = layer # Upload tile to texture array super().bind(4) self.setLayerData(layer, tile.image) self.release() # Update the ordering of the tile dict: the new tile should be on top self.curtiles.move_to_end((tile.tilex, tile.tiley, tile.zoom), last=False) # Update index texture idxdata = np.array([0, 0, 1, layer], dtype=np.int32) self.indextexture.bind(2) glh.gl.glTexSubImage2D_alt(glh.Texture.Target2D, 0, tile.idxx, tile.idxy, 1, 1, glh.Texture.RGBA_Integer, glh.Texture.Int32, idxdata.tobytes()) self.indextexture.release()
class Ui_MainWindow(object): """ Some static colors that will be used often in the UI """ GREEN = QtGui.QColor(196, 237, 194) YELLOW = QtGui.QColor(247, 247, 181) RED = QtGui.QColor(237, 194, 194) def __init__(self, MainWindow): """ UI Setup (Nasty, auto-generated code with some modifications) :param MainWindow: the main PyQt window """ MainWindow.setObjectName("MainWindow") MainWindow.resize(550, 540) MainWindow.setMinimumSize(QtCore.QSize(550, 525)) MainWindow.setMaximumSize(QtCore.QSize(550, 525)) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.lblLogo = QtWidgets.QLabel(self.centralwidget) self.lblLogo.setGeometry(QtCore.QRect(10, 10, 111, 51)) self.lblLogo.setText("") self.lblLogo.setPixmap(QtGui.QPixmap("data_files/DOH.gif")) self.lblLogo.setScaledContents(True) self.lblLogo.setObjectName("lblLogo") self.lblName = QtWidgets.QLabel(self.centralwidget) self.lblName.setGeometry(QtCore.QRect(150, 0, 391, 71)) self.lblName.setStyleSheet("color: rgb(36, 21, 255)") font = QtGui.QFont() font.setFamily("Arial") font.setPointSize(22) font.setBold(True) font.setWeight(75) self.lblName.setFont(font) self.lblName.setObjectName("lblName") self.hline1 = QtWidgets.QFrame(self.centralwidget) self.hline1.setGeometry(QtCore.QRect(0, 60, 551, 16)) self.hline1.setFrameShape(QtWidgets.QFrame.HLine) self.hline1.setFrameShadow(QtWidgets.QFrame.Sunken) self.hline1.setObjectName("hline1") self.lblPath = QtWidgets.QLabel(self.centralwidget) self.lblPath.setGeometry(QtCore.QRect(10, 70, 411, 31)) font = QtGui.QFont() font.setFamily("Arial") font.setPointSize(11) font.setBold(True) font.setWeight(75) self.lblPath.setFont(font) self.lblPath.setObjectName("lblPath") self.btnSelectFolder = QtWidgets.QPushButton(self.centralwidget) self.btnSelectFolder.setGeometry(QtCore.QRect(430, 70, 111, 31)) self.btnSelectFolder.setObjectName("btnSelectFolder") self.btnStart = QtWidgets.QPushButton(self.centralwidget) self.btnStart.setGeometry(QtCore.QRect(10, 440, 531, 31)) self.btnStart.setObjectName("btnStart") self.progressBar = QtWidgets.QProgressBar(self.centralwidget) self.progressBar.setGeometry(QtCore.QRect(10, 480, 531, 20)) self.progressBar.setProperty("value", 0) self.progressBar.setObjectName("progressBar") self.tableWidget = QtWidgets.QTableWidget(self.centralwidget) self.tableWidget.setGeometry(QtCore.QRect(10, 150, 531, 281)) self.tableWidget.setColumnCount(3) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setRowCount(0) self.tableWidget.setEditTriggers(QtWidgets.QTableWidget.NoEditTriggers) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setFamily("Arial") item.setFont(font) self.tableWidget.setHorizontalHeaderItem(0, item) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setFamily("Arial") item.setFont(font) self.tableWidget.setHorizontalHeaderItem(1, item) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setFamily("Arial") item.setFont(font) item.setTextAlignment(QtCore.Qt.AlignLeft) self.tableWidget.setHorizontalHeaderItem(2, item) self.lblUsername = QtWidgets.QLabel(self.centralwidget) self.lblUsername.setGeometry(QtCore.QRect(10, 110, 81, 31)) font = QtGui.QFont() font.setFamily("Arial") font.setPointSize(11) font.setBold(True) font.setWeight(75) self.lblUsername.setFont(font) self.lblUsername.setObjectName("lblUsername") self.txtUsername = QtWidgets.QLineEdit(self.centralwidget) self.txtUsername.setGeometry(QtCore.QRect(90, 110, 181, 31)) self.txtUsername.setObjectName("txtUsername") self.lblPassword = QtWidgets.QLabel(self.centralwidget) self.lblPassword.setGeometry(QtCore.QRect(280, 110, 81, 31)) font = QtGui.QFont() font.setFamily("Arial") font.setPointSize(11) font.setBold(True) font.setWeight(75) self.lblPassword.setFont(font) self.lblPassword.setObjectName("lblPassword") self.txtPassword = QtWidgets.QLineEdit(self.centralwidget) self.txtPassword.setGeometry(QtCore.QRect(360, 110, 181, 31)) self.txtPassword.setObjectName("txtPassword") self.txtPassword.setEchoMode(QtWidgets.QLineEdit.Password) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 550, 25)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) """ Set custom names and events for UI """ self.retranslate_ui(MainWindow) self.setup_events() QtCore.QMetaObject.connectSlotsByName(MainWindow) """ Thread pool for processing """ self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(max_threads) """ Variables for tracking progress """ # The total number of upload attempts, successful or not self.completed_tasks = 0 self.completed_tasks_lock = QtCore.QMutex() @staticmethod def show_error_message(msg): """ Helper method to display a messagebox with an error :param msg: the error message :return: None """ msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Critical) msg_box.setText(msg) msg_box.setWindowTitle("There's a problem...") msg_box.setStandardButtons(QMessageBox.Ok) msg_box.exec_() def start(self): """ Called when the user clicks the Begin Upload button. This function checks if there are valid MODS XML files loaded, whether there is a username and password and if there is, whether the username and password are valid. Then creates a threadpool and submits all the files to be processed :return: None """ # Make sure table has files, username and password are entered if self.tableWidget.rowCount() is 0: self.show_error_message("No files have been loaded! Select" + " a folder with valid MODS XML files.") elif len(self.txtUsername.text().strip()) is 0: self.show_error_message("You must enter a username!") elif len(self.txtPassword.text().strip()) is 0: self.show_error_message("You must enter a password!") elif not sign_in(self.txtUsername.text(), self.txtPassword.text()): self.show_error_message( "Ensure your username and password are correct!") else: for i in range(self.tableWidget.rowCount()): items = [] for j in range(self.tableWidget.columnCount()): item = self.tableWidget.item(i, j) items.append(str(item.text())) worker = Worker(upload, items, i) worker.signals.started.connect(self.started) worker.signals.result.connect(self.worker_response_handler) self.threadpool.start(worker) def started(self, row_index): """ The function that handles a started emit from a Worker. When a upload task starts, it calls this method to update the UI and set the row to yellow to inform the user the row is being processed (i.e. the uploading process) :param row_index: the index of the row being processed :return: None """ self.set_row_color(row_index, Ui_MainWindow.YELLOW) def worker_response_handler(self, completed): """ The handler to handle a response from a Worker when an upload task is completed (not necessarily successful) :param completed: the tuple holding the result from the Worker :return: None """ # Pattern match the tuple success, row_index = completed if success: self.set_row_color(row_index, Ui_MainWindow.GREEN) else: self.set_row_color(row_index, Ui_MainWindow.RED) # Safely increment completed tasks # and set progress bar value self.completed_tasks_lock.lock() self.completed_tasks = self.completed_tasks + 1 self.progressBar.setValue( (self.completed_tasks / self.tableWidget.rowCount()) * 100) self.completed_tasks_lock.unlock() def load_xml_from_folder(self): """ Loads all XML files from a folder recursively using globe. :return: None """ # Reset the UI and its variables self.reset() # Create a set of all the files to upload file_list = set() for filename in glob.iglob(self.lblPath.text().replace("/", os.sep) + os.sep + '**' + os.sep + '*.xml', recursive=True): file_list.add(filename) # Set the new file count self.file_count = len(file_list) # Reset the table widget self.tableWidget.clearContents() # Add the rows self.tableWidget.setRowCount(len(file_list)) # Populate the rows row_index = 0 for filename in file_list: try: # Parse the path to get the repository and number fnms = filename.split(os.sep) objIDpts = fnms[-1].split("_") repository = objIDpts[0] number = objIDpts[1].split(".")[0] self.tableWidget.setItem( row_index, 0, QtWidgets.QTableWidgetItem(repository)) self.tableWidget.setItem(row_index, 1, QtWidgets.QTableWidgetItem(number)) self.tableWidget.setItem(row_index, 2, QtWidgets.QTableWidgetItem(filename)) row_index = row_index + 1 except Exception as e: print(e) # No valid DOH files found if row_index is 0: self.tableWidget.setRowCount(0) # Set Path column size header = self.tableWidget.horizontalHeader() header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) def set_folder(self): """ Sets the folder selected by the user in the label and calls the function to load XML files from it :return: None """ dir_path = str( QFileDialog.getExistingDirectory(None, 'Select Folder', self.lblPath.text(), QFileDialog.ShowDirsOnly)) if dir_path is not None and len(dir_path) > 0: self.lblPath.setText(dir_path.replace("/", os.sep)) self.load_xml_from_folder() def reset(self): """ Resets the UI variables and elements for another upload session :return: None """ self.completed_tasks = 0 self.progressBar.setValue(0) def setup_events(self): """ Connects the UI buttons to relevant functions, called only once at the start of the program :return: None """ self.btnSelectFolder.clicked.connect(self.set_folder) self.btnStart.clicked.connect(self.start) def set_row_color(self, row_index, color): """ Sets the color of a row given the index of it :param row_index: the index of the row to color :param color: the color as a QtGui.QColor e.g. QtGui.QColor(QtCore.Qt.green) Can also use the static color variables set in the UI for yellow, red & green :return: None """ for i in range(self.tableWidget.columnCount()): self.tableWidget.item(row_index, i).setBackground(color) def retranslate_ui(self, MainWindow): """ Renames all the elements that need custom text from their default text :param MainWindow: the ui window :return: None """ _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle( _translate("MainWindow", "DOH - MODS XML Uploader")) self.lblName.setText( _translate("MainWindow", "DOH - MODS XML Uploader")) self.lblPath.setText( _translate("MainWindow", os.path.dirname(os.path.realpath(__file__)))) self.btnSelectFolder.setText( _translate("MainWindow", "Select Folder...")) self.btnStart.setText(_translate("MainWindow", "Start Upload")) item = self.tableWidget.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "Repository")) item = self.tableWidget.horizontalHeaderItem(1) item.setText(_translate("MainWindow", "Number")) item = self.tableWidget.horizontalHeaderItem(2) item.setText(_translate("MainWindow", "Path")) self.lblUsername.setText(_translate("MainWindow", "Username")) self.lblPassword.setText(_translate("MainWindow", "Password"))
class main_program(QObject): def __init__(self): super(main_program, self).__init__() self.video_source = None self.ui = Ui_MainWindow() self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(8) self.database = Database() self.combo_list = [ "Görüntü kaynağını seçiniz...", "Webcam", "IP Kamera", "IP Kamera 2" ] self.is_signedin = False def button_connections(self): # Activate in main. self.ui.start_stop_button.clicked.connect(self.Start) # self.ui.stop_button.clicked.connect(self.Stop) self.ui.to_taskbar_button.clicked.connect(MainWindow.showMinimized) self.ui.quit_button.clicked.connect(sys.exit) self.ui.btn_page_1.clicked.connect( lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_1)) self.ui.btn_page_2.clicked.connect( lambda: self.ui.stackedWidget.setCurrentWidget(self.ui.page_2)) self.ui.media_source_combobox.addItems(self.combo_list) self.ui.media_source_combobox.activated.connect(self.ComboSelected) self.ui.sign_out_button.clicked.connect(self.sign_out_clicked) self.ui.sign_in_button.clicked.connect(self.login_func) self.ui.reload_table_button.clicked.connect(self.load_report_table) @pyqtSlot(QImage) def setImage(self, image): self.ui.image_frame.setPixmap(QPixmap.fromImage(image)) if self.detection.stopped: self.ui.image_frame.setPixmap( QtGui.QPixmap(":/placeholders/Resources/placeholder2.png")) @pyqtSlot(str) def setInfo(self, statement): self.ui.out_info_box.setText(statement) @pyqtSlot(str) def setTitleBox(self, statement): self.ui.info_title_box.setText(statement) def Start(self): if self.is_signedin: self.ui.out_info_box.setText("") if self.check_video_source(): self.ui.image_frame.setPixmap( QtGui.QPixmap("Resources/please_wait.png")) self.detection = Detection() self.detection.video_source = self.video_source # Connect signals and slots self.detection.signals.changeButton.connect( self.change_start_context) self.detection.signals.changePixmap.connect(self.setImage) self.detection.signals.changeTextBox.connect(self.setInfo) self.detection.signals.changeTitleBox.connect(self.setTitleBox) self.threadpool.start(self.detection) self.change_start_context("stop_button") else: self.ui.out_info_box.setText("Video kaynağınızı kontrol edin.") else: self.ui.out_info_box.setText("Lütfen önce giriş yapınız.") @pyqtSlot(str) def change_start_context(self, mode): if mode == "start_button": self.ui.start_stop_button.clicked.disconnect() self.ui.start_stop_button.clicked.connect(self.Start) self.ui.start_stop_button.setStyleSheet( "QPushButton {\n" "color: rgb(255, 255, 255);\n" "border: 0px solid;\n" "background-color: #667BC4;\n" "padding:0.5em;\n" "font-weight:bold;\n" "font-size:12px;\n" "}\n" "QPushButton:hover {\n" " background-color: #7289DA;\n" "}") self.ui.start_stop_button.setText("Başlat") elif mode == "stop_button": self.ui.start_stop_button.clicked.disconnect() self.ui.start_stop_button.clicked.connect(self.Stop) self.ui.start_stop_button.setStyleSheet( "QPushButton {\n" "color: rgb(255, 255, 255);\n" "border: 0px solid;\n" "background-color: #ff2115;\n" "padding:0.5em;\n" "font-weight:bold;\n" "font-size:12px;\n" "}\n" "QPushButton:hover {\n" "background-color: #ff392e;\n" "}") self.ui.start_stop_button.setText("Durdur") def check_video_source(self): if self.video_source is None: return False else: _, frame = cv2.VideoCapture(self.video_source).read() if frame is not None: return True else: return False # TODO: When loop ends, button context should change. def Stop(self): try: self.detection.stopped = True self.threadpool.clear() self.threadpool.releaseThread() # print("Active thread count last (stop): {}".format(self.threadpool.activeThreadCount())) # self.change_start_context("start_button") except AttributeError: print("Detection is not initialize yet.") def ComboSelected(self): media_source_str = self.ui.media_source_combobox.currentText() current_index = self.ui.media_source_combobox.currentIndex() if current_index == 0: self.video_source = None if current_index == 1: self.video_source = 0 if current_index == 2: self.video_source = 'videos/toprakli_fit_buyuk.mp4' if current_index == 3: self.video_source = "videos/inek_field.mp4" #################################################### # SIGN IN - SIGN OUT # #################################################### def starting_page(self): """ This function answer this question: 'Did user sign in before or not?' """ is_matching, file_path = self.database.get_data_n_check() if is_matching: self.change_profile_button_context(1) else: try: os.remove(file_path) self.ui.info_box.setText( "Güvenlikle ilgili bir sorun meydana geldi.\nYeniden giriş yapın." ) except FileNotFoundError: print("Usr.md file doesn't exist.") self.change_profile_button_context(0) def change_profile_button_context(self, status): # 0 is for NOT signed in, 1 is for signed in. icon = QtGui.QIcon() if status == 0: self.ui.btn_page_4.disconnect() self.ui.btn_page_4.clicked.connect( lambda: self.ui.stackedWidget.setCurrentWidget(self.ui. sign_in_page)) icon.addPixmap( QtGui.QPixmap( ":/icons/Resources/icons/32px/user_alternative.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.ui.btn_page_4.setIcon(icon) self.is_signedin = False elif status == 1: self.ui.btn_page_4.disconnect() self.ui.btn_page_4.clicked.connect( lambda: self.ui.stackedWidget.setCurrentWidget(self.ui. profile_page)) icon.addPixmap( QtGui.QPixmap(":/icons/Resources/icons/32px/user_active.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.ui.btn_page_4.setIcon(icon) self.is_signedin = True # cu stands for current user. cu_email, cu_name, cu_surname = self.database.get_current_user_info( ) self.ui.current_name_surname.setText("{} {}".format( cu_name, cu_surname)) self.ui.current_email.setText("{}".format(cu_email)) def login_func(self): self.ui.profile_info_box.setAlignment(QtCore.Qt.AlignCenter) if self.ui.email.text() == "" or self.ui.password.text() == "": self.ui.info_box.setStyleSheet("color:#ff5048;font-weight: 700;") self.ui.info_box.setText("Boş alanları doldurunuz.") else: encrypted_pass = self.database.encrpt_password( self.ui.password.text()) email = self.ui.email.text() try: self.database.cursor.execute( "select name, email, password, userID from User WHERE User.email = %s and User.password=%s", (email, encrypted_pass)) x = self.database.cursor.next() CURRENT_USER_NAME = x[0] CURRENT_USER_EMAIL = x[1] CURRENT_USER_ID = x[3] # Save user information to local. self.save_current_user_local(CURRENT_USER_EMAIL, CURRENT_USER_ID) # Change the context and icon of profile button self.change_profile_button_context(1) # Redirecting to page 2. self.ui.stackedWidget.setCurrentWidget(self.ui.profile_page) # Complete message. self.ui.profile_info_box.setText(f"Giriş başarılı.") except StopIteration: self.ui.info_box.setStyleSheet( "color:#ff5048; font-weight: 700;") self.ui.info_box.setText("E-postanız veya parolanız yanlış.") def save_current_user_local(self, email, uid): file_exist_message = "Old user. [File exist]" # Get OS of user. platform_name = platform.system() if platform_name == "Windows": save_dir = os.getenv('APPDATA') save_dir += '\\Provactus\\' file_path = save_dir + '\\usr.md' elif platform_name == "Linux": save_dir = '/var/Provactus' file_path = '/var/Provactus/usr.md' # Trying to create file. try: os.mkdir(save_dir) except FileExistsError: print(file_exist_message) # Local registry for following usages. with open(file_path, 'w+') as file: file.write(f"{uid}\n{email}\n") def sign_out_clicked(self): # Get OS of user. platform_name = platform.system() if platform_name == "Windows": save_dir = os.getenv('APPDATA') save_dir += '\\Provactus\\' file_path = save_dir + '\\Provactus\\usr.md' elif platform_name == "Linux": file_path = "/var/Provactus/usr.md" os.remove(file_path) self.change_profile_button_context(0) # Redirecting to page 2. self.ui.stackedWidget.setCurrentWidget(self.ui.sign_in_page) self.ui.info_box.setText("") #################################################### # Report # #################################################### def load_report_table(self): if self.is_signedin: t = datetime.now() current_time = t.strftime("%d/%m/%y %H:%M:%S.%f")[:-4] self.report_page_controls(True) self.ui.report_info_box.setText( "Son güncellenme tarihi: {}".format(current_time)) # To update the information, we have to re-initialize the db. self.database = Database() (report_id, date, elapsed, total_left, total_right, user_id, enter_position) = self.database.get_reports( self.database.get_id_local()) var_collection = [date, elapsed, total_left, total_right] var_collection_rev = [date, elapsed, total_right, total_right] # reverse vars for var in var_collection: var.reverse() # listing if len(report_id) != 0: self.ui.report_table.setRowCount(len(report_id)) for col_n in range(4): for row_n in range(len(report_id)): if enter_position[row_n] == "right": self.ui.report_table.setItem( row_n, col_n, QtWidgets.QTableWidgetItem( str(var_collection[col_n][row_n]))) elif enter_position[row_n] == "left": self.ui.report_table.setItem( row_n, col_n, QtWidgets.QTableWidgetItem( str(var_collection_rev[col_n][row_n]))) else: self.report_page_controls(False) self.ui.report_info_box.setText("Henüz rapor oluşturmadınız.") else: self.report_page_controls(False) self.ui.report_info_box.setText("Önce giriş yapınız.") def report_page_controls(self, is_okay): self.ui.report_info_box.setAlignment(QtCore.Qt.AlignCenter) if is_okay is True: self.ui.report_info_box.setStyleSheet("color: #FFFFFF;\n" "font-weight: 700;\n" "font-size: 14px;\n" "margin:2em;\n" "") self.ui.report_table.show() self.ui.report_info_box.setText("") else: self.ui.report_info_box.setStyleSheet("color: #ff5048;\n" "font-weight: 700;\n" "font-size: 14px;\n" "margin:2em;\n" "") self.ui.report_table.close() self.ui.report_info_box.show()
class BaseProcess(QObject): def __init__(self, queue_name, default_limit): super().__init__() self.running_tasks = {} self.done_tasks = [] self.default_limit = default_limit self.queue = PGQ(queue_name) self.queue_name = queue_name self.pool = QThreadPool(self) self.pool.setMaxThreadCount(5000) signal.signal(signal.SIGUSR1, self.__signal_handler) self.items = [] try: __items = TaskModel.select().where((TaskModel.status == constants.STATUS_RUNNING) & (TaskModel.queue_name == self.queue_name)) for __item in __items: self.items.append(__item) except OperationalError: raise NetworkError('Network is unreachable') def run(self): while 1: # sleep for decrease cpu usage sleep(0.01) QCoreApplication.processEvents() from_db = False if self.running_tasks.__len__() >= self.limit(): continue try: if self.items: from_db = True item = self.items.pop() else: item = self.queue.get() # type: RequestObject if item: if not from_db: task_model = TaskModel() task_model.route = item.route task_model.process_id = item.process_id task_model.status = constants.STATUS_RUNNING task_model.data = item.data task_model.call_back = item.call_back task_model.token = item.token task_model.module_version = item.module_version task_model.queue_name = self.queue_name task_model.save() else: task_model = item task = Task(task_model) if task.instance_module: task.instance_module.task_finished.connect(self.__task_finished) task.setAutoDelete(True) self.running_tasks.update({item.process_id: task}) # check cancel or pause request before start self.apply_action_by_pid(task, item.process_id) self.pool.start(task) else: # TODO: set error alarm ApiLogging.error('problem running task') except Exception as e: ApiLogging.error('process exception' + str(e)) # TODO: set error alarm continue def limit(self): try: limit = Setting.get(self.queue_name) if limit: return int(limit) except Exception: # use default limit if exception or not present pass return self.default_limit @pyqtSlot(str, name='task_finished') def __task_finished(self, task_id): ApiLogging.info('task finished') del self.running_tasks[task_id] self.done_tasks.append(task_id) self.send_signal() @staticmethod def send_signal(): pids = [] for process_name in constants.APP_PROCESSES: if process_name.get('name') == 'process_sync.py': pids = find_pid(process_name.get('token')) if len(pids) > 1: ApiLogging.warning('Too many sync process running') elif len(pids) == 1: p = psutil.Process(pids[0]) p.send_signal(signal.SIGUSR1) def __signal_handler(self, signal, frame): ApiLogging.info('base process signal received') # TODO: update limit self.check_pending_actions() def apply_action_by_pid(self, task, pid): actions = ActionModel.select().where(ActionModel.process_id == pid) for action in actions: if action.action == constants.STATUS_PAUSE: task.instance_module.pause_request = action.timeout elif action.action == constants.STATUS_CANCEL: task.instance_module.cancel_request = True elif action.action == constants.STATUS_RESUME: task.instance_module.resume_request = True action.status = constants.STATUS_SUCCESS action.save() def check_pending_actions(self): actions = ActionModel.select().where(ActionModel.status == constants.STATUS_PENDING) for action in actions: if action.process_id in self.running_tasks: task = self.running_tasks.get(action.process_id) if task: if action.action == constants.STATUS_PAUSE: task.instance_module.pause_request = action.timeout elif action.action == constants.STATUS_CANCEL: task.instance_module.cancel_request = True elif action.action == constants.STATUS_RESUME: task.instance_module.resume_request = True action.status = constants.STATUS_SUCCESS action.save() elif action.process_id in self.done_tasks: action.status = constants.STATUS_SUCCESS action.save()
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import QDate, QRunnable, QObject, pyqtSignal, pyqtSlot, QThreadPool, QMutex from PyQt5.QtWidgets import QMessageBox, QAbstractItemView from robobrowser import RoboBrowser import getDOHCollns import os import re from datetime import datetime """ Thread pool for all the threads """ threadpool = QThreadPool() threadpool.setMaxThreadCount(100) """ The session that will be used by all browser instances to access the collections. Set in sign_in() """ global_session = None """ Regex to get individual collection objects that end in numbers """ num_str = r'/islandora/object/\D*?%3A\d*?$' # [a-z]+%3A[0-9]+ """ Track how many MODS XML it is possible to download. This value changes based on how many collections the user selects. """ downloadable = 0 """
class videoDownloaderGui(QMainWindow): def __init__(self, parent=None): ''' sets up videoDownloader gui ''' super().__init__(parent) self.setGeometry(100, 100, 2000, 1250) self.setWindowTitle('DownTube') self.setWindowIcon(QIcon(resource_path('./gui/py.png'))) central = QWidget() self.setCentralWidget(central) self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.settings = {} self.settingsFile = 'settings.pickle' if os.path.isfile(self.settingsFile): self.loadSettings() else: self.settings['path'] = addToPath(findDesktop(), '/downloaded videos') self.settings['defultRes'] = '720p' self.dumpSettings() self.index = 0 self.videos = [] self.resolution = self.settings['defultRes'] self.downloadPath = self.settings['path'] self.downloadCounter = 0 self.progressbars = [] self.downloading = {} self.downloadTitles = [] self.status = [] self.botbots = [] self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(10) self.startbot = self.threadpool.start self.grid = QGridLayout() self.grid.setAlignment(Qt.AlignCenter) self.api = None central.setLayout(self.grid) self.makeAndSendBot(self.connectToApi) mainMenu = self.menuBar() settingsMenu = mainMenu.addMenu('&Settings') fileLocation = QAction("&Download locaition", self) fileLocation.setShortcut("Ctrl+SHIFT+S") fileLocation.setStatusTip( 'set the file location to download videos to') fileLocation.triggered.connect(self.getfolder) settingsMenu.addAction(fileLocation) resoulutionMenu = mainMenu.addMenu('&Resoulution') for res in ['360p', '720p']: resoulution = QAction(f"&{res}", self) resoulution.setStatusTip(f'set the download size to {res}') resoulution.triggered.connect(partial(self.setRes, res)) resoulutionMenu.addAction(resoulution) self.description = QTextBrowser() self.description.setText( 'description:\ntype somthing in to look for videos\nenjoy 🤞') self.description.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.description.setStatusTip('the description of the current video') self.searchbar = QLineEdit() self.searchbar.setPlaceholderText('search for video or enter url') self.searchbar.setStyleSheet( "border: 2px solid black;border-radius: 10px;font-size: 36px;") self.searchbar.setSizePolicy( QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)) self.searchbar.setStatusTip('search for a video') self.searchbar.returnPressed.connect(self.searchVids) self.downloadbtn = QPushButton() self.downloadbtn.setText('Download') self.downloadbtn.setStyleSheet( 'QPushButton {background-color: darkgray; border: 1px solid black;border-radius: 10px;font-size: 30px;color: white;padding: 5px 15px;}' ) self.downloadbtn.setEnabled(False) self.downloadbtn.setSizePolicy( QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)) self.downloadbtn.setCursor(QCursor(Qt.PointingHandCursor)) self.downloadbtn.setStatusTip('download the current video') self.downloadbtn.clicked.connect( lambda: self.makeAndSendBot(self.downloadVideo, startedFunc=self.onDownloadStart, progressFunc=self.showBars, finishedFunc=self.downloadBtnSetup, errorFunc=self.downloadFailed)) self.backbtn = QPushButton() self.backbtn.setText('<<<') self.backbtn.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)) self.backbtn.setCursor(QCursor(Qt.PointingHandCursor)) self.backbtn.clicked.connect(lambda: self.changeIndex(-1)) self.backbtn.setStatusTip('go to last video') self.backbtn.setEnabled(False) self.nextbtn = QPushButton() self.nextbtn.setText('>>>') self.nextbtn.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)) self.nextbtn.setCursor(QCursor(Qt.PointingHandCursor)) self.nextbtn.clicked.connect(lambda: self.changeIndex(1)) self.nextbtn.setStatusTip('go to next video') self.nextbtn.setEnabled(False) self.picture = QLabel() img = QPixmap(resource_path('./gui/youtube-logo.jpg')) img = img.scaled(1280, 720) self.picture.setPixmap(img) self.picture.setStatusTip('the thumbnail of the video selected') self.titleDisplay = QLabel() self.titleDisplay.setText('search for a video') self.titleDisplay.setStatusTip('the title of the selected video') self.videoType = QComboBox() self.videoType.addItems(['video and audio', 'audio only']) self.videoType.setCursor(QCursor(Qt.PointingHandCursor)) self.videoType.setStatusTip('pick your download format') self.progressGrid = QGridLayout() progressSpacer = QSpacerItem(20, 200, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding) for i in range(20): if i % 2 == 0: title = QLabel() title.setWordWrap(True) self.progressGrid.addWidget(title, i, 0, 1, 2) self.downloadTitles.append(title) title.setHidden(True) else: prog = QProgressBar() prog.setValue(100) self.progressGrid.addWidget(prog, i, 0, 1, 2) self.progressbars.append(prog) prog.setHidden(True) self.progressGrid.addItem(progressSpacer, 20, 0) searchbarSpacer = QSpacerItem(20, 60, QSizePolicy.Minimum, QSizePolicy.Preferred) descriptionSpacer = QSpacerItem(325, 20, QSizePolicy.Preferred, QSizePolicy.Minimum) downloadSpacer = QSpacerItem(20, 25, QSizePolicy.Maximum, QSizePolicy.Preferred) progressGridSpacer = QSpacerItem(200, 20, QSizePolicy.Preferred, QSizePolicy.Minimum) self.grid.addWidget(self.videoType, 3, 9, 1, 2) self.grid.addItem(progressGridSpacer, 1, 0, rowSpan=1, columnSpan=2) self.grid.addWidget(self.titleDisplay, 3, 5, 1, 3) self.grid.addWidget(self.picture, 4, 5, 3, 6) self.grid.addWidget(self.backbtn, 7, 5, 1, 3) self.grid.addWidget(self.nextbtn, 7, 8, 1, 3) self.grid.addItem(downloadSpacer, 8, 6, rowSpan=1, columnSpan=1) self.grid.addWidget(self.downloadbtn, 9, 5, 1, 6) self.grid.addItem(searchbarSpacer, 10, 4, rowSpan=1, columnSpan=1) self.grid.addWidget(self.searchbar, 11, 4, 1, 10) self.grid.addItem(descriptionSpacer, 8, 14, rowSpan=1, columnSpan=4) self.grid.addWidget(self.description, 3, 14, 4, 3) self.grid.addLayout(self.progressGrid, 4, 0, 3, 4) def connectToApi(self): self.api = ytsearch.youtubeConnect(key.key) def setRes(self, resoulution): self.resolution = resoulution def searchVids(self): ''' search through the youtube api and acts acordingly ''' while not self.api: time.sleep(.05) matchedText = re.search('youtube\.com/watch\?v=(.+)', self.searchbar.text()) if matchedText: url = matchedText.group(1) self.videos = [ self.api.video_by_url(url, thumbnailQuality='maxres', defultQuality='high') ] else: self.videos = self.api.video_search(self.searchbar.text(), limit=10, thumbnailQuality='maxres', defultQuality='high') videoCount = len(self.videos) if videoCount >= 1: self.index = 0 self.status = ['download' for x in self.videos] self.downloadThumbnails() self.setVideo() if videoCount == 1: self.nextbtn.setEnabled(False) self.backbtn.setEnabled(False) else: self.nextbtn.setEnabled(True) self.backbtn.setEnabled(False) self.searchbar.setPlaceholderText('search for video or enter url') else: self.emptySearch() def emptySearch(self): ''' sets up a empty search gui ''' self.searchbar.setText('') self.searchbar.setPlaceholderText( 'no videos availaible that match the search') self.nextbtn.setEnabled(False) self.backbtn.setEnabled(False) img = QPixmap(resource_path('./gui/empty.jpg')) img = img.scaled(1280, 720) self.picture.setPixmap(img) self.downloadBtnSetup(state='empty') def downloadThumbnails(self): ''' downloads the thumbnails of all the videos so you could display them later ''' count = 0 for vid in self.videos: urlretrieve(vid.thumbnailLink, f'gui/thumbnails/thumbnail{count}.jpg') count += 1 def setVideo(self): ''' set up the gui based on the current video ''' img = QPixmap( resource_path(f'gui/thumbnails/thumbnail{self.index}.jpg')) img = img.scaled(1280, 720, transformMode=Qt.FastTransformation) self.picture.setPixmap(img) self.description.setText(self.videos[self.index].description) self.setTitleText(self.videos[self.index].title) self.downloadBtnSetup() def setTitleText(self, text): ''' displays title text above thumbnail ''' if len(text) >= 40: self.titleDisplay.setText(text[:40] + '...') else: self.titleDisplay.setText(text) def changeIndex(self, num): ''' changes the index if its not out of range ''' self.index += num if self.index + 1 == len(self.videos): self.nextbtn.setEnabled(False) self.backbtn.setEnabled(True) elif self.index == 0: self.backbtn.setEnabled(False) self.nextbtn.setEnabled(True) else: self.backbtn.setEnabled(True) self.nextbtn.setEnabled(True) self.setVideo() def downloadBtnSetup(self, state=None): ''' sets up the download bar based on the currently selected video ''' if not state: state = self.status[self.index] if state == 'downloading': self.downloadbtn.setText('Downloading..') bgColor = 'darkgray' bgColorHover = 'gray' bgColorPressed = 'light gray' self.downloadbtn.setEnabled(False) elif state == 'downloaded': self.downloadbtn.setText('Download again') bgColor = 'gray' bgColorHover = 'lightgray' bgColorPressed = 'lightgray' self.downloadbtn.setEnabled(True) elif state == 'download': self.downloadbtn.setText('Download') bgColor = 'rgb(0, 85, 255)' bgColorHover = 'rgb(89, 111, 255)' bgColorPressed = 'lightblue' self.downloadbtn.setEnabled(True) elif state == 'empty': self.downloadbtn.setText('Download') bgColor = 'darkgray' bgColorHover = 'gray' bgColorPressed = 'light gray' self.downloadbtn.setEnabled(False) elif state == 'error': self.downloadbtn.setText('Download Failed') bgColor = 'red' bgColorHover = 'orange' bgColorPressed = 'darkred' self.downloadbtn.setEnabled(True) self.downloadbtn.setStyleSheet( 'QPushButton {background-color: %s; border: 1px solid black;border-radius: 10px;font-size: 30px;color: white;padding: 5px 15px;}' 'QPushButton:pressed { background-color: %s }' 'QPushButton:hover { background-color: %s }' % (bgColor, bgColorPressed, bgColorHover)) def onDownloadStart(self): self.status[self.index] = 'downloading' self.downloadBtnSetup() def downloadVideo(self): ''' downloads video that is currently selected ''' index = self.index video = self.videos[index] print('downloading ' + video.url) vid = None for i in range(4): try: yt = YouTube(video.url, on_progress_callback=self.progress_func) vid = yt.streams.filter(res=self.resolution, progressive=True).first() vid.download(self.settings['path']) self.status[index] = 'downloaded' print('download complete') return except: print(f'tried {i} time(s)') if vid in self.downloading: index = self.downloading.pop(vid) self.progressbars[index].setHidden(True) self.downloadTitles[index].setHidden(True) yt = YouTube(video.url, on_progress_callback=self.progress_func) vid = yt.streams.filter(res=self.resolution, progressive=True).first() vid.download('./videos') self.status[index] = 'downloaded' print('download complete') return def downloadFailed(self, error): print(error) self.status[self.index] = 'error' self.downloadBtnSetup() def progress_func(self, stream, chunk, bytes_remaining): size = stream.filesize progress = (float(abs(bytes_remaining - size) / size)) * float(100) self.bot.signals.progress.emit((stream, int(progress))) def showBars(self, info): ''' display all progressBars in a list ''' stream, progress = info if stream not in self.downloading: self.downloadCounter += 1 self.downloading[stream] = self.downloadCounter % 10 index = self.downloading[stream] self.downloadTitles[index].setText(stream.title) self.downloadTitles[index].setHidden(False) else: index = self.downloading[stream] self.progressbars[index].setValue(progress) self.progressbars[index].setHidden(False) if progress == 100: self.downloadTitles[index].setHidden(True) self.progressbars[index].setHidden(True) self.downloading.pop(stream) return def getfolder(self): dirName = QFileDialog.getExistingDirectory(caption='open a folder', directory='c:\\') if dirName != '': self.settings['path'] = os.path.normpath(dirName) self.dumpSettings() def loadSettings(self): with open(self.settingsFile, 'rb') as f: self.settings = pickle.load(f) def dumpSettings(self): with open(self.settingsFile, 'wb') as f: pickle.dump(self.settings, f) def makeAndSendBot(self, func, *args, **kwargs): ''' makes and sends out a thread ''' self.bot = makebot(func, *args, **kwargs) self.startbot(self.bot)
super().__init__() def run(self): index = 0 if not os.path.exists( f"download/{self.qq}/{self.item[:self.item.find('.json')]}"): os.makedirs( f"download/{self.qq}/{self.item[:self.item.find('.json')]}") for item in self.l: print("正在处理", index, self.qq) url = item.get("photo") or item.get('video') html = requests.get(url) if html.status_code != 200: print("下载失败!") return None filename = f"download/{self.qq}/{self.item[:self.item.find('.json')]}/{index}.{'mp4' if item.get('video') else 'jpeg'}" with open(filename, 'wb') as f: f.write(html.content) index += 1 threadpool = QThreadPool() threadpool.setMaxThreadCount(8) qqs = [""] for qq in qqs: for item in os.listdir(f"data/{qq}"): with open(f"data/{qq}/{item}", encoding='utf-8') as f: urls = json.load(f) threadpool.start(Download(urls, qq, item)) threadpool.waitForDone()
class Window(QMainWindow, ClipableObserver): def __init__(self, brres_files=[]): super().__init__() self.open_files = [] self.brres = None self.image_updater = {} # maps brres to list of subscribers self.cwd = os.getcwd() self.__init_threads() self.locked_files = set( ) # lock files that are pending conversion etc... # AutoFix.get().set_pipe(self) self.__init_UI() self.shell_is_shown = False self.shell = None for file in brres_files: self.open(file.name) AutoFix.get().info('Initialized main window', 5) self.show() def __init_threads(self): AutoFix.get().info('Starting threads...', 5) self.threadpool = QThreadPool() # for multi-threading self.threadpool.setMaxThreadCount(5) self.converter = converter = ConvertManager.get() converter.signals.on_conversion_finish.connect( self.on_conversion_finish) self.image_manager = image_manager = ImageManager.get() if image_manager.enabled: image_manager.signals.on_image_update.connect(self.on_image_update) self.threadpool.start(image_manager) else: AutoFix.get().warn( 'Image Manager disabled, do you have Wiimms SZS Tools installed?' ) self.threadpool.start(converter) log_pipe = LoggerPipe() log_pipe.info_sig.connect(self.info) log_pipe.warn_sig.connect(self.warn) log_pipe.error_sig.connect(self.error) def __init_menus(self): # Files # Exit exit_act = QAction('&Exit', self) exit_act.setShortcut('Ctrl+q') exit_act.setStatusTip('Exit Application') exit_act.triggered.connect(self.close) # Open open_act = QAction('&Open', self) open_act.setShortcut('Ctrl+o') open_act.setStatusTip('Open a Brres file') open_act.triggered.connect(self.open_dialog) # Save save_act = QAction('&Save', self) save_act.setShortcut('Ctrl+s') save_act.setStatusTip('Save file') save_act.triggered.connect(self.save) # Save as save_as = QAction('Save &As', self) save_as.setStatusTip('Save file as') save_as.triggered.connect(self.save_as_dialog) # import import_act = QAction('&Import', self) import_act.setShortcut('Ctrl+i') import_act.setStatusTip('Import file') import_act.triggered.connect(self.import_file_dialog) # export export_act = QAction('&Export', self) export_act.setShortcut('Ctrl+e') export_act.setStatusTip('Export file') export_act.triggered.connect(self.export_file_dialog) # File Menu menu = self.menuBar() fileMenu = menu.addMenu('&File') fileMenu.addAction(open_act) fileMenu.addAction(save_act) fileMenu.addAction(save_as) fileMenu.addSeparator() fileMenu.addAction(import_act) fileMenu.addAction(export_act) fileMenu.addSeparator() fileMenu.addAction(exit_act) # Tools shell_Act = QAction('&Interactive Shell', self) shell_Act.setShortcut('Ctrl+Shift+I') shell_Act.setStatusTip('Run interactive commands') shell_Act.triggered.connect(self.open_interactive_shell) kcl_calc_Act = QAction('&KCL Calculator', self) kcl_calc_Act.setShortcut('Ctrl+k') kcl_calc_Act.setStatusTip('KCL Flag Calculator') kcl_calc_Act.triggered.connect(self.open_kcl_calculator) toolMenu = menu.addMenu('&Tools') toolMenu.addAction(shell_Act) toolMenu.addAction(kcl_calc_Act) # Help report_Act = QAction('&Report Issue', self) report_Act.setStatusTip('Report an issue') report_Act.triggered.connect(self.report_issue) website_Act = QAction('&Website', self) website_Act.setStatusTip('Visit website') website_Act.triggered.connect(self.open_website) about_Act = QAction('&About', self) about_Act.setStatusTip('Information about ABMatt') about_Act.triggered.connect(self.about_abmatt) help_menu = menu.addMenu('&Help') help_menu.addAction(report_Act) help_menu.addAction(website_Act) help_menu.addSeparator() help_menu.addAction(about_Act) def open_website(self): webbrowser.open('https://github.com/Robert-N7/abmatt') def report_issue(self): webbrowser.open('https://github.com/Robert-N7/abmatt/issues') def about_abmatt(self): self.box = QMessageBox() bit_size = '64-Bit' if sys.maxsize > 2**32 else '32-Bit' self.box.setText( f'ABMatt Version {load_config.VERSION} {platform.platform()} {bit_size}' ) self.box.setWindowTitle('ABMatt') self.box.show() def open_kcl_calculator(self): self.calculator = KCLCalculator() def open_interactive_shell(self): if not self.shell_is_shown: if self.shell is None: self.shell = InteractiveCmd() self.left.addWidget(self.shell) else: self.shell.show() self.shell_is_shown = True else: self.shell_is_shown = False self.shell.hide() def locate_material(self, brres_path): return self.material_browser.locate_material(brres_path) def __init_child_UI(self, top_layout): # left vert_widget = QWidget(self) # policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding) # policy.setHorizontalStretch(2) # vert_widget.setSizePolicy(policy) top_layout.addWidget(vert_widget) self.left = vert_layout = QVBoxLayout() vert_widget.setLayout(vert_layout) widget = QWidget(self) vert_layout.addWidget(widget) center_layout = QHBoxLayout() widget.setLayout(center_layout) self.logger = QPlainTextEdit(self) self.logger.setReadOnly(True) self.logger.setFixedHeight(100) vert_layout.addWidget(self.logger) self.treeview = BrresTreeView(self) self.treeview.setMinimumWidth(300) center_layout.addWidget(self.treeview) self.poly_editor = PolyEditor(self) center_layout.addWidget(self.poly_editor) # center_widget.setGeometry(0, 0, 300, 300) # right # top_layout.addSpacing(30) self.material_browser = MaterialTabs(self) policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding) policy.setHorizontalStretch(1) self.material_browser.setSizePolicy(policy) top_layout.addWidget(self.material_browser) # self.material_browser.setFixedWidth(300) def __init_UI(self): self.setWindowTitle('ANoobs Brres Material Tool') self.resize(1000, 700) self.__init_menus() main_layout = QHBoxLayout() self.__init_child_UI(main_layout) widget = QWidget() widget.setLayout(main_layout) self.setCentralWidget(widget) self.statusBar().showMessage('Ready') def on_image_update(self, brres_dir): # simply gives it back to the image manager to update observers self.image_manager.notify_image_observers(*brres_dir) def on_update_polygon(self, poly): enable_edits = poly.parent.parent not in self.locked_files self.poly_editor.on_update_polygon(poly, enable_edits=enable_edits) def emit(self, message, head=None, tail=None): message = message.replace('\n', '<br/>').replace(' ', ' ') if head: message = head + message if tail: message += tail self.logger.appendHtml(message) def get_brres_by_fname(self, fname): path = os.path.abspath(fname) for x in self.open_files: if os.path.abspath(x.name) == path: return x def set_brres(self, brres): if brres != self.brres: self.brres = brres def open_dialog(self): fname, filter = QFileDialog.getOpenFileName(self, 'Open file', self.cwd, "Brres files (*.brres)") if fname: self.open(fname) def on_node_update(self, node): self.treeview.on_brres_update(node) def on_rename_update(self, node, old_name): if type(node) == Brres: self.treeview.on_brres_rename(old_name, node.name) self.material_browser.on_name_update() def on_child_update(self, child): self.treeview.on_brres_update(child.parent) def open(self, fname, force_update=False): self.cwd = os.path.dirname(fname) opened = self.get_brres_by_fname(fname) if opened: if not force_update: return self.set_brres(opened) else: opened = Brres.get_brres(fname) self.open_files.append(opened) opened.register_observer(self) # either it's newly opened or forcing update self.set_brres(opened) self.treeview.on_brres_update(opened) self.material_browser.add_brres_materials_to_scene(opened) def save(self): if len(self.open_files): last = None for x in self.open_files: # A precaution against overwriting old models # overwrite = True if not x.has_new_model else Brres.OVERWRITE if x.save(overwrite=True): last = x if last is not None: self.update_status('Wrote file {}'.format(last.name)) def save_as_dialog(self): if self.brres: fname, filter = QFileDialog.getSaveFileName( self, 'Save as', self.cwd, 'Brres files (*.brres)') if fname: self.cwd = os.path.dirname(fname) self.brres.save(fname, overwrite=True) self.update_status('Wrote file {}'.format(fname)) else: AutoFix.get().error('No Brres file selected.') def import_texture(self, filename): raise NotImplementedError() def on_material_select(self, material): self.material_browser.on_material_select(material) def import_file(self, fname, brres_name=None, brres=None, mdl0=None): if mdl0 is not None: brres = mdl0.parent if not brres: if brres_name is not None: brres = self.get_brres_by_fname(brres_name) elif self.brres and os.path.splitext(os.path.basename(self.brres.name))[0] == \ os.path.splitext(os.path.basename(fname))[0]: brres = self.brres self.cwd, name = os.path.split(fname) base_name, ext = os.path.splitext(name) lower = ext.lower() if lower == '.dae': converter = DaeConverter2(brres, fname, mdl0=mdl0) elif lower == '.obj': converter = ObjConverter(brres, fname, mdl0=mdl0) # elif lower in ('.png', '.jpg', '.bmp', '.tga'): # return self.import_texture(fname) else: self.statusBar().showMessage('Unknown extension {}'.format(ext)) return # converter.load_model() # self.on_conversion_finish(converter) self.update_status('Added {} to queue...'.format(fname)) self.lock_file(converter.brres) self.converter.enqueue(converter) def lock_file(self, brres): self.locked_files.add(brres) self.poly_editor.on_brres_lock(brres) def unlock_file(self, brres): try: self.locked_files.remove(brres) except KeyError: pass self.poly_editor.on_brres_unlock(brres) def on_conversion_finish(self, converter): self.open(converter.brres.name, force_update=True) self.unlock_file(converter.brres) self.update_status('Finished Converting {}'.format( converter.brres.name)) def import_file_dialog(self, brres=None, mdl0=None): fname, filter = QFileDialog.getOpenFileName(self, 'Import model', self.cwd, '(*.dae *.obj)') if fname: self.import_file(fname, brres=brres, mdl0=mdl0) def export_file_dialog(self, brres=None, mdl0=None): if mdl0 is not None: brres = mdl0.parent multiple_models = False if not brres: brres = self.brres if not brres: AutoFix.get().error('Nothing to export!') return elif mdl0 is None: if len(brres.models) > 1: multiple_models = True fname, fil = QFileDialog.getSaveFileName(self, 'Export model', self.cwd, 'Model files (*.dae *.obj)') if fname: self.cwd, name = os.path.split(fname) base_name, ext = os.path.splitext(name) lower = ext.lower() if lower == '.obj': klass = ObjConverter elif lower == '.dae': klass = DaeConverter2 else: self.statusBar().showMessage( 'Unknown extension {}'.format(ext)) return if multiple_models: for x in brres.models: export_name = os.path.join(self.cwd, base_name + '-' + x.name + ext) converter = klass(brres, export_name, encode=False, mdl0=x) self.update_status('Added {} to queue...'.format(x.name)) self.converter.enqueue(converter) else: if mdl0 is None: mdl0 = brres.models[0] converter = klass(brres, fname, encode=False, mdl0=mdl0) self.update_status('Added {} to queue...'.format(mdl0.name)) self.converter.enqueue(converter) def close_file(self, brres=None): if brres is None: brres = self.brres if brres.is_modified: m = QMessageBox(QMessageBox.Warning, 'Save Before Closing', f'Save {brres.name} before closing?', buttons=QMessageBox.Yes | QMessageBox.No, parent=self) if m.exec_() == QMessageBox.Yes: brres.save(overwrite=True) if brres in self.open_files: self.open_files.remove(brres) brres.unregister(self) brres.close(try_save=False) self.brres = None self.poly_editor.on_brres_lock(brres) self.treeview.on_file_close(brres) def shouldExitAnyway(self, result): return result == QMessageBox.Ok def closeEvent(self, event): self.material_browser.on_close() files_to_save = [] for x in self.open_files: if x.is_modified: files_to_save.append(x) if Brres.OVERWRITE: for x in files_to_save: x.close() elif files_to_save: self.files_to_save = files_to_save fnames = ', '.join([x.name for x in files_to_save]) m = QMessageBox(QMessageBox.Warning, 'Confirm Exit?', f'Exit without saving {fnames}?', buttons=QMessageBox.Yes | QMessageBox.No, parent=self) if m.exec_() == QMessageBox.No: event.ignore() return for x in files_to_save: x.close() self.threadpool.clear() self.image_manager.stop() self.converter.stop() event.accept() def info(self, message): self.emit(message, '<p style="color:Blue;">', '</p>') def warn(self, message): self.emit(message, '<p style="color:Red;">', '</p>') def error(self, message): self.emit(message, '<p style="color:Red;">', '</p>') def update_status(self, message): self.statusBar().showMessage(message)
class GuiBehavior: def __init__(self, gui): self.filter_thread = QThreadPool() self.download_thread = QThreadPool() self.download_thread.setMaxThreadCount( 1) # Limits concurrent downloads to 1. self.download_workers = [] self.gui = gui self.handle_init() def handle_init(self): ''' Load cached downloads. Create file in case it does not exist. ''' try: with open(absp('app/cache'), 'rb') as f: self.cached_downloads = pickle.load(f) for download in self.cached_downloads: self.gui.links = download[0] self.add_links(True, download) except EOFError: self.cached_downloads = [] logging.debug('No cached downloads.') except FileNotFoundError: self.cached_downloads = [] create_file('app/cache') ''' Load settings. Create file in case it doesn't exist. ''' try: with open(absp('app/settings'), 'rb') as f: self.settings = pickle.load(f) except EOFError: self.settings = None logging.debug('No settings found.') except FileNotFoundError: self.settings = None create_file('app/settings') def resume_download(self): ''' Resume selected downloads. ''' selected_rows = check_selection(self.gui.table) if selected_rows: for i in selected_rows: if i < len(self.download_workers): self.download_workers[i].resume() def stop_download(self): ''' Stop selected downloads. ''' selected_rows = check_selection(self.gui.table) if selected_rows: for i in reversed(selected_rows): if i < len(self.download_workers): self.download_workers[i].stop(i) self.download_workers.remove(self.download_workers[i]) def pause_download(self): ''' Pause selected downloads. ''' selected_rows = check_selection(self.gui.table) if selected_rows: for i in selected_rows: if i < len(self.download_workers): self.download_workers[i].signals.update_signal.emit( self.download_workers[i].data, [None, None, 'Paused', '0 B/s']) self.download_workers[i].pause() def add_links(self, state, cached_download=''): ''' Calls FilterWorker() ''' worker = FilterWorker( self, cached_download, (self.gui.password.text() if not cached_download else '')) worker.signals.download_signal.connect(self.download_receive_signal) worker.signals.alert_signal.connect(alert) self.filter_thread.start(worker) def download_receive_signal(self, row, link, append_row=True, dl_name='', progress=0): ''' Append download to row and start download. ''' if append_row: self.gui.table_model.appendRow(row) index = self.gui.table_model.index( self.gui.table_model.rowCount() - 1, 4) progress_bar = QProgressBar() progress_bar.setValue(progress) self.gui.table.setIndexWidget(index, progress_bar) row[4] = progress_bar worker = DownloadWorker(link, self.gui.table_model, row, self.settings, dl_name) worker.signals.update_signal.connect(self.update_receive_signal) worker.signals.unpause_signal.connect(self.download_receive_signal) self.download_thread.start(worker) self.download_workers.append(worker) def update_receive_signal(self, data, items): ''' Update download data. items = [File Name, Size, Down Speed, Progress, Pass] ''' if data: if not PyQt5.sip.isdeleted(data[2]): for i in range(len(items)): if items[i] and isinstance(items[i], str): data[i].setText(items[i]) if items[i] and not isinstance(items[i], str): data[i].setValue(items[i]) def set_dl_directory(self): file_dialog = QFileDialog(self.gui.settings) file_dialog.setFileMode(QFileDialog.Directory) file_dialog.exec_() self.gui.dl_directory_input.setText(file_dialog.selectedFiles()[0]) def change_theme(self, theme=None): ''' Change app palette (theme). 0 = Light 1 = Dark ''' if theme: self.gui.theme_select.setCurrentIndex(theme) if self.gui.theme_select.currentIndex() == 0: self.gui.app.setPalette(self.gui.main.style().standardPalette()) elif self.gui.theme_select.currentIndex() == 1: self.gui.app.setPalette(dark_theme) def save_settings(self): with open(absp('app/settings'), 'wb') as f: settings = [] settings.append( self.gui.dl_directory_input.text()) # Download Directory - 0 settings.append( self.gui.theme_select.currentIndex()) # Theme - 1 settings.append( self.gui.timeout_input.value()) # Timeout - 2 settings.append( self.gui.proxy_settings_input.text()) # Proxy Settings - 3 pickle.dump(settings, f) self.settings = settings self.gui.settings.hide() def select_settings(self): ''' Select settings page. ''' selection = self.gui.settings_list.selectedIndexes()[0].row() self.gui.stacked_settings.setCurrentIndex(selection) def handle_exit(self): ''' Save cached downloads data. ''' active_downloads = [] for w in self.download_workers: download = w.return_data() if download: active_downloads.append(download) active_downloads.extend(self.cached_downloads) with open(absp('app/cache'), 'wb') as f: if active_downloads: pickle.dump(active_downloads, f) os._exit(1)
class DataProcess(DataGUI): # Main GUI for data module sdk_level_signal = QtCore.pyqtSignal(list) authority_signal = QtCore.pyqtSignal(list) type_signal = QtCore.pyqtSignal(list) add_apk_signal = pyqtSignal(int, int, int) add_progress_signal = pyqtSignal(float) market_signal = pyqtSignal(list) app_signal = pyqtSignal(list) update_signal = pyqtSignal(list) update_information_signal = pyqtSignal(list) delete_apk_signal = pyqtSignal() delete_progress_signal = pyqtSignal(float) error_signal = pyqtSignal(str) market_model = None app_model = None update_model = None search_app_thread = None search_platform_thread = None search_update_thread = None search_update_info_thread = None inbox_update_id_list = [] def __init__(self): super(DataProcess, self).__init__() self.thread_pool = QThreadPool() self.thread_pool.globalInstance() self.thread_pool.setMaxThreadCount(8) self.bind_error() self.load_data() self.bind_add_apk() self.bind_search() self.bind_delete() self.check_value() """ 加载ComboBox数据 """ def load_data(self): # sdk level combobox sdk_thread = SDKLevelThread() sdk_thread.transfer(self) self.sdk_level_signal.connect(self.update_sdk) self.thread_pool.start(sdk_thread) # authority combobox authority_thread = AuthorityThread() authority_thread.transfer(self) self.authority_signal.connect(self.update_authority) self.thread_pool.start(authority_thread) # type combobox type_thread = TypeThread() type_thread.transfer(self) self.type_signal.connect(self.update_type) self.thread_pool.start(type_thread) def update_sdk(self, sdk_list): sdk_list = ["UNKNOWN"] + sdk_list self.sdk_list = sdk_list self.sdk_combobox.addItems(sdk_list) def update_authority(self, authority_list): if not authority_list: return authority_index_list, authority_name_list = zip(*authority_list) authority_name_list = list(authority_name_list) self.authority_id_list = list(authority_index_list) self.authority_combobox.addItems(authority_name_list) def update_type(self, type_list): if not type_list: return type_index_list, type_name_list = zip(*type_list) self.type_id_list = list(type_index_list) self.type_combobox.addItems(type_name_list) """ 查找 """ def bind_search(self): self.search_button.clicked.connect(self.search_click) self.market_signal.connect(self.update_market) self.first_file_tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.first_file_tree.clicked.connect(self.first_tree_click) self.app_signal.connect(self.update_app) self.second_file_tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.second_file_tree.clicked.connect(self.second_tree_click) self.update_signal.connect(self.update_update) self.third_file_tree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.third_file_tree.clicked.connect(self.third_tree_click) self.update_information_signal.connect(self.update_information) self.bind_drag_search() def search_click(self): # get the combobox value sdk_name_list = self.sdk_combobox.get_select_text() authority_select_list = self.authority_combobox.get_select_index() app_type_select_list = self.type_combobox.get_select_index() # 选中全部sdk 或 未选中任何sdk时,无需筛选 if len(sdk_name_list) == len(self.sdk_list) or len(sdk_name_list) == 0: sdk_name_list = None self.selected_sdk_name_list = sdk_name_list # 未选中任何authority时,无需筛选 if len(authority_select_list) == 0: authority_id_list = None else: authority_id_list = [ self.authority_id_list[_index_] for _index_ in authority_select_list ] self.selected_authority_id_list = authority_id_list # 未选中任何type 或 选中全部type时, 无需筛选 if len(app_type_select_list) == 0 or len(app_type_select_list) == len( self.type_id_list): type_id_list = None else: type_id_list = [ self.type_id_list[_index_] for _index_ in app_type_select_list ] self.selected_type_id_list = type_id_list search_platform_thread = SearchPlatformThread() search_platform_thread.transfer(self, sdk_name_list, authority_id_list, type_id_list) if self.search_platform_thread: # 取消上一个请求 try: self.thread_pool.cancel(self.search_platform_thread) except RuntimeError: pass self.search_platform_thread = search_platform_thread self.thread_pool.start(search_platform_thread) def update_market(self, market_list): platform_model = QStandardItemModel() icon_path = os.path.join(__current_folder_path__, "./images/folder.png") for market in market_list: platform_model.appendRow( QStandardItem(QIcon(icon_path), market['market_name'])) self.market_list = market_list if self.market_model: self.market_model.deleteLater() self.market_model = platform_model self.first_file_tree.setModel(platform_model) self.first_file_tree.scrollTo(platform_model.index(0, 0)) # clear the second tree app_model = QStandardItemModel() self.app_list = [] if self.app_model: self.app_model.deleteLater() self.app_model = app_model self.second_file_tree.setModel(app_model) # clear the third tree update_model = QStandardItemModel() self.update_list = [] if self.update_model: self.update_model.deleteLater() self.update_model = update_model self.third_file_tree.setModel(update_model) # clear the information box self.clear_apk_info_layout() self.inbox_update_id_list = [] # not find any data if len(market_list) == 0: QMessageBox().warning(self, "Not Found", "Not found any apk in database.", QMessageBox.Ok) def first_tree_click(self): current_row_index = self.first_file_tree.currentIndex().row() search_app_thread = SearchAppThread() search_app_thread.transfer( self, self.market_list[current_row_index]['market_id'], self.selected_sdk_name_list, self.selected_authority_id_list, self.selected_type_id_list) if self.search_app_thread: # 取消上一个请求 try: self.thread_pool.cancel(self.search_app_thread) except RuntimeError: pass self.search_app_thread = search_app_thread self.thread_pool.start(search_app_thread) def update_app(self, app_list): app_model = QStandardItemModel() icon_path = os.path.join(__current_folder_path__, "./images/android.png") for app in app_list: app_model.appendRow( QStandardItem(QIcon(icon_path), app['app_title'])) self.app_list = app_list if self.app_model: self.app_model.deleteLater() self.app_model = app_model self.second_file_tree.setModel(app_model) self.second_file_tree.scrollTo(app_model.index(0, 0)) def second_tree_click(self): current_row_index = self.second_file_tree.currentIndex().row() search_update_thread = SearchUpdateThread() search_update_thread.transfer( self, self.app_list[current_row_index]['app_id'], self.selected_sdk_name_list, self.selected_authority_id_list, self.selected_type_id_list) if self.search_update_thread: # 取消上一个请求 try: self.thread_pool.cancel(self.search_update_thread) except RuntimeError: pass self.search_update_thread = search_update_thread self.thread_pool.start(search_update_thread) def update_update(self, update_list): update_model = QStandardItemModel() icon_path = os.path.join(__current_folder_path__, "./images/version.png") for update in update_list: version = update['version'].split( '.apk')[0] if update['version'].endswith( '.apk') else update['version'] version = update['version'].split( '.xapk')[0] if update['version'].endswith('.xapk') else version update_model.appendRow(QStandardItem(QIcon(icon_path), version)) self.update_list = update_list if self.update_model: self.update_model.deleteLater() self.update_model = update_model self.third_file_tree.setModel(update_model) self.third_file_tree.scrollTo(update_model.index(0, 0)) def third_tree_click(self): current_third_tree_row_index = self.third_file_tree.currentIndex().row( ) search_apk_info_by_update_id_thread = SearchApkInfoByUpdateIdThread() search_apk_info_by_update_id_thread.transfer( self, self.update_list[current_third_tree_row_index]['update_id']) if self.search_update_info_thread: # 取消上一个请求 try: self.thread_pool.cancel(self.search_update_info_thread) except RuntimeError: pass self.search_update_info_thread = search_apk_info_by_update_id_thread self.thread_pool.start(search_apk_info_by_update_id_thread) # clear the information box self.clear_apk_info_layout() self.inbox_update_id_list = [] def update_information(self, information_list): self.clear_apk_info_layout() inbox_update_id_list = [] for information in information_list: # add the new information widget information['market'] = information['market_name'] update_folder = get_app_folder(information) image_file_list = glob.glob(os.path.join(update_folder, "*.jpg")) description_file = os.path.join(update_folder, "description.txt") if os.path.exists(description_file): with open(description_file, 'r') as _file_: description = _file_.read().strip() if description != "": information['description'] = description information['image_file_list'] = image_file_list new_information_widget = InformationWidget() new_information_widget.load_data(information) self.apk_info_layout.addWidget(new_information_widget) inbox_update_id_list.append(information['update_id']) self.inbox_update_id_list = inbox_update_id_list """ 拖动查找 """ def bind_drag_search(self): self.apk_info_scroll_area.file_signal.connect(self.drag_search) def drag_search(self, file_url): drag_search_thread = DragSearchThread() drag_search_thread.transfer(self, file_url) if self.search_app_thread: # 取消上一个进程 try: self.thread_pool.cancel(self.search_app_thread) except RuntimeError: pass self.search_app_thread = drag_search_thread self.thread_pool.start(drag_search_thread) # uncheck the third tree row_index = self.third_file_tree.currentIndex().row() if row_index != -1: self.third_file_tree.setCurrentIndex( self.update_model.index(-1, -1)) # clear the information box self.clear_apk_info_layout() self.inbox_update_id_list = [] """ 删除 """ def bind_delete(self): self.delete_apk_button.clicked.connect(self.delete_apk_button_click) self.delete_apk_signal.connect(self.delete_apk_success) self.delete_from_folder_button.clicked.connect( self.delete_from_folder_button_click) def delete_apk_button_click(self): if not self.inbox_update_id_list: return # clear the information box self.clear_apk_info_layout() inbox_update_id_list = self.inbox_update_id_list self.inbox_update_id_list = [] # check the third file system tree in_third_tree = False reserved_update_list = [] # type: List[Dict] for update in self.update_list: if update['update_id'] in inbox_update_id_list: in_third_tree = True else: reserved_update_list.append(update) if in_third_tree: new_update_model = QStandardItemModel() icon_path = os.path.join(__current_folder_path__, "./images/version.png") for update in reserved_update_list: version = update['version'].split( '.apk')[0] if update['version'].endswith( '.apk') else update['version'] version = update['version'].split( '.xapk')[0] if update['version'].endswith( '.xapk') else version new_update_model.appendRow( QStandardItem(QIcon(icon_path), version)) self.update_list = reserved_update_list if self.update_model: self.update_model.deleteLater() self.update_model = new_update_model self.third_file_tree.setModel(new_update_model) self.third_file_tree.scrollTo(new_update_model.index(0, 0)) # start the delete thread delete_apk_thread = DeleteApkThread() delete_apk_thread.transfer(self, inbox_update_id_list) self.thread_pool.start(delete_apk_thread) def delete_apk_success(self): QMessageBox.information(self, "Delete successfully", "Successfully delete APK(s).", QMessageBox.Yes) self.delete_from_folder_button.setVisible(True) self.delete_progress_bar.setValue(0) self.delete_progress_bar.setVisible(False) def delete_from_folder_button_click(self): dir_choose = QFileDialog.getExistingDirectory( self, "Choose APK Directory", os.path.join(__current_folder_path__, "../../")) if not dir_choose: return user_choose = QMessageBox.question( self, "Delete Confirm", "Do confirm to delete folder '{}'?.".format(dir_choose), QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel) if user_choose == QMessageBox.Cancel: return # set the layout self.delete_from_folder_button.setVisible(False) self.delete_progress_bar.setValue(0) self.delete_progress_bar.setVisible(True) # start the delete thread delete_thread = MultiDeleteThread() delete_thread.transfer(self, dir_choose) self.thread_pool.start(delete_thread) """ 错误 """ def bind_error(self): self.error_signal.connect(self.catch_error) def catch_error(self, _err_: str): log_file = os.path.join( __current_folder_path__, "../../log/main_gui.{}.log".format( datetime.datetime.now().strftime("%Y-%m-%d-%H"))) with open(log_file, 'a') as _file_: _file_.write(_err_) def check_value(self): enviro = True if python_interface is None: QMessageBox.warning( self, "Python Interface Error", "Please set the 'python_interface' in setting.py.", QMessageBox.Ok, QMessageBox.Ok) enviro = False return enviro """ 批量添加APK """ def bind_add_apk(self): self.add_progress_signal.connect(self.add_apk_progress_bar.setValue) self.add_apk_signal.connect(self.add_apk_success) self.add_apk_button.clicked.connect(self.add_apk_button_click) def add_apk_button_click(self): dir_choose = QFileDialog.getExistingDirectory( self, "Choose APK Directory", os.path.join(__current_folder_path__, "../../")) if not dir_choose: return self.add_apk_button.setVisible(False) self.add_apk_progress_bar.setValue(0) self.add_apk_progress_bar.setVisible(True) add_apk_thread = AddAPKThread() add_apk_thread.transfer(self, dir_choose) self.thread_pool.start(add_apk_thread) def add_apk_success(self, success_number, repeated_number, error_number): QMessageBox.information( self, "Add APK", "Successfully add APKs. {} success, {} Repeated and {} error.". format(success_number, repeated_number, error_number), QMessageBox.Ok, QMessageBox.Ok) self.add_apk_button.setVisible(True) self.add_apk_progress_bar.setVisible(False) self.add_apk_progress_bar.setValue(0)
class TrainingWindow(Window): """Abstraction for training view based on Window class""" def __init__(self, window_path: str) -> None: super().__init__(window_path) sys.stdout = EmittingStream( textWritten=self._add_info) # textWritten works this way self.lbl_cancel.mouseReleaseEvent = self._cancel_training # thread_pool and ml_worker setup. A training view needs 2 threads to work properly self._thread_pool = QThreadPool() self._thread_pool.setMaxThreadCount(2) self._ml_worker = LongWorker() self._ml_worker.set_params(self._train_model) self._ml_worker.signals.program_finished.connect(self.next) self._ml_worker.signals.program_error.connect(self.handle_error) self._ml_worker.signals.result.connect(self._add_info) self._ml_worker.setAutoDelete(True) self._thread_pool.start(self._ml_worker, priority=1) def close_window(self) -> None: super(TrainingWindow, self).close_window() widget.close() def next(self) -> None: QThread.sleep(1) next_form = FinalResultWindow() widget.addWidget(next_form) widget.removeWidget(widget.currentWidget()) widget.setCurrentIndex(widget.currentIndex()) @abstractmethod def _train_model(self) -> None: pass def last_warning_pop_up(self) -> bool: pop_up: PopUp = WarningPopUp() title = "Cancelar entrenamiento" body = "¿Estas seguro que deseas cancelar el entrenamiento?" additional = "La aplicación se cerrará para evitar conflictos con las variables utilizadas hasta el momento." answer = pop_up.open_pop_up(title, body, additional) return answer def _cancel_training(self, event) -> None: """Show a Warning pop up and then if user wants to finished the app, close it""" event.accept() want_to_stop_training = self.last_warning_pop_up() if want_to_stop_training: self._thread_pool.cancel(self._ml_worker) self.close_window() def handle_error(self, error) -> None: """Print error message to the QTextEdit""" def write_error(): for i in info: self._add_info(i) QThread.sleep(1) # deactivate lbl press behaviour due to an error lbl_cancel_style = cancel_buttons_style["Not_available"] self.lbl_cancel.mouseReleaseEvent = None self.lbl_cancel.setStyleSheet(lbl_cancel_style) info = ("Error\n", str(error), "\nCerrando aplicación para evitar conflictos de memoria" + " ", ".", ".", ".") # worker to write info to ted_info temp_worker = LongWorker(func=write_error) temp_worker.signals.program_finished.connect(self.close_window) self._thread_pool.start(temp_worker, priority=2) def _add_info(self, info: str) -> None: """Append text to the QTextEdit.""" cursor = self.ted_info.textCursor() cursor.movePosition(QTextCursor.End) cursor.insertText(info) self.ted_info.setTextCursor(cursor) self.ted_info.ensureCursorVisible()
class FileHandler: """ A class to handle finding/loading/saving to files. So, IO operations. """ # TODO: Implement logging, since returned values from threaded functions are discarded. # Need to know if errors hanppen! def __init__(self, settings='settings.json', profiles='profiles.json'): self.profile_path = profiles self.settings_path = settings self.work_dir = os.getcwd().replace('\\', '/') self.force_save = False self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(1) @staticmethod def find_file(relative_path, exist=True): """ Get absolute path to resource, works for dev and for PyInstaller """ try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = sys._MEIPASS except AttributeError: base_path = os.path.abspath(".") path = os.path.join(base_path, relative_path).replace('\\', '/') if exist: if FileHandler.is_file(path): # print(f'Returning existing path: {path}') return path else: # print(f'No found: {relative_path}') return None else: # print(f'Returning path: {path}') return path @threaded_cooldown def save_settings(self, settings): try: with open(self.settings_path, 'w') as f: json.dump(settings, f, indent=4, sort_keys=True) return True except (OSError, IOError) as e: # TODO: Logging! return False @threaded_cooldown def save_profiles(self, profiles): try: with open(self.profile_path, 'w') as f: json.dump(profiles, f, indent=4, sort_keys=True) return True except (OSError, IOError) as e: # TODO: Logging! return False def load_settings(self, reset=False) -> SettingsClass: """ Reads settings, or writes them if absent, or if instructed to using reset. """ def get_file(path): """ """ if FileHandler.is_file(path): with open(path, 'r') as f: return json.load(f) else: return {} try: profiles = get_file(self.profile_path) except json.decoder.JSONDecodeError as e: raise ProfileLoadError(str(e)) if reset: return SettingsClass(get_base_settings(), profiles, self) else: settings = get_file(self.settings_path) if settings: return SettingsClass(settings, profiles, self) else: return self.load_settings(reset=True) @staticmethod def is_file(path): return os.path.isfile(path) and os.access(path, os.X_OK) def find_exe(self, program): """Used to find executables.""" local_path = os.path.join(self.work_dir, program) if FileHandler.is_file(local_path): # print(f'Returning existing isfile exe: {os.path.join(self.work_dir, program)}') return local_path for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, program) if FileHandler.is_file(exe_file): # print(f'Returning existing exe: {os.path.abspath(exe_file)}') return os.path.abspath(exe_file) # TODO: Check if not covered by path above! # print(f'No found: {program}') return None @staticmethod def read_textfile(path): if FileHandler.is_file(path): try: with open(path, 'r') as f: content = f.read() return content except (OSError, IOError) as e: return None else: return None @threaded def write_textfile(self, path, content): # TODO: Warn user on error. Need smart simple method to send message from threadpool. if FileHandler.is_file(path): try: with open(path, 'w') as f: f.write(content) return True except (OSError, IOError) as e: print('Error! ', e) return False else: print('Error!') return False
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.folderIcon = QIcon(self.style().standardIcon(QStyle.SP_FileDialogNewFolder)) self.folderPix = QPixmap(self.folderIcon.pixmap(QSize(19, 19))) self.pbList = [] self.chList = [] self.lblList = [] self.rmbList = [] #self.newFolders = [] self.initUI() def initUI(self): self.resize(720, 300) 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.codecLabel.setMinimumHeight(13) self.alphaLabel = QLabel('Alpha' , self) self.alphaLabel.setMinimumHeight(13) self.frameRateLabel = QLabel('Frame Rate' , self) self.frameRateLabel.setMinimumHeight(13) 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.setMinimumSize(80,23) self.codecComboBox.addItem("UT Video") self.codecComboBox.activated[str].connect(self.chooseCodec) self.alphaComboBox = QComboBox(self) self.alphaComboBox.setMinimumSize(80,23) self.alphaComboBox.addItem("No Alpha") self.alphaComboBox.addItem("with Alpha") self.alphaComboBox.activated[str].connect(self.chooseAlpha) self.frameRateComboBox = QComboBox(self) self.frameRateComboBox.setMinimumSize(80,23) self.frameRateComboBox.addItem("23.976") self.frameRateComboBox.addItem("24.00") self.frameRateComboBox.addItem("29.97") self.frameRateComboBox.addItem("30.00") self.frameRateComboBox.setCurrentIndex(1) self.frameRateComboBox.activated[str].connect(self.chooseFrameRate) self.compressButton = QPushButton('Compress', self) self.compressButton.setMinimumSize(80,23) 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.radio1.setMinimumSize(80,25) self.radio2 = QRadioButton('Output file name from File name', self) self.radio2.setMinimumSize(80,25) 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.setMinimumHeight(3) 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.setVerticalSpacing(11) self.gridLayoutProgress.setHorizontalSpacing(6) self.gridLayoutProgress.setSizeConstraint(QLayout.SetNoConstraint) self.removeGroupBox = QButtonGroup(self) self.addProgressBarUI(self.inList) self.removeGroupBox.buttonClicked[int].connect(self.removeButtonClicked) #ADD MORE AREA self.gridLayoutAddMore = QGridLayout() self.gridLayoutAddMore.setContentsMargins(0, 0, 0, 0) self.dragAndDropLabel_1 = QLabel("Drag and Drop folders here", self) self.dragAndDropLabel_1.setMinimumSize(QSize(120, 40)) self.dragAndDropLabel_1.setAlignment(Qt.AlignCenter) self.dragAndDropLabel_2 = QLabel("", self) self.dragAndDropLabel_2.setFixedSize(QSize(20, 40)) self.dragAndDropLabel_2.setAlignment(Qt.AlignCenter) self.dragAndDropLabel_2.setPixmap(self.folderPix) sI = QSpacerItem(40, 40,QSizePolicy.Expanding, QSizePolicy.Minimum) sI2 = QSpacerItem(40, 40,QSizePolicy.Expanding, QSizePolicy.Minimum) self.gridLayoutAddMore.addItem(sI, 1, 0, 1, 1) self.gridLayoutAddMore.addWidget(self.dragAndDropLabel_2, 1, 1, 1, 1) self.gridLayoutAddMore.addWidget(self.dragAndDropLabel_1, 1, 2, 1, 1) self.gridLayoutAddMore.addItem(sI2, 1, 3, 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.exploreOutputBtn = QPushButton('Explore Output',self) self.exploreOutputBtn.clicked[bool].connect(self.exploreOutput) self.hideShowLog = QPushButton('Show Log',self) self.hideShowLog.setCheckable(True) self.hideShowLog.clicked[bool].connect(self.showDebugLog) #self.hideShowLog.setMinimumSize(QSize(0, 20)) self.logText = QPlainTextEdit('',self) self.logText.setReadOnly(True) self.logText.hide() self.spacerItem = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gridLayoutDebug.addWidget(self.line_2, 0, 0, 1, 1) self.gridLayoutDebug.addWidget(self.exploreOutputBtn, 1, 0, 1, 1) self.gridLayoutDebug.addWidget(self.hideShowLog, 2, 0, 1, 1) self.gridLayoutDebug.addWidget(self.logText, 3, 0, 1, 1) self.gridLayoutDebug.addItem(self.spacerItem, 4, 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.setMinimumSize(self.size()) self.threadpool = QThreadPool() self.threadpool.setMaxThreadCount(1) print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) ''' Progress bar populate function ''' def addProgressBarUI(self, arr): pbCount = len(self.pbList) for i in range(len(arr)): tempCheckBox = QCheckBox(self) tempCheckBox.setChecked(True) tempCheckBox.setMinimumSize(14,21) tempRemoveButton = QPushButton(self) tempRemoveButton.setIcon(self.removeIcon) tempRemoveButton.setFlat(True) tempRemoveButton.setIconSize(QSize(19,19)) tempRemoveButton.setFixedSize(QSize(19,21)) tempPB = QProgressBar(self) tempPB.setMinimumSize(50,21) tempPB.setMinimum(0) tempPB.setMaximum(100) tempPB.setTextVisible(True) tempPB.setFormat(str(arr[i])+" %p%") tempPB.setAlignment(Qt.AlignCenter) tempPB.setValue(0) if i==0: self.defaulStyle = tempPB.styleSheet() tempStatusLabel = QLabel(self) tempStatusLabel.setPixmap(self.okPix) tempStatusLabel.setMinimumSize(13,21) self.gridLayoutProgress.addWidget(tempCheckBox, pbCount+i, 0, 1, 1) self.gridLayoutProgress.addWidget(tempPB, pbCount+i, 1, 1, 1) self.gridLayoutProgress.addWidget(tempStatusLabel, pbCount+i, 2, 1, 1) self.gridLayoutProgress.addWidget(tempRemoveButton, pbCount+i, 3, 1, 1) self.removeGroupBox.addButton(tempRemoveButton, pbCount+i) self.pbList.append(tempPB) self.chList.append(tempCheckBox) self.lblList.append(tempStatusLabel) self.rmbList.append(tempRemoveButton) ''' 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() def addNewFolders(self, newFolders): self.addProgressBarUI(newFolders) self.setMinimumHeight(self.height()+len(newFolders)*32) #self.resize(self.width(),self.height()+200) self.update() self.adjustSize() self.setMinimumSize(self.size()) ''' 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() ''' print(self.gridLayoutProgress.rowCount()) self.pbList[i].setParent(None) self.chList[i].setChecked(False) self.chList[i].setParent(None) self.lblList[i].setParent(None) self.rmbList[i].setParent(None) 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]) self.gridLayoutProgress.invalidate() self.gridLayoutProgress = QGridLayout() self.gridLayoutProgress.setVerticalSpacing(11) self.gridLayoutProgress.setHorizontalSpacing(6) self.gridLayoutProgress.setSizeConstraint(QLayout.SetDefaultConstraint) self.verticalLayout.insertLayout(1,self.gridLayoutProgress) print(self.gridLayoutProgress.rowCount()) ''' 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) #clear the gridLayout for j in reversed(range(len(self.pbList))): self.pbList[j].setParent(None) self.chList[j].setParent(None) self.lblList[j].setParent(None) self.rmbList[j].setParent(None) #reorder the gridLayout for j in range(len(self.pbList)): self.gridLayoutProgress.addWidget(self.chList[j], j, 0, 1, 1) self.gridLayoutProgress.addWidget(self.pbList[j], j, 1, 1, 1) self.gridLayoutProgress.addWidget(self.lblList[j], j, 2, 1, 1) self.gridLayoutProgress.addWidget(self.rmbList[j], j, 3, 1, 1) self.removeGroupBox.setId(self.rmbList[j],j) self.setMinimumHeight(self.height()-30) #self.setMinimumHeight(100) self.adjustSize() self.setMinimumSize(self.size()) print(self.gridLayoutProgress.rowCount()) #self.correctSize() ''' 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) ''' def correctSize(self): self.logText.show() self.gridLayoutDebug.removeItem(self.spacerItem) self.adjustSize() self.setMinimumSize(self.size()) self.repaint() self.gridLayoutDebug.addItem(self.spacerItem) self.logText.hide() self.setMinimumHeight(100) self.adjustSize() self.setMinimumSize(self.size()) def showDebugLog(self, bol): if bol: self.logText.show() self.hideShowLog.setText('Hide Log') self.gridLayoutDebug.removeItem(self.spacerItem) self.adjustSize() #self.resize(self.width(), self.height()+80) self.setMinimumSize(self.size()) else: self.gridLayoutDebug.addItem(self.spacerItem) self.logText.hide() self.hideShowLog.setText('Show Log') self.setMinimumHeight(100) self.adjustSize() self.setMinimumSize(self.size()) #self.resize(self.width(), 100) #self.setGeometry(0,0,self.width(),100) ''' 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}") self.pbList[r[2]].setValue(100) 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.logText.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.logText.appendPlainText) # Execute self.threadpool.start(worker) def exploreOutput(self): FILEBROWSER_PATH = os.path.join(os.getenv('WINDIR'), 'explorer.exe') explorePath = os.path.normpath('C:\\ExportedMOVs') subprocess.run([FILEBROWSER_PATH, explorePath])