class SearchUserQComboBox(QComboBox): def __init__(self): super().__init__() self.setEditable(True) self.results: List[MixcloudUser] = [] self.selected_result: Any[MixcloudUser, None] = None self.search_artist_thread = SearchArtistThread() # Connections self._connect_with_delay( input=self.lineEdit().textEdited, slot=self.get_suggestions, ) self.currentIndexChanged.connect( lambda user: self.set_selected_result(index=self.currentIndex())) self.search_artist_thread.new_result.connect(self.add_result) self.search_artist_thread.error_signal.connect(self.show_error) def _connect_with_delay(self, input: Callable, slot: Slot, delay_ms: int = 750): """Connects a given input to a given Slot with a given delay.""" self.timer = QTimer() self.timer.setInterval(delay_ms) self.timer.setSingleShot(True) self.timer.timeout.connect(slot) input.connect(self.timer.start) @Slot() def get_suggestions(self) -> None: phrase = self.currentText() if phrase: self.clear() self.results.clear() self.search_artist_thread.phrase = phrase self.search_artist_thread.start() @Slot() def show_error(self, msg: str): ErrorDialog(self.parent(), message=msg) @Slot() def add_result(self, item: MixcloudUser): self.results.append(item) if len(self.results) == 1: self.set_selected_result(index=0) self.addItem(f'{item.name} ({item.username})') @Slot(MixcloudUser) def set_selected_result(self, index: int): self.selected_result = self.results[index]
def queue_text_change(ui, text: str) -> None: global text_timer if text_timer: text_timer.stop() text_timer = QTimer() text_timer.setSingleShot(True) text_timer.timeout.connect(partial(update_button_text, ui, text)) text_timer.start(500)
class RefreshOrLogoutThread(QThread): def __init__(self, identity_service, token, is_refresh, timeout, parent=None): QThread.__init__(self, parent) self.__token = token self.__is_refresh = is_refresh self.__timeout = timeout self.__identity_service = identity_service def run(self): print("Start thread") self.__refresh_timer = QTimer() callback = self.__refresh_callback if self.__is_refresh else self.__logout_callback self.__refresh_timer.timeout.connect(callback) self.__refresh_timer.setSingleShot(True) self.__refresh_timer.start(self.__timeout) self.exec_() self.__stop_timer() print("End thread") def __refresh_callback(self): try: print("Refresh callback") api_url = AppConfig.instance().config['api_url'] rf_token = self.__token['refresh_token'] (status, resp) = fqcs_api.refresh_token(api_url, rf_token) if (status == True): self.__identity_service.save_token_json(resp) self.__identity_service.check_token() else: raise Exception("Resp error") except Exception as ex: print("Error refresh callback") self.__identity_service.log_out() finally: self.__stop_timer() self.quit() return def __logout_callback(self): try: self.__identity_service.log_out() except: print("Log out error") finally: self.__stop_timer() self.quit() return def __stop_timer(self): self.__refresh_timer.stop()
class ImageAcquisition(QObject): signal = Signal(list) __instance = None @staticmethod def getInstance(): """ Static access method. """ if ImageAcquisition.__instance == None: ImageAcquisition() return ImageAcquisition.__instance def __init__(self, parent=None, camera_id=0, resolution_x=640, resolution_y=480, time_in_ms=200): QObject.__init__(self, parent) # cv2.CAP_DSHOW to ensure compatibility with Windows and DirectShow if platform.system() == "Windows": self.cap = cv2.VideoCapture(cv2.CAP_DSHOW + camera_id) else: self.cap = cv2.VideoCapture(camera_id) self.img_timer = QTimer(self) self.img_timer.setSingleShot(False) self.img_timer.timeout.connect(self.read_frame) self.img_timer.start(time_in_ms) self._x = resolution_x self._y = resolution_y self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self._x) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._y) if ImageAcquisition.__instance is not None: raise Exception("This class is a singleton!") else: ImageAcquisition.__instance = self @property def x(self): return self._x @property def y(self): return self._y def read_frame(self): ret, frame = self.cap.read() self.signal.emit(frame) return frame def __del__(self): self.cap.release() print('ImageAcquisition closed')
def queue_command(self, name, command): timer = self.timers.get(name, None) if timer is None: timer = QTimer() timer.setInterval(200) timer.setSingleShot(True) timer.timeout.connect(self.timeout) timer.start() setattr(timer, 'command', command) self.timers[name] = timer
def set_intervals(self, intervals): self.timers.clear() self.intervals = intervals for val in intervals: timer = QTimer(self) timer.setSingleShot(True) timer.setInterval(val * 1000) timer.setTimerType(Qt.PreciseTimer) timer.timeout.connect(self.target) if (val == intervals[-1]): timer.timeout.connect(self.callback) self.timers.append(timer)
def event_loop(msec): """Event loop to show the GUI during a unit test. https://www.qtcentre.org/threads/23541-Displaying-GUI-events-with-QtTest """ loop = QEventLoop() timer = QTimer() timer.timeout.connect(loop.quit) timer.setSingleShot(True) timer.start(msec) loop.exec_()
class MyGlWidget(QGLWidget): "PySideApp uses Qt library to create an opengl context, listen to keyboard events, and clean up" def __init__(self, renderer, glformat, app): "Creates an OpenGL context and a window, and acquires OpenGL resources" super(MyGlWidget, self).__init__(glformat) self.renderer = renderer self.app = app # Use a timer to rerender as fast as possible self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.setInterval(0) self.timer.timeout.connect(self.render_vr) # Accept keyboard events self.setFocusPolicy(Qt.StrongFocus) def __enter__(self): "setup for RAII using 'with' keyword" return self def __exit__(self, type_arg, value, traceback): "cleanup for RAII using 'with' keyword" self.dispose_gl() def initializeGL(self): if self.renderer is not None: self.renderer.init_gl() self.timer.start() def paintGL(self): "render scene one time" self.renderer.render_scene() self.swapBuffers() # Seems OK even in single-buffer mode def render_vr(self): self.makeCurrent() self.paintGL() self.doneCurrent() self.timer.start() # render again real soon now def disposeGL(self): if self.renderer is not None: self.makeCurrent() self.renderer.dispose_gl() self.doneCurrent() def keyPressEvent(self, event): "press ESCAPE to quit the application" key = event.key() if key == Qt.Key_Escape: self.app.quit()
def _set_up_broker_fields( led: Led, edit: QLineEdit, timer: QTimer, timer_callback: Callable, kafka_obj_type: Type[KafkaInterface], ): led.turn_off() validator = BrokerAndTopicValidator() edit.setValidator(validator) validator.is_valid.connect(partial(validate_line_edit, edit)) edit.textChanged.connect(lambda: timer.start(1000)) timer.setSingleShot(True) timer.timeout.connect(partial(timer_callback, kafka_obj_type))
class TestThread(QObject): data = Signal(dict) def __init__(self,timer,name,ptnum,amplIncr,rampPts,parent=None): QObject.__init__(self,parent) self.pointNum = ptnum self.rampPoints = rampPts self.counterIncr = 1 self.amplIncr = amplIncr self.name = name self.amplCounter = 1 self.timer = timer self.internalTimer = None @Slot() def startTimer(self): self.internalTimer = QTimer(self) self.internalTimer.timeout.connect(self.calcData) self.internalTimer.setSingleShot(False) self.internalTimer.start(self.timer) def calcData(self): dataX = np.arange(0,self.pointNum)*2*np.pi/self.pointNum dataY = np.sin(dataX)*(self.amplIncr*self.amplCounter) toSend = {"name":self.name,"x":dataX,"y":dataY} self.amplCounter += self.counterIncr if self.amplCounter == self.rampPoints or self.amplCounter == -1*self.rampPoints: self.counterIncr *= -1 self.amplCounter += self.counterIncr self.data.emit(toSend) @Slot() def stopTimer(self): if self.internalTimer is not None: self.internalTimer.stop() while self.internalTimer.isActive(): sleep(0.05) self.internalTimer = None
def test_controller_and_worker_better(self): app = QCoreApplication.instance() or QCoreApplication(sys.argv) controller = Controller() controller.worker.finished.connect(QCoreApplication.quit, type=Qt.QueuedConnection) timeout_timer = QTimer(parent=controller) timeout_timer.setInterval(3000) timeout_timer.setSingleShot(True) timeout_timer.timeout.connect(lambda: QCoreApplication.exit(-1)) timeout_timer.start() with patch.object(controller, "on_worker_result") as on_result: controller.start() self.assertEqual(0, app.exec_()) self.assertEqual(20, len(on_result.mock_calls))
class EntropyWidget(QWidget): def __init__(self, parent, view, data): super(EntropyWidget, self).__init__(parent) self.view = view self.data = data self.raw_data = data.file.raw self.block_size = (len(self.raw_data) / 4096) + 1 if self.block_size < 1024: self.block_size = 1024 self.width = int(len(self.raw_data) / self.block_size) self.image = QImage(self.width, 1, QImage.Format_ARGB32) self.image.fill(QColor(0, 0, 0, 0)) self.thread = EntropyThread(self.raw_data, self.image, self.block_size) self.started = False self.timer = QTimer() self.timer.timeout.connect(self.timerEvent) self.timer.setInterval(100) self.timer.setSingleShot(False) self.timer.start() self.setMinimumHeight(UIContext.getScaledWindowSize(32, 32).height()) def paintEvent(self, event): p = QPainter(self) p.drawImage(self.rect(), self.image) p.drawRect(self.rect()) def sizeHint(self): return QSize(640, 32) def timerEvent(self): if not self.started: self.thread.start() self.started = True if self.thread.updated: self.thread.updated = False self.update() def mousePressEvent(self, event): if event.button() != Qt.LeftButton: return frac = float(event.x()) / self.rect().width() offset = int(frac * self.width * self.block_size) self.view.navigateToFileOffset(offset)
def open(self, url, timeout=60): """Wait for download to complete and return result""" loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(loop.quit) self.loadFinished.connect(loop.quit) self.load(QUrl(url)) timer.start(timeout * 1000) loop.exec_() # delay here until download finished if timer.isActive(): # downloaded successfully timer.stop() return self.html() else: # timed out print('Request timed out:', url)
def open(self, url: str, timeout: int = 10): """Wait for download to complete and return result""" loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) # noinspection PyUnresolvedReferences timer.timeout.connect(loop.quit) # noinspection PyUnresolvedReferences self.loadFinished.connect(loop.quit) self.load(QUrl(url)) # noinspection PyArgumentList timer.start(timeout * 1000) loop.exec_() # delay here until download finished if timer.isActive(): # downloaded successfully timer.stop() else: logger.info('Request timed out: %s' % url)
def wait_signal(signal, timeout=5000): """Block loop until signal emitted, or timeout (ms) elapses.""" loop = QEventLoop() signal.connect(loop.quit) yield if timeout: timer = QTimer() timer.setInterval(timeout) timer.setSingleShot(True) timer.timeout.connect(loop.quit) timer.start() else: timer = None loop.exec_() signal.disconnect(loop.quit) if timer and timer.isActive(): timer.stop()
class TestScatter(QObject): data = Signal(dict) def __init__(self,timer,name,xInc,parent=None): QObject.__init__(self,parent) self.xInc = xInc self.currX = 0 self.name = name self.timer = timer self.internalTimer = None @Slot() def startTimer(self): self.internalTimer = QTimer(self) self.internalTimer.timeout.connect(self.calcData) self.internalTimer.setSingleShot(False) self.internalTimer.start(self.timer) def calcData(self): dataX = self.currX dataY = np.sin(dataX) self.currX+=self.xInc toSend = {self.name:[dataX,dataY]} self.data.emit(toSend) @Slot() def stopTimer(self): if self.internalTimer is not None: self.internalTimer.stop() while self.internalTimer.isActive(): sleep(0.05) self.internalTimer = None
class IdleDetection(QObject): def __init__(self, parent): super(IdleDetection, self).__init__(parent) self._parent: QWidget = parent # Report user inactivity self._idle_timer = QTimer() self._idle_timer.setSingleShot(True) self._idle_timer.setTimerType(Qt.VeryCoarseTimer) self._idle_timer.setInterval(10000) # Detect inactivity for automatic session save self._idle_timer.timeout.connect(self.set_inactive) self.idle = False self.parent.installEventFilter(self) def is_active(self): return self.idle def set_active(self): self.idle = False self._idle_timer.stop() def set_inactive(self): self.idle = True def eventFilter(self, obj, eve): if eve is None or obj is None: return False if eve.type() == QEvent.KeyPress or \ eve.type() == QEvent.MouseMove or \ eve.type() == QEvent.MouseButtonPress: self.set_active() return False if not self._idle_timer.isActive(): self._idle_timer.start() return False
def wait(): # loop = QEventLoop() # QTimer.singleShot(delay * 1000, loop.quit) # loop.exec_() loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(loop.quit) if append: TIMER_RUNNING.append(timer) LOOP_RUNNING.append(loop) timer.start(delay * 1000) loop.exec_() TIMER_RUNNING.remove(timer) LOOP_RUNNING.remove(loop) else: timer.start(delay * 1000) loop.exec_()
class ViewerApp(QApplication): idle_event = Signal() def __init__(self, version: str): super(ViewerApp, self).__init__(sys.argv) self.setApplicationName(f'{APP_NAME}') self.setApplicationVersion(version) self.setApplicationDisplayName(f'{APP_NAME} v{version}') load_style(self) self.idle_timer = QTimer() self.idle_timer.setSingleShot(True) self.idle_timer.setTimerType(Qt.VeryCoarseTimer) self.idle_timer.setInterval(3 * 60 * 1000) # 3 min until idle self.idle_timer.timeout.connect(self.set_idle) self.installEventFilter(self) self.window = ViewerWindow(self) self.window.show() def eventFilter(self, obj, eve): if eve is None or obj is None: return False if eve.type() == QEvent.KeyPress or \ eve.type() == QEvent.MouseMove or \ eve.type() == QEvent.MouseButtonPress: self.set_active() return False return False def set_active(self): self.idle_timer.start() def set_idle(self): LOGGER.debug('Application is idle.') self.idle_event.emit()
class QW_AnimCntrl(QWidget): updated_cadmshfem = Signal() def __init__(self, fem): super(QW_AnimCntrl, self).__init__() self.fem = fem self.tmr_stepTime = QTimer(self) self.tmr_stepTime.setSingleShot(False) self.tmr_stepTime.timeout.connect(self.stepTime) self.btn_Initialize = QPushButton("initialize") self.btn_Animate = QPushButton("animate") self.btn_Animate.setCheckable(True) self.btn_Animate.toggled.connect(self.btn_Animate_toggled) self.btn_Initialize.pressed.connect(self.btn_Initialize_pressed) self.hl = QHBoxLayout() self.hl.addWidget(self.btn_Initialize) self.hl.addWidget(self.btn_Animate) self.setLayout(self.hl) def btn_Animate_toggled(self): if self.btn_Animate.isChecked(): self.tmr_stepTime.start(30) else: self.tmr_stepTime.stop() def btn_Initialize_pressed(self): self.fem.initialize() self.updated_cadmshfem.emit() def stepTime(self): self.fem.step_time() self.updated_cadmshfem.emit()
class ImageRecorder(QObject): def __init__(self, get_frame_func, subfolder, parent=None): QObject.__init__(self, parent) self.get_frame_func = get_frame_func self.period_timer = QTimer(self) self.duration_timer = QTimer(self) self.subfolder = subfolder # create subfolder if not present if os.path.isdir(subfolder) == False: os.mkdir(subfolder) def start(self, time_duration_in_s, time_period_in_s): self.time_duration_in_s = time_duration_in_s self.time_period_in_s = time_period_in_s self.period_timer.setSingleShot(False) self.period_timer.timeout.connect(self.read_frame) self.period_timer.start(self.time_period_in_s * 1000) self.duration_timer.setSingleShot(True) self.duration_timer.timeout.connect(self.period_timer.stop) self.duration_timer.start(self.time_duration_in_s * 1000) def stop(self): self.period_timer.stop() self.duration_timer.stop() # disconnect all QObject.disconnect(self.period_timer) QObject.disconnect(self.duration_timer) @property def timeout(self): return self.duration_timer.timeout # cyclic started reading of frames def read_frame(self): unique_filename = datetime.now().strftime("%d-%m-%Y_%H-%M-%S") frame = self.get_frame_func() cv2.imwrite(self.subfolder + '/' + unique_filename + '.jpg', frame)
def __init__(self, settings): super().__init__() # Dialog UI self.ui = SearchUI.Ui_SearchUI() self.ui.setupUi(self) # Dialog Variables self.settings = settings self.results = [] # Connect actions # Search Bar timer = QTimer() timer.setSingleShot(True) timer.setInterval(300) timer.timeout.connect(self.actSearch) self.ui.txtSearchBar.textChanged.connect(lambda: timer.start()) # List self.ui.listResults.currentItemChanged.connect(self.actListSelect) self.ui.listResults.itemActivated.connect(self.actListClick)
def beam_vector_changed(self): if self.mode == ViewType.polar: # Polar needs a complete re-draw # Only emit this once every 100 milliseconds or so to avoid # too many updates if the slider widget is being used. if not hasattr(self, '_beam_vec_update_polar_timer'): timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(HexrdConfig().rerender_needed.emit) self._beam_vec_update_polar_timer = timer HexrdConfig().flag_overlay_updates_for_all_materials() self._beam_vec_update_polar_timer.start(100) return # If it isn't polar, only overlay updates are needed if not self.iviewer or not hasattr(self.iviewer, 'instr'): return # Re-draw all overlays from scratch HexrdConfig().clear_overlay_data() bvec = HexrdConfig().instrument_config['beam']['vector'] self.iviewer.instr.beam_vector = (bvec['azimuth'], bvec['polar_angle']) self.update_overlays()
class Win(QMainWindow): def __init__(self, id, time): super(Win, self).__init__() self.regions = [] self.running = False self.layout_id = id self.layout_time = time self.layout_timer = QTimer() self.layout_timer.setSingleShot(True) self.layout_timer.timeout.connect(self.stop) self.widget = QWidget() self.setCentralWidget(self.widget) #---- kong ---- self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint) #self.setAttribute(Qt.WA_TranslucentBackground) #---- def __enter__(self): self.play(self.layout_id) self.layout_timer.setInterval(self.layout_time * 1000) self.layout_timer.start() return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_tb or exc_type or exc_val: pass def play(self, layout_id): path = f'content/{layout_id}.xml' layout = yLayout.get_layout(path) if not layout: print('---- yWin: layout error ----') return False color = layout['bgcolor'] self.setStyleSheet(f'background-color: {color}') #---- kong ---- for region in layout['regions']: region['layout_id'] = layout_id r = yRegion.get_region(region, self.widget) self.regions.append(r) if self.regions: for r in self.regions: r.play_end_signal.connect(self.replay) r.play() #---- return True @Slot() def replay(self): print('---- yWin: replay ----') if self.regions: for r in self.regions: r.play() def stop(self): if self.regions: for r in self.regions: r.stop() self.regions = [] #---- del self.regions[:] self.widget = None #self.widget = QWidget() #self.setCentralWidget(self.widget) print(f'---- yWin: stop to close ----') #---- kong ---- self.close()
print(f'---- yWin: stop to close ----') #---- kong ---- self.close() #---- #================================ # # if __name__ == '__main__': app = QApplication(sys.argv) win = app.desktop().screenGeometry() signal.signal(signal.SIGINT, lambda s, f: app.quit()) r = -1 with Win('2', 10) as w: t = QTimer() t.setSingleShot(True) t.timeout.connect(w.showFullScreen) t.start(1000) w.setGeometry(win) w.show() r = app.exec_() sys.exit(r) # # #
class TafelsMainWindow(QMainWindow, Ui_MainWindow): test_timed_out: bool card_stats: CardStats cards_todo: List[Card] state: GameState test_timer: QTimer question_start_time: float test_answers: Dict[Card, int] def __init__(self): super().__init__() self.setupUi(self) self.state = GameState.SETUP self.hook_events() self.enable_controls() self.question.setAlignment(Qt.AlignRight) self.sound_ok = QSound(":/sound/sound/ok.wav") self.sound_error = QSound(":/sound/sound/error.wav") self.test_timed_out = False self.card_stats = CardStatsLoader.load(self.get_stats_file()) self.apply_selections(SelectionsLoader.load( self.get_selections_file())) print(self.card_stats) def hook_events(self): for pb in self.numpad_controls(): pb.clicked.connect(self.numpad_click) self.pb_clear.clicked.connect(self.clear_answer) self.pb_submit.clicked.connect(self.check_answer) self.pb_stop.clicked.connect(self.stop_all) self.pb_test.clicked.connect(self.start_test) self.pb_practice.clicked.connect(self.start_practice) self.answer.returnPressed.connect(self.check_answer) def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def numpad_controls(self) -> Iterable[QPushButton]: return [ self.pushButton_1, self.pushButton_2, self.pushButton_3, self.pushButton_4, self.pushButton_5, self.pushButton_6, self.pushButton_7, self.pushButton_8, self.pushButton_9, self.pushButton_0 ] def enable_controls(self): for pb in self.numpad_controls(): pb.setEnabled(self.is_running()) self.pb_clear.setEnabled(self.is_running()) self.pb_submit.setEnabled(self.is_running()) self.answer.setEnabled(self.is_running()) self.pb_stop.setEnabled(self.is_running()) self.pb_practice.setEnabled(self.state == GameState.SETUP) self.pb_test.setEnabled(self.state == GameState.SETUP) self.lst_selection.setEnabled(self.state == GameState.SETUP) def is_running(self): return self.state == GameState.TESTING or self.state == GameState.PRACTICE def get_selection(self) -> Iterable[int]: selection = [] for i in range(0, self.lst_selection.count()): item: QListWidgetItem = self.lst_selection.item(i) if item.checkState() == Qt.Checked: selection.append(int(item.text())) return selection def apply_selections(self, selection: Iterable[int]): for i in range(0, self.lst_selection.count()): item: QListWidgetItem = self.lst_selection.item(i) if int(item.text()) in selection: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) @Slot() def clear_answer(self): self.answer.setText("") @Slot() def numpad_click(self): sender = self.sender() self.answer.setText(self.answer.text() + sender.text()) @Slot() def start_test(self): SelectionsLoader.store(self.get_selections_file(), self.get_selection()) self.state = GameState.TESTING self.test_timed_out = False self.enable_controls() self.cards_todo = list( self.card_stats.select_for_test(TEST_SIZE, self.get_selection())) shuffle(self.cards_todo) self.show_question_or_feedback() self.feedback.setText("") self.test_answers = {} self.test_timer = QTimer(self) self.test_timer.timeout.connect(self.test_timeout) self.test_timer.setInterval(TEST_DURATION_MSEC) self.test_timer.setSingleShot(True) self.test_timer.start() self.progressBar.setValue(0) self.progressBar.setMaximum(len(self.cards_todo)) @Slot() def start_practice(self): print("starting practice") SelectionsLoader.store(self.get_selections_file(), self.get_selection()) self.state = GameState.PRACTICE self.test_timed_out = False self.enable_controls() self.cards_todo = list(Card.generate(self.get_selection())) shuffle(self.cards_todo) self.show_question_or_feedback() self.feedback.setText("") self.progressBar.setValue(0) self.progressBar.setMaximum(len(self.cards_todo)) @Slot() def stop_all(self): print("stopping") self.state = GameState.SETUP self.enable_controls() self.progressBar.setValue(0) if self.state == GameState.TESTING: self.test_timer.stop() del self.test_timer self.test_timed_out = False @Slot() def test_timeout(self): self.test_timed_out = True def show_test_results(self): print(self.test_answers) msgBox = QMessageBox() msgBox.setTextFormat(Qt.RichText) msgBox.setText(self.generate_report()) msgBox.exec() def current_card(self): return self.cards_todo[-1] @Slot() def check_answer(self): try: answer = int(self.answer.text()) except ValueError: self.clear_answer() return stop_time = time() if answer == self.current_card().answer(): self.correct_answer(stop_time) else: self.wrong_answer() def correct_answer(self, stop_time): time_delta = stop_time - self.question_start_time print(" %s took %f" % (self.current_card(), time_delta)) self.card_stats.add_correct_answer(self.current_card(), time_delta) self.save_stats() if self.state == GameState.PRACTICE: self.sound_ok.play() self.next_card() elif self.state == GameState.TESTING: self.test_answers[self.current_card()] = int(self.answer.text()) self.next_card() def next_card(self): self.cards_todo.pop() self.progressBar.setValue(1 + self.progressBar.maximum() - len(self.cards_todo)) self.show_question_or_feedback() def wrong_answer(self): self.card_stats.add_error(self.current_card()) print(" %s wrong answer %s" % (str(self.current_card()), self.answer.text())) self.save_stats() if self.state == GameState.PRACTICE: self.sound_error.play() self.style_feedback() self.feedback.setText(" " + self.answer.text() + " ") self.answer.setText("") elif self.state == GameState.TESTING: self.test_answers[self.current_card()] = int(self.answer.text()) self.next_card() def style_feedback(self, color=Qt.red, strikeout=True): font = self.question.font() font.setStrikeOut(strikeout) self.feedback.setFont(font) palette = self.feedback.palette() palette.setColor(self.feedback.foregroundRole(), color) self.feedback.setPalette(palette) def show_question_or_feedback(self): if len(self.cards_todo) == 0 or self.test_timed_out: if self.state == GameState.PRACTICE: self.style_feedback(Qt.green, False) self.feedback.setText("Klaar!") else: self.show_test_results() self.question.setText("") self.answer.setText("") self.stop_all() else: self.question.setText(str(self.current_card()) + " =") self.answer.setText("") self.answer.setFocus() self.feedback.setText("") self.question_start_time = time() def generate_report(self) -> str: correct_answers = 0 for (card, my_answer) in self.test_answers.items(): correct_answer = card.answer() if my_answer == correct_answer: correct_answers += 1 report = "<h1>" report += "Resultaat toets = %d / %d" % (correct_answers, TEST_SIZE) report += "<img src='%s'></img>" % self.get_report_icon( correct_answers / TEST_SIZE) report += " </h1>\n<br>" for (card, my_answer) in self.test_answers.items(): correct_answer = card.answer() if my_answer == correct_answer: report += "<font size='6' color='green'>%s = %d</font><br>\n" % ( str(card), my_answer) else: report += "<font size='6' color='red'>%s = <s>%s</s> </font>" \ "<font size='6'>%d</font><br>\n" % (str(card), str(my_answer), correct_answer) return report @staticmethod def get_report_icon(score: float) -> str: if score == 1.0: return ":/icons/icons/emoji/1F3C6.svg" # prize elif score >= 0.9: return ":/icons/icons/emoji/1F600.svg" # :D elif score >= 0.8: return ":/icons/icons/emoji/1F642.svg" # :-) elif score >= 0.6: return ":/icons/icons/emoji/1F610.svg" # :-| else: return ":/icons/icons/emoji/1F61F.svg" # :-( @staticmethod def get_stats_file() -> Path: dir = user_state_dir("tafels") return Path(dir, "cardstate.dat") @staticmethod def get_selections_file() -> Path: dir = user_state_dir("tafels") return Path(dir, "selections.dat") def save_stats(self): CardStatsLoader.store(self.get_stats_file(), self.card_stats)
def exception_setup(python, thread, where, activeTime_s): logging.getLogger(__name__).info("------------------------------------------------------") logging.getLogger(__name__).info("Starting exception_setup %d %s %s %f", python, thread, where, activeTime_s) from nexxT.services.ConsoleLogger import ConsoleLogger logger = ConsoleLogger() Services.addService("Logging", logger) class LogCollector(logging.StreamHandler): def __init__(self): super().__init__() self.logs = [] def emit(self, record): self.logs.append(record) # avoid warning flood about service profiling not found Services.addService("Profiling", None) collector = LogCollector() logging.getLogger().addHandler(collector) try: t = QTimer() t.setSingleShot(True) # timeout if test case hangs t2 = QTimer() t2.start((activeTime_s + 3)*1000) try: test_json = Path(__file__).parent / "test_except_constr.json" with test_json.open("r", encoding='utf-8') as fp: cfg = json.load(fp) if nexxT.useCImpl and not python: cfg["composite_filters"][0]["nodes"][2]["library"] = "binary://../binary/${NEXXT_PLATFORM}/${NEXXT_VARIANT}/test_plugins" cfg["composite_filters"][0]["nodes"][2]["thread"] = thread cfg["composite_filters"][0]["nodes"][2]["properties"]["whereToThrow"] = where mod_json = Path(__file__).parent / "test_except_constr_tmp.json" with mod_json.open("w", encoding="utf-8") as fp: json.dump(cfg, fp) config = Configuration() ConfigFileLoader.load(config, mod_json) config.activate("testApp") app.processEvents() aa = Application.activeApplication init = True def timeout(): nonlocal init if init: init = False aa.stop() aa.close() aa.deinit() else: app.exit(0) def timeout2(): print("Application timeout hit!") nonlocal init if init: init = False aa.stop() aa.close() aa.deinit() else: print("application exit!") app.exit(1) t2.timeout.connect(timeout2) t.timeout.connect(timeout) def state_changed(state): if state == FilterState.ACTIVE: t.setSingleShot(True) t.start(activeTime_s*1000) elif not init and state == FilterState.CONSTRUCTED: t.start(1000) aa.stateChanged.connect(state_changed) aa.init() aa.open() aa.start() app.exec_() finally: del t del t2 finally: logging.getLogger().removeHandler(collector) Services.removeAll() return collector.logs
class Plotter(FigureCanvasQTAgg): def __init__(self, xtitle=None, ytitle=None, width=5, height=4, dpi=100): fig = Figure(figsize=(width, height), dpi=dpi) self.axes = fig.add_subplot(111) self.axes.autoscale(enable=True) super().__init__(fig) self._createDialog() self._needsUpdating = False self.updateTimer = QTimer() self.updateTimer.setSingleShot(False) self.updateTimer.setInterval(UPDATE_INTERVAL) self.updateTimer.timeout.connect(self._updateFigure) self.updateThread = QThread() self.updateThread.started.connect(self._refreshFigure) self.xdata = list(range(MAXIMUM_POINTS)) self.ydata = [0] * MAXIMUM_POINTS #The following lines may not be correct self.xtitle = xtitle self.ytitle = ytitle self.xtitle("SAMPLE X") # self._plot_ref = None self._showDialog() self.updateTimer.start() def _updateFigure(self): if self._needsUpdating: self.updateThread.start() def _refreshFigure(self): # if self._plot_ref is None: # self._plot_ref, = self.axes.plot(self.xdata, self.ydata, 'r', marker='o', markersize=12) # else: # self._plot_ref.set_xdata(self.xdata) # self._plot_ref.set_ydata(self.ydata) self.axes.cla() self.axes.plot(self.xdata, self.ydata, 'r', marker='o', markersize=12) if self.xtitle is not None: self.axes.xtitle(self.xtitle) if self.ytitle is not None: self.axes.ytitle(self.ytitle) self.draw() self._needsUpdating = False def _createDialog(self): self.plotDialog = QDialog() self.plotDialog.setWindowTitle("Processing Plot") dialogButtons = QDialogButtonBox(QDialogButtonBox.Close) dialogButtons.clicked.connect(self._closeDialog) layout = QVBoxLayout() self.toolbar = NavigationToolbar(self, self.plotDialog) layout.addWidget(self.toolbar) layout.addWidget(self) layout.addWidget(dialogButtons) self.plotDialog.setLayout(layout) def _closeDialog(self): self.plotDialog.close() def _showDialog(self): self.plotDialog.show() def addNewData(self, x, y): #Check data validity x = np.array(x).flatten() y = np.array(y).flatten() if x.size != y.size: print( "Problem with adding new values to the plot, x and y should have the same length" ) return #TODO: Add other validation conditions #Add data to class length = x.shape[0] self.xdata = self.xdata[-(MAXIMUM_POINTS - length):] + x.tolist() self.ydata = self.ydata[-(MAXIMUM_POINTS - length):] + y.tolist() #flag it for updates self._needsUpdating = True def __del__(self): self.plotDialog.close() while not self.updateThread.isFinished(): pass
class WizardSession: settings_dir = CreateZip.settings_dir last_session_file = Path(settings_dir, 'last_preset_session.rksession') automagic_filter = set() def __init__(self, wizard): """ Saves and loads all data to the wizard :param modules.gui.wizard.wizard.PresetWizard wizard: """ self.wizard = wizard self.data = SessionData() self.update_options_timer = QTimer() self.update_options_timer.setInterval(15) self.update_options_timer.setSingleShot(True) self.update_options_timer.timeout.connect(self._update_available_options) # -- Preset Pages KnechtModels for available PR and Package options -- self.opt_models = dict() # ModelCode: KnechtModel self.pkg_models = dict() # ModelCode: KnechtModel self._load_default_filter() def _load_default_filter(self): """ Read Package default filter from qt resources """ f = QFile(Resource.icon_paths.get('pr_data')) try: f.open(QIODevice.ReadOnly) data: QByteArray = f.readAll() data: bytes = data.data() Settings.load_json_from_bytes(PrJsonData, data) except Exception as e: LOGGER.error(e) finally: f.close() self.data.pkg_filter = PrJsonData.package_filter[::] self.automagic_filter = set(PrJsonData.wizard_automagic_filter) # Update Start Page Package Widget if hasattr(self.wizard, 'page_welcome'): self.wizard.page_welcome.reload_pkg_filter() def _clean_up_import_data(self): new_models = list() for trim in self.data.import_data.models: if trim.model in self.data.import_data.selected_models: new_models.append(trim) self.data.import_data.models = new_models def _load_default_attributes(self): """ Make sure that all attributes are present in SessionData after pickle load. Previous version may had less attributes. """ default_session = SessionData() for k in dir(default_session): v = getattr(default_session, k) if k.startswith('__') or not isinstance(v, (int, str, float, bool, list, dict, tuple, set)): continue # Set missing attributes if not hasattr(self.data, k): LOGGER.debug('Setting default session attribute: %s: %s', k, v) setattr(self.data, k, v) # Make sure newly added attributes exists in older sessions self.data.import_data.options_text_filter = True def reset_session(self): self.data = SessionData() # Reset Preset Page content - available PR-Options/Packages self.opt_models = dict() self.pkg_models = dict() self._load_default_filter() self.wizard.page_fakom.result_tree.clear() self.clear_preset_pages() def load(self, file: Path=None) -> bool: if not file: file = self.last_session_file result = True try: self.data = Settings.pickle_load(file, compressed=True) except Exception as e: LOGGER.error('Error loading wizard session: %s', e) result = False try: self._load_default_attributes() except Exception as e: LOGGER.debug('Error setting default session attributes: %s', e) result = False if not result: # Restore Default Session self.data = SessionData() self._load_default_filter() return result def save(self, file: Path=None) -> bool: if not file: file = self.last_session_file for page_id in self.data.preset_page_ids: page: PresetWizardPage = self.wizard.page(page_id) if not isinstance(page, PresetWizardPage): LOGGER.warning('Skipping non existing page %s', page_id) continue src_item_model = page.preset_tree.model().sourceModel() self.data.store_preset_page_content(page.model, page.fakom, src_item_model) self._clean_up_import_data() return Settings.pickle_save(self.data, file, compressed=True) def iterate_preset_pages(self): for page_id in self.data.preset_page_ids: preset_page: PresetWizardPage = self.wizard.page(page_id) if not isinstance(preset_page, PresetWizardPage): continue yield preset_page def clear_preset_pages(self): page_id, cleared_pages = self.wizard.page_placeholder.id, 0 if not page_id > 0: return while True: page_id += 1 if isinstance(self.wizard.page(page_id), (PresetWizardPage, ResultWizardPage)): self.wizard.removePage(page_id) cleared_pages += 1 else: break LOGGER.debug('Cleared %s preset pages.', cleared_pages) def create_preset_pages(self): """ Create a Wizard preset page for each selected FaKom item """ self.clear_preset_pages() self.data.preset_page_ids = set() for model_code, fakom_ls in self.data.fakom_selection.items(): # Create available PR-Options and Packages per model self._update_preset_pages_item_models(model_code) for fakom in fakom_ls: preset_page = PresetWizardPage(self.wizard, model_code, fakom) page_id = self.wizard.addPage(preset_page) self.data.preset_page_ids.add(page_id) LOGGER.debug('Creating preset page: %s', page_id) # --- Load preset page content if available --- saved_model = self.data.load_preset_page_content(model_code, fakom) preset_page.load_model(saved_model) # Add Results Wizard Page self.wizard.addPage(self.wizard.page_result) # Populate Navigation Menu self.wizard.nav_menu.create_preset_page_entries() def update_available_options(self): """ This will be called from multiple views after a refresh so we delay the update with a timer so that it will update only once. """ self.update_options_timer.start() def update_available_options_immediately(self): """ Called from automagic routine for immediate updates """ self._update_available_options(ignore_lock_btn=True) def _update_available_options(self, ignore_lock_btn: bool=False): """ Update PR-Options and Packages Trees based on Preset Page Content """ used_pr_families, used_pr_options, visible_pr_options = set(), set(), set() visible_pkgs, used_pkgs = set(), set() current_page = self.wizard.page(self.wizard.currentId()) if not isinstance(current_page, PresetWizardPage): return # -- Add user locked options and packages used_pr_options.update(self.data.user_locked_pr) used_pkgs.update(self.data.user_locked_pkg) # --- Update PR-Options in use by all pages --- for preset_page in self.iterate_preset_pages(): pr_options = self._collect_tree_pr_data(preset_page.preset_tree)[0] used_pr_options.update(pr_options) for index, item in preset_page.preset_tree.editor.iterator.iterate_view(): if item.data(Kg.TYPE) == 'package': used_pkgs.add(item.data(Kg.VALUE)) # -- Update currently used PR-Families on current page -- used_pr_families = self._collect_tree_pr_data(current_page.preset_tree)[1] used_pr_families.update(self.automagic_filter) # --- Update available PR-Options --- for opt_index, opt_item in current_page.option_tree.editor.iterator.iterate_view(): # Clear userType and locked style opt_item.fixed_userType = 0 opt_item.style_unlocked() item_type = opt_item.data(Kg.TYPE) if item_type in used_pr_families or opt_item.data(Kg.NAME) in used_pr_options: if current_page.option_lock_btn.isChecked() or ignore_lock_btn: opt_item.fixed_userType = Kg.locked_variant opt_item.style_locked() else: opt_item.style_italic() else: visible_pr_options.add(opt_item.data(Kg.NAME)) # --- Update available Packages --- for pkg_index, pkg_item in current_page.pkg_tree.editor.iterator.iterate_view(): # Clear userType and locked style pkg_item.fixed_userType = 0 pkg_item.style_unlocked() pkg_name = pkg_item.data(Kg.NAME) lock_pkg = False if pkg_item.data(Kg.VALUE) in used_pkgs: lock_pkg = True else: for pkg_variant in pkg_item.iter_children(): if pkg_variant.data(Kg.TYPE) in used_pr_families or pkg_name in used_pr_options: lock_pkg = True break # Package Country Filter if self.data.pkg_filter_regex and re.search(self.data.pkg_filter_regex, pkg_name): lock_pkg = True if lock_pkg: if current_page.option_lock_btn.isChecked() or ignore_lock_btn: pkg_item.fixed_userType = Kg.locked_preset pkg_item.style_locked() else: pkg_item.style_italic() else: visible_pkgs.add(pkg_item.data(Kg.VALUE)) # Show or Hide locked PR-Options and Packages if current_page.option_hide_btn.isChecked(): current_page.option_tree.permanent_type_filter = list(visible_pr_options) current_page.pkg_tree.permanent_type_filter = list(visible_pkgs) else: del current_page.option_tree.permanent_type_filter del current_page.pkg_tree.permanent_type_filter @staticmethod def _collect_tree_pr_data(view: KnechtTreeView): pr_options, pr_families = set(), set() for index, item in view.editor.iterator.iterate_view(): variant_ls = view.editor.collect.collect_index(index) for variant in variant_ls.variants: pr_families.add(variant.item_type) pr_options.add(variant.name) return pr_options, pr_families def _update_preset_pages_item_models(self, model_code: str): """ Populate preset page models with available pr options and packages """ if model_code not in self.opt_models: # --- Create Knecht item model for available PR-Options --- self.opt_models[model_code] = self._create_options_knecht_model( model_code, self.data.import_data, is_pr_options=True ) if model_code not in self.pkg_models: # --- Create Knecht item model for available PR-Options --- self.pkg_models[model_code] = self._create_options_knecht_model( model_code, self.data.import_data, is_pr_options=False ) @staticmethod def _create_options_knecht_model(model_code, import_data: KnData, is_pr_options=True): """ Create Knecht Item Model with either available PR-Options or Packages """ converter = KnechtDataToModel(import_data) opt_item_model = KnechtModel() trim = [t for t in import_data.models if t.model == model_code] if not trim: return opt_item_model else: trim = trim[0] if is_pr_options: if import_data.options_text_filter: # Create PR-Options matching E converter.create_pr_options(trim.iterate_optional_filtered_pr(), opt_item_model.root_item, ignore_pr_family=False) else: # Create PR-Options not matching L converter.create_pr_options(trim.iterate_optional_pr(), opt_item_model.root_item, ignore_pr_family=False) else: converter.create_packages(trim, opt_item_model.root_item, filter_pkg_by_pr_family=False) return opt_item_model
class MaterialMerger(QWidget): def __init__(self, ui): """ Dialog to merge Materials directory of different models :param modules.gui.main_ui.KnechtWindow ui: Main Window """ super(MaterialMerger, self).__init__(ui) SetupWidget.from_ui_file(self, Resource.ui_paths['knecht_material_merger']) self.setWindowTitle('AViT Material Merger') self.src_path_objects = list() self.titleLabel: QLabel self.titleLabel.setText(''' <p>Vergleicht beliebige Quell -Materials- Verzeichnisse mit dem Ziel Material Verzeichnis. Ersetzt nur Unterordner die bereits im Ziel Verzeichniss bestehen. Die Quell-Verzeichnisse werden gewählt anhand der jüngsten enthaltenen CSB Datei. <b>Erstellt kein BackUp!</b></p> <table> <tr> <th>Ziel</th> <th>Aktion</th> <th>Quelle</th> </tr> <tr> <td>ABC001</td> <td> < - - </td> <td>ABC001</td> </tr> <tr> <td></td> <td>x</td> <td>DEF001</td> </tr> <tr> <td>DBC000</td> <td> < - - </td> <td>DBC000</td> </tr> </table> ''') self.titleLabel.setWordWrap(True) self.srcGrp: QGroupBox self.srcGrp.setTitle(_('Quell-Verzeichnisse')) self.targetGrp: QGroupBox self.targetGrp.setTitle(_('Ziel-Verzeichnis')) self.addSrcBtn: QPushButton self.addSrcBtn.setText(_('Quelleordner hinzufügen')) self.addSrcBtn.pressed.connect(self.add_source_path_object) self.mergeBtn: QPushButton self.mergeBtn.setText(_('Vereinen')) self.mergeBtn.pressed.connect(self.merge) self.target_path_widget = SetDirectoryPath( self, line_edit=self.targetPathLineEdit, tool_button=self.targetPathToolBtn, reject_invalid_path_edits=True) self.resultBrowser: QTextBrowser self.resultBrowser.append('Displaying results.') self.copy_thread = None self.thread_timer = QTimer() self.thread_timer.setInterval(1000) self.thread_timer.setSingleShot(False) self.thread_timer.timeout.connect(self.update_copy_thread_status) # -- Create initial source path widget self.add_source_path_object() self.sorted_src_dirs = dict() self.tex_difference_dirs = dict() def remove_source_path_object(self): btn = self.sender() src_path = [s for s in self.src_path_objects if s == btn.src_path][0] for i in range(src_path.layout.count()): w = src_path.layout.itemAt(i).widget() w.deleteLater() self.src_path_objects.remove(src_path) src_path.layout.deleteLater() src_path.path_widget.deleteLater() src_path.deleteLater() def add_source_path_object(self): src_path = QObject(self) h_layout = QHBoxLayout(self.srcGrp) label = QLabel(f'Source_{len(self.src_path_objects)}') h_layout.addWidget(label) line_edit = QLineEdit(self.srcGrp) h_layout.addWidget(line_edit) tool_btn = QToolButton(self.srcGrp) tool_btn.setText('...') h_layout.addWidget(tool_btn) del_btn = QPushButton(self.srcGrp) del_btn.setIcon(IconRsc.get_icon('delete')) h_layout.addWidget(del_btn) del_btn.src_path = src_path del_btn.released.connect(self.remove_source_path_object) path_widget = SetDirectoryPath(self, line_edit=line_edit, tool_button=tool_btn) src_path.layout = h_layout src_path.path_widget = path_widget # Save widget in path objects list and add widget to source path layout self.src_path_objects.append(src_path) self.srcLayout.addLayout(h_layout) def merge(self): self.mergeBtn.setEnabled(False) self.resultBrowser.clear() self.resultBrowser.append('<h1>' + _('Vereine Material Verzeichnisse') + '</h1><br>') self.resultBrowser.append( f'Target: <i>{self.target_path_widget.path.as_posix()}</i>') if self.target_path_widget.path is None or not self.target_path_widget.path.exists( ): self.resultBrowser.append( f'<span style="color: red;">Could not locate Target path: {self.target_path_widget.path}</span>' ) return target_dir_names = [ target_dir.name for target_dir in self.target_path_widget.path.iterdir() ] # -- Collect source Materials src_material_csbs = dict() for idx, src in enumerate(self.src_path_objects): self.resultBrowser.append( f'Source #{idx}: <i>{src.path_widget.path.as_posix()}</i>') _src_csbs = self._collect_csb_materials( src.path_widget.path.iterdir(), target_dir_names) for src_dir_name, entry_ls in _src_csbs.items(): if src_dir_name not in src_material_csbs: src_material_csbs[src_dir_name] = list() for entry in entry_ls: src_material_csbs[src_dir_name].append(entry) del src_dir_name, entry_ls self.resultBrowser.append('<br><br>') # -- Sort entries by CSB File change time self.sorted_src_dirs = dict() for dir_name, entry_list in src_material_csbs.items(): self.sorted_src_dirs[dir_name] = sorted(entry_list, key=lambda k: k['ctime'], reverse=True)[0] if len(entry_list) > 1: self.resultBrowser.append( f'Found <b>{dir_name}</b> in multiple sources. Selecting newer file from: ' f'{self.sorted_src_dirs[dir_name]["path"].parent.parent.name} from ' f'{datetime.utcfromtimestamp(self.sorted_src_dirs[dir_name]["ctime"]).strftime("%d.%m.%Y %H:%M")}' ) for e in entry_list: self.resultBrowser.append( f'Checked: {e["path"].parent.parent.name} - CSB last modified date: ' f'{datetime.utcfromtimestamp(e["ctime"]).strftime("%d.%m.%Y %H:%M")}' ) self.resultBrowser.append('<br>') del src_material_csbs # -- Filter out Materials that have differing textures for target_dir in self.target_path_widget.path.iterdir(): if target_dir.name not in self.sorted_src_dirs: continue # -- Collect source and target texture file paths src_tex = self._collect_texture_files( self.sorted_src_dirs[target_dir.name]['path']) tgt_tex = self._collect_texture_files(target_dir) # -- Compare and exclude differing files if tgt_tex.symmetric_difference(src_tex): # -- Remove Entries that have differing textures self.sorted_src_dirs.pop(target_dir.name) # -- Add Report Entry self.tex_difference_dirs[ target_dir.name] = tgt_tex.symmetric_difference(src_tex) # -- Replace Material directories in target dir thread_signals = ThreadSignals() thread_signals.update.connect(self.resultBrowser.append) self.copy_thread = Thread(target=copy_material_dirs, args=(self.target_path_widget.path, self.sorted_src_dirs, thread_signals)) self.copy_thread.start() self.thread_timer.start() def update_copy_thread_status(self): if self.copy_thread is None: return if self.copy_thread.is_alive(): return self.resultBrowser.append('Copy thread finished!<br>') self.report_untouched_materials() self.export_html_report() self.thread_timer.stop() self.mergeBtn.setEnabled(True) def report_untouched_materials(self): """ Report Materials not copied into target Directories existing in the target path but not in any source path """ self.resultBrowser.append('<h1>Material Merger Results</h1><br>') count = 0 for target_dir in self.target_path_widget.path.iterdir(): if target_dir.name in self.sorted_src_dirs or target_dir.name in self.tex_difference_dirs: continue self.resultBrowser.append( f'<b>{target_dir.name}</b> - ' f'was not in any source directory and was not updated.<br>') count += 1 if not count: self.resultBrowser.append( 'Source and target directories perfectly matched!? Did you cheat?<br>' ) self.resultBrowser.append( '<h2>Materials with differing texture files</h2> <br>') if not self.tex_difference_dirs: self.resultBrowser.append( '<i>Found no Materials with differing texture files.</i>') for target_dir in self.target_path_widget.path.iterdir(): if target_dir.name not in self.tex_difference_dirs: continue filenames = ''.join( [f'{f}; ' for f in self.tex_difference_dirs[target_dir.name]]) self.resultBrowser.append( f'<b>{target_dir.name}</b> - ' f'contained differing texture files: <i>{filenames}</i><br>') def export_html_report(self): # Report file path name = f'MaterialMerger_Report_{datetime.now().strftime("%d%m%Y_%H%M")}.html' report_file = QUrl.fromLocalFile( os.path.abspath(os.path.expanduser(f'~\\Documents\\{name}'))) # Result browser html content html_data = str(self.resultBrowser.toHtml()) # Write report try: with open(report_file.toLocalFile(), 'w') as f: f.write(html_data) except Exception as e: LOGGER.error(e) QDesktopServices.openUrl(report_file) @staticmethod def _collect_texture_files(directory: Path): return { file.name for file in directory.iterdir() if file.suffix.casefold() not in ('.csb', '.bak', '.texturepath', '.db') and not file.is_dir() } @staticmethod def _collect_csb_materials(directories: Union[List[Path]], target_dir_names: List[str]) -> dict: dir_data = dict() for src_dir in directories: # -- Skip directories not in target and non existing if src_dir is None or src_dir.is_file( ) or not src_dir.exists() or src_dir.name not in target_dir_names: continue # -- Get contained CSB files for d in src_dir.glob('*.csb'): # -- Add entry with directory path and CSB last modified time path_entry = {'path': src_dir, 'ctime': d.stat().st_mtime} if src_dir.name not in dir_data: dir_data[src_dir.name] = list() dir_data[src_dir.name].append(path_entry) return dir_data