def __init__(self, owner: QtW.QWidget, dao: da.TagsDao, editable: bool, selection_changed: typ.Optional[typ.Callable[[None], None]] = None, cell_changed: typ.Optional[typ.Callable[[int, int, str], None]] = None, rows_deleted: typ.Optional[typ.Callable[[typ.List[model.CompoundTag]], None]] = None): """Initializes this tab. :param owner: Tab’s owner. :param dao: The tag’s DAO. :param editable: If true the contained table will be editable. :param selection_changed: Action called when the selection changes. :param cell_changed: Action called when a cell has been edited. It takes the cell’s row, column and text. :param rows_deleted: Action called when rows have been deleted. It takes the list of deleted values. """ super().__init__( owner, dao, _t('dialog.edit_tags.tab.compound_tags.title'), addable=True, editable=editable, tag_class=model.CompoundTag, additional_columns=[ (_t('dialog.edit_tags.tab.compound_tags.table.header.definition'), False), ], additional_search_columns=[0], selection_changed=selection_changed, cell_changed=cell_changed, rows_deleted=rows_deleted )
def _on_dest_button_clicked(self): """Opens a directory chooser then sets the destination path to the one selected if any.""" if self._mode == EditImageDialog.REPLACE: destination = utils.gui.open_file_chooser( single_selection=True, mode=utils.gui.FILTER_IMAGES, directory=config.CONFIG.last_directory, parent=self) config.CONFIG.last_directory = destination.parent else: destination = utils.gui.open_directory_chooser( directory=config.CONFIG.last_directory, parent=self) config.CONFIG.last_directory = destination if destination: if self._mode == EditImageDialog.REPLACE: img = self._images[0] if self._image_to_replace is None: self._image_to_replace = img.path if self._image_to_replace == destination: utils.gui.show_error( _t('dialog.edit_image.error.replace_self'), parent=self) return self._destination = destination self._ok_btn.setDisabled(False) self._images[0] = model.Image(id=img.id, path=self._destination, hash=img.hash) self._set(0) else: self._destination = destination key = 'target_image' if self._mode == EditImageDialog.REPLACE else 'target_path' self._dest_label.setText( _t('dialog.edit_image.' + key, path=self._destination)) self._dest_label.setToolTip(str(self._destination))
def _on_input(self, input_: str): if self._results: if input_.upper() == 'Y': self._print_results() elif input_.upper() == 'N': self._column_names = None self._results = None else: self._command_line.print(_t('SQL_console.display_more')) else: cursor = self._connection.cursor() try: cursor.execute(input_) except sqlite3.Error as e: self._command_line.print_error(_t('SQL_console.error')) self._command_line.print_error(e) cursor.close() else: if input_.lower().startswith('select'): results = cursor.fetchall() if cursor.description is not None: column_names = tuple(desc[0] for desc in cursor.description) else: column_names = () self._column_names = column_names self._results = results self._results_offset = 0 self._results_total = len(results) self._print_results() else: self._command_line.print( _t('SQL_console.affected_rows', row_count=cursor.rowcount)) cursor.close()
def _print_results(self): results = self._results[self._results_offset:] if len(results) == 0: self._command_line.print(_t('SQL_console.no_results')) else: limit = 20 i = 0 rows = [] for result in results: if i % limit == 0: if i > 0: self._print_rows(rows, self._column_names) rows.clear() self._command_line.print( _t('SQL_console.display_more')) self._results_offset += i break upper_bound = min(self._results_offset + i + limit, self._results_total) self._command_line.print( _t('SQL_console.results', start=self._results_offset + i + 1, end=upper_bound, total=self._results_total)) rows.append(tuple(map(repr, result))) i += 1 else: self._print_rows(rows, self._column_names) self._results = None
def _check_column(self, column: int, check_duplicates: bool) -> typ.Tuple[int, int, str]: """Checks column’s integrity. :param column: The column to check. :param check_duplicates: Whether to check for any duplicate cell values. :return: A tuple with 3 values: table integrity which is one of OK, DUPLICATE, EMPTY or FORMAT; row of the invalid cell or -1 if whole column is valid; the error message. OK if all cells have valid values; DUPLICATE if some cells have identical values; EMPTY if a cell is empty; FORMAT if a cell is not formatted correctly. """ for row in range(self._table.rowCount()): if self._table.isRowHidden(row): continue if self._table.item(row, column).text().strip() == '': return self._EMPTY, row, _t('dialog.edit_tags.error.empty_cell') ok, message = self._check_cell_format(row, column) if not ok: return self._FORMAT, row, message if check_duplicates: cell_value = self._table.item(row, column).text() for r in range(self._table.rowCount()): if self._table.isRowHidden(r): continue if r != row and self._table.item(r, column).text() == cell_value: return self._DUPLICATE, r, _t('dialog.edit_tags.error.duplicate_value', row=row) return self._OK, -1, ''
def __init_button_box(self) -> QtW.QBoxLayout: """Initializes the buttons. Additional buttons can be set by overriding the _init_buttons method. These buttons will be added in the order they are returned and to the left of default buttons. :return: The list of buttons. """ box = QtW.QHBoxLayout() box.addStretch(1) if self._buttons_mode == Dialog.OK_CANCEL: icon = QtW.QStyle.SP_DialogOkButton else: icon = QtW.QStyle.SP_DialogCloseButton self._ok_btn = QtW.QPushButton( self.style().standardIcon(icon), _t('dialog.common.ok_button.label') if self._buttons_mode == Dialog.OK_CANCEL else _t('dialog.common.close_button.label'), parent=self) self._ok_btn.clicked.connect(self._on_ok_clicked) if self._buttons_mode == Dialog.OK_CANCEL: self._cancel_btn = QtW.QPushButton( self.style().standardIcon(QtW.QStyle.SP_DialogCancelButton), _t('dialog.common.cancel_button.label'), parent=self) self._cancel_btn.clicked.connect(self.reject) buttons = self._init_buttons() for b in buttons: box.addWidget(b) box.addWidget(self._ok_btn) if self._buttons_mode == Dialog.OK_CANCEL: box.addWidget(self._cancel_btn) return box
def _set_row(self, tag: typ.Optional[_TagType], row: int): defined = tag is not None id_item = _IntTableWidgetItem(str(tag.id if defined else self._dummy_type_id)) id_item.setWhatsThis('ident') # noinspection PyTypeChecker id_item.setFlags(id_item.flags() & ~QtC.Qt.ItemIsEditable & ~QtC.Qt.ItemIsSelectable) if self._editable: id_item.setBackground(self._DISABLED_COLOR) self._table.setItem(row, 0, id_item) label_item = QtW.QTableWidgetItem(tag.label if defined else 'new_tag') label_item.setWhatsThis('label') self._table.setItem(row, 1, label_item) # Populate additional columns for j, column in enumerate(self._columns[2:-2]): value, cell_label = self._get_value_for_column(column, tag, not defined) item = QtW.QTableWidgetItem(value) item.setWhatsThis(cell_label) if not self._editable: # noinspection PyTypeChecker item.setFlags(item.flags() & ~QtC.Qt.ItemIsEditable & ~QtC.Qt.ItemIsSelectable) self._table.setItem(row, 2 + j, item) if not self._editable: # noinspection PyTypeChecker label_item.setFlags(label_item.flags() & ~QtC.Qt.ItemIsEditable & ~QtC.Qt.ItemIsSelectable) if defined and tag.type: text = tag.type.label else: text = _t('dialog.edit_tags.tab.tags_common.table.combo_no_type') type_item = QtW.QTableWidgetItem(text) font = type_item.font() font.setItalic(not defined or not tag.type) type_item.setFont(font) # noinspection PyTypeChecker type_item.setFlags(type_item.flags() & ~QtC.Qt.ItemIsEditable & ~QtC.Qt.ItemIsSelectable) self._table.setItem(row, self._type_column, type_item) else: combo = QtW.QComboBox(parent=self._owner) combo.currentIndexChanged.connect(self._combo_changed) combo.setWhatsThis('tag_type') combo.setProperty('row', row) combo.addItem(_t('dialog.edit_tags.tab.tags_common.table.combo_no_type')) for tag_type in self._tags_dao.get_all_tag_types(): combo.addItem(self._get_combo_text(tag_type.id, tag_type.label)) if defined and tag.type is not None: combo.setCurrentIndex(combo.findText(self._get_combo_text(tag.type.id, tag.type.label))) self._table.setCellWidget(row, self._type_column, combo) # count property is added to tag argument before calling this method. number_item = _IntTableWidgetItem(str(getattr(tag, 'count') if defined else 0)) # noinspection PyTypeChecker number_item.setFlags(number_item.flags() & ~QtC.Qt.ItemIsEditable & ~QtC.Qt.ItemIsSelectable) if self._editable: number_item.setBackground(self._DISABLED_COLOR) self._table.setItem(row, self._tag_use_count_column, number_item)
def _init_body(self) -> QtW.QLayout: self.setGeometry(0, 0, 480, 400) layout = QtW.QVBoxLayout() buttons = QtW.QHBoxLayout() buttons.addStretch(1) self._add_row_btn = QtW.QPushButton(parent=self) self._add_row_btn.setIcon(utils.gui.icon('list-add')) self._add_row_btn.setToolTip( _t('dialog.edit_tags.add_item_button.tooltip')) self._add_row_btn.setFixedSize(24, 24) self._add_row_btn.setFocusPolicy(QtC.Qt.NoFocus) self._add_row_btn.clicked.connect(self._add_row) buttons.addWidget(self._add_row_btn) self._delete_row_btn = QtW.QPushButton(parent=self) self._delete_row_btn.setIcon(utils.gui.icon('list-remove')) self._delete_row_btn.setToolTip( _t('dialog.edit_tags.delete_items_button.tooltip')) self._delete_row_btn.setFixedSize(24, 24) self._delete_row_btn.setFocusPolicy(QtC.Qt.NoFocus) self._delete_row_btn.clicked.connect(self._delete_selected_row) buttons.addWidget(self._delete_row_btn) if self._editable: layout.addLayout(buttons) else: self._add_row_btn.hide() self._delete_row_btn.hide() self._tabbed_pane = QtW.QTabWidget(parent=self) self._tabbed_pane.currentChanged.connect(self._tab_changed) self._init_tabs() layout.addWidget(self._tabbed_pane) search_layout = QtW.QHBoxLayout() self._search_field = _InputField(parent=self) self._search_field.setPlaceholderText( _t('dialog.edit_tags.search_field.placeholder')) self._search_field.returnPressed.connect(self._search) self._search_field.textChanged.connect(self._reset_status_label) search_layout.addWidget(self._search_field) search_btn = QtW.QPushButton( utils.gui.icon('search'), _t('dialog.edit_tags.search_button.label'), parent=self) search_btn.clicked.connect(self._search) search_layout.addWidget(search_btn) layout.addLayout(search_layout) self._status_label = components.LabelWithIcon(parent=self) layout.addWidget(self._status_label) return layout
def _update_ui(self): dest = self._destination_input.text() dest_exists = dest and pathlib.Path(dest).absolute().is_dir() if not dest_exists: self._dest_path_warning_label.setText( _t('dialog.move_images.destination_empty') if not dest else _t( 'dialog.move_images.destination_non_existant')) self._dest_path_warning_label.setVisible(not dest_exists) self._ok_btn.setDisabled(not dest_exists or not self._images)
def __init__(self, parent: QtW.QWidget = None): super().__init__('', _t('dialog.common.cancel_button.label'), 0, 100, parent=parent) self.setWindowTitle(_t('popup.progress.title')) self.setMinimumDuration(500) self.setModal(parent is not None)
def _check_cell_format(self, row: int, col: int) -> (bool, str): text = self._table.item(row, col).text() if col == 1: if model.Tag.LABEL_PATTERN.match(text) is None: return False, _t('dialog.edit_tags.error.invalid_tag_name') tag_id = int(self._table.item(row, 0).text()) if self._tags_dao.tag_exists(tag_id, text): return False, _t('dialog.edit_tags.error.duplicate_tag_name') return True, ''
def _check_cell_format(self, row: int, col: int) -> (bool, str): text = self._table.item(row, col).text() if col == 1: return model.TagType.LABEL_PATTERN.match(text) is not None, \ _t('dialog.edit_tags.error.invalid_tag_name') if col == 2: return (model.TagType.SYMBOL_PATTERN.match(text) is not None, _t('dialog.edit_tags.error.invalid_tag_type_symbol')) return True, ''
def __init__(self, images: typ.List[typ.Tuple[model.Image, float]], image_dao: data_access.ImageDao, tags_dao: data_access.TagsDao, parent: QtW.QWidget = None): self._images = images self._index = -1 super().__init__(parent=parent, title=_t('dialog.similar_images.title'), modal=True, mode=self.OK_CANCEL) self._ok_btn.setText(_t('dialog.similar_images.button.copy_tags.label')) self._ok_btn.setDisabled(True) self._cancel_btn.setText(_t('dialog.similar_images.button.close.label')) self._image_dao = image_dao self._tags_dao = tags_dao
def __init__(self, tags_dao: data_access.TagsDao, editable: bool = True, parent: typ.Optional[QtW.QWidget] = None): """Creates a dialog. :param tags_dao: Tags DAO instance. :param parent: The widget this dialog is attached to. :param editable: If true tags and types will be editable. """ self._init = False self._editable = editable def type_cell_changed(row: int, col: int, _): if col == 1: for tab in self._tabs[1:]: tag_type = self._tabs[self._TAG_TYPES_TAB].get_value(row) if tag_type is not None: tab.update_type_label(tag_type) self._check_integrity() def types_deleted(deleted_types: typ.List[model.TagType]): for tab in self._tabs[1:]: tab.delete_types(deleted_types) self._tabs = (_tabs.TagTypesTab( self, tags_dao, self._editable, selection_changed=self._selection_changed, cell_changed=type_cell_changed, rows_deleted=types_deleted), _tabs.CompoundTagsTab( self, tags_dao, self._editable, selection_changed=self._selection_changed, cell_changed=self._check_integrity, rows_deleted=self._check_integrity), _tabs.TagsTab(self, tags_dao, self._editable, selection_changed=self._selection_changed, cell_changed=self._check_integrity, rows_deleted=self._check_integrity)) title = _t('dialog.edit_tags.title_edit') if self._editable else _t( 'dialog.edit_tags.title_readonly') mode = self.CLOSE if not self._editable else self.OK_CANCEL super().__init__(parent=parent, title=title, modal=self._editable, mode=mode) self._valid = True
def _on_progress_update(self, progress: float, data: pathlib.Path, status: int): self._progress_dialog.setValue(int(progress * 100)) status_label = _t('popup.progress.status_label') if status == 1: status_ = _t('popup.progress.status.success') elif status == 2: status_ = _t('popup.progress.status.failed') else: status_ = _t('popup.progress.status.unknown') self._progress_dialog.setLabelText( f'{progress * 100:.2f} %\n{data}\n{status_label} {status_}')
def _set(self, index: int): """Sets the current image.""" # Update completer if self._tags_changed or index == 0: self._tags_input.set_completer_model([ t.label for t in self._tags_dao.get_all_tags(tag_class=model.Tag, sort_by_label=True) ]) image = self._images[index] # Calculate hash if in add mode if self._mode == EditImageDialog.ADD: hash_ = utils.image.get_hash(image.path) self._images[index] = image = model.Image(id=image.id, path=image.path, hash=hash_) self._image_path_lbl.setText(str(image.path)) self._image_path_lbl.setToolTip(str(image.path)) if self._mode == EditImageDialog.REPLACE: self._tags_input.setDisabled(False) if self._mode != EditImageDialog.ADD: tags = [] if image.id in self._tags: tags = self._tags[image.id] self._set_tags(tags, clear_undo_redo=True) if self._mode == EditImageDialog.REPLACE: self._tags_input.setDisabled(True) self._tags_changed = False self._canvas.set_image(image.path) similar_images = self._image_dao.get_similar_images(image.path) or [] self._similar_images = [ (image, score) for image, _, score, same_path in similar_images if not same_path ] if self._similar_images: self._similarities_btn.show() else: self._similarities_btn.hide() if self._index == len(self._images) - 1: if self._skip_btn: self._skip_btn.setDisabled(True) self._ok_btn.setText(_t('dialog.edit_image.finish_button.label')) elif self._skip_btn: self._ok_btn.setText( _t('dialog.edit_image.apply_next_button.label')) self.setWindowTitle(self._get_title())
def _search(self): text = self._search_field.text().strip() if len(text) > 0: found = self._tabs[self._tabbed_pane.currentIndex()].search(text) if found is None: self._status_label.setText(_t('dialog.edit_tags.syntax_error')) self._status_label.setIcon(utils.gui.icon('warning')) elif not found: self._status_label.setText(_t('dialog.edit_tags.no_match')) self._status_label.setIcon(utils.gui.icon('help-about')) else: self._reset_status_label() self._search_field.setFocus()
def _replace_paths(self): image_dao = data_access.ImageDao(config.CONFIG.database_path) try: regex = self._to_replace.replace('/', r'\/') query = queries.query_to_sympy(f'path:/{regex}/', simplify=False) except ValueError as e: self._error = str(e) return images = image_dao.get_images(query) if images is None: self._error = _t('thread.search.error.image_loading_error') else: total = len(images) progress = 0 for i, image in enumerate(images): if self._cancelled: break # Replace but keep absolute path new_path = pathlib.Path( re.sub(self._to_replace, self._replacement, str(image.path))).absolute() self.progress_signal.emit(progress, (self._mode, image.path, new_path), self.STATUS_UNKNOWN) new_hash = utils.image.get_hash(new_path) ok = image_dao.update_image(image.id, new_path, new_hash) if ok: self._affected += 1 else: self._failed_images.append(image) progress = i / total self.progress_signal.emit( progress, (self._mode, image.path, new_path), self.STATUS_SUCCESS if ok else self.STATUS_FAILED)
def _get_error(self) -> typ.Optional[str]: try: tags = self._get_tags() except ValueError as e: error = re.search('"(.+)"', str(e))[1] return _t('dialog.edit_image.error.invalid_tag_format', error=error) else: if len(tags) == 0: return _t('dialog.edit_image.error.no_tags') elif t := self._get_compound_tags(tags): return _t('dialog.edit_image.error.compound_tags_disallowed', tags='\n'.join(t)) elif t := self._get_duplicate_tags(tags): return _t('dialog.edit_image.error.duplicate_tags', tags='\n'.join(t))
def __init__(self, owner: QtW.QWidget, dao: da.TagsDao, editable: bool, selection_changed: typ.Optional[typ.Callable[[None], None]] = None, cell_changed: typ.Optional[typ.Callable[[int, int, str], None]] = None, rows_deleted: typ.Optional[typ.Callable[[typ.List[_Type]], None]] = None): """Initializes this tab. :param owner: Tab’s owner. :param dao: The tag’s DAO. :param editable: If true the contained table will be editable. :param selection_changed: Action called when the selection changes. :param cell_changed: Action called when a cell has been edited. It takes the cell’s row, column and text. :param rows_deleted: Action called when rows have been deleted. It takes the list of deleted values. """ super().__init__( owner, dao, _t('dialog.edit_tags.tab.tag_types.title'), addable=True, deletable=True, editable=editable, columns_to_check=[(1, True), (2, True)], search_columns=[1, 2], selection_changed=selection_changed, cell_changed=cell_changed, rows_deleted=rows_deleted )
def _init_body(self) -> QtW.QLayout: self.setMinimumSize(200, 140) body = QtW.QHBoxLayout() self._label = QtW.QLabel(parent=self) year = datetime.now().year copyright_year = '' if year == 2018 else f' - {year}' self._label.setText(f""" <html style="font-size: 12px"> <h1>Image Library v{constants.VERSION}</h1> <p>© 2018{copyright_year} Damien Vergnet</p> <p>Icons © FatCow</p> <p>Find more on <a href="https://github.com/Darmo117/ImageDatabase">GitHub</a>.</p> </html> """.strip()) self._label.setOpenExternalLinks(True) self._label.setContextMenuPolicy(QtC.Qt.CustomContextMenu) self._label.customContextMenuRequested.connect(self._link_context_menu) self._label.linkHovered.connect(self._update_current_link) body.addWidget(self._label) self._current_link = None self._label_menu = QtW.QMenu(parent=self._label) self._label_menu.addAction(_t('dialog.about.menu.copy_link_item')) self._label_menu.triggered.connect( lambda: pyperclip.copy(self._current_link)) return body
def migrate(connection: sqlite3.Connection, thread: gui.threads.WorkerThread): connection.executescript(f""" BEGIN; CREATE TABLE version ( db_version INTEGER PRIMARY KEY, app_version TEXT ); ALTER TABLE images ADD COLUMN hash BLOB; -- Cannot use INTEGER as hashes are 64-bit *unsigned* integers CREATE INDEX idx_images_hash ON images (hash); -- Speed up hash querying ALTER TABLE tags ADD COLUMN definition TEXT; INSERT INTO version (db_version, app_version) VALUES (1, "{constants.VERSION}"); """) cursor = connection.execute('SELECT id, path FROM images') rows = cursor.fetchall() total_rows = len(rows) for i, (ident, path) in enumerate(rows): if thread.cancelled: cursor.close() connection.rollback() break thread.progress_signal.emit( i / total_rows, _t(f'popup.database_update.migration_0000.hashing_image_text', image=path, index=i + 1, total=total_rows), thread.STATUS_UNKNOWN) image_hash = utils.image.get_hash(path) try: connection.execute('UPDATE images SET hash = ? WHERE id = ?', (data_access.ImageDao.encode_hash(image_hash) if image_hash is not None else None, ident)) except sqlite3.Error as e: cursor.close() thread.error = str(e) thread.cancel() else: thread.progress_signal.emit( (i + 1) / total_rows, _t(f'popup.database_update.migration_0000.hashing_image_text', image=path, index=i + 1, total=total_rows), thread.STATUS_SUCCESS) else: cursor.close() connection.commit()
def _replace(self) -> typ.Tuple[bool, typ.Optional[str]]: """Replaces an image by another one. The old image is deleted. The new image will stay in its directory. :return: True if everything went well. """ if self._destination.exists(): try: self._image_to_replace.unlink() except OSError: pass image = self._images[0] new_hash = utils.image.get_hash(image.path) return self._image_dao.update_image( image.id, self._destination, new_hash), _t('dialog.edit_image.error.') else: return False, _t('dialog.edit_image.error.file_does_not_exists')
def __init__(self, images: typ.List[model.Image], parent: QtW.QWidget = None): self._images = images super().__init__(parent=parent, title=_t('dialog.move_images.title'), modal=True, mode=_dialog_base.Dialog.OK_CANCEL) self._update_ui()
def __init__(self, parent: typ.Optional[QtW.QWidget] = None): """Creates the 'About' dialog. :param parent: The widget this dialog is attached to. """ super().__init__(parent=parent, title=_t('dialog.about.title', app_name=constants.APP_NAME), modal=True, mode=_dialog_base.Dialog.CLOSE)
def _apply(self) -> bool: ok = all(map(lambda t: t.apply(), self._tabs)) if not ok: utils.gui.show_error(_t('dialog.edit_tags.error.saving'), parent=self) else: self._apply_btn.setEnabled(False) super()._apply() return True
def _move_image( path: pathlib.Path, new_path: pathlib.Path) -> typ.Tuple[bool, typ.Optional[str]]: """Moves an image to a specific directory. :param path: Image’s path. :param new_path: Path to the new directory. :return: True if the image was moved. """ if new_path.exists(): return False, _t('dialog.edit_image.error.file_already_exists') if not path.exists(): return False, _t('dialog.edit_image.error.file_does_not_exist') try: shutil.move(path, new_path) except OSError: return False, _t('dialog.edit_image.error.failed_to_move_file') else: return True, None
def _add(self, image: model.Image, tags: typ.List[model.Tag], new_path: typ.Optional[pathlib.Path]) \ -> typ.Tuple[bool, typ.Optional[str]]: """Adds an image to the database. :param image: The image to add. :param tags: Image’s tags. :param new_path: If present the image will be moved in this directory. :return: True if everything went well. """ if image.path.exists(): ok, error = True, None if new_path: ok, error = self._move_image(image.path, new_path) if ok: ok = self._image_dao.add_image( image.path if new_path is None else new_path, tags) error = _t('dialog.edit_image.error.image_not_added') return ok, error else: return False, _t('dialog.edit_image.error.file_does_not_exists')
def _on_progress_update(self, progress: float, data: tuple, status: int): progress *= 100 self._progress_dialog.setValue(int(progress)) status_label = _t('popup.progress.status_label') if status == 1: status_ = _t('popup.progress.status.success') elif status == 2: status_ = _t('popup.progress.status.failed') else: status_ = _t('popup.progress.status.unknown') mode = data[0] if mode == _WorkerThread.PATHS: old_path, new_path = data[1:] self._progress_dialog.setLabelText( f'{progress:.2f} %\n{old_path}\n→ {new_path}\n{status_label} {status_}' ) elif mode == _WorkerThread.TAGS: image_path = data[1] self._progress_dialog.setLabelText( f'{progress:.2f} %\n{image_path}\n{status_label} {status_}')
def __init__(self, tags: typ.Iterable[str], state: OperationsDialog.FormState = None, parent: typ.Optional[QtW.QWidget] = None): self._state = state.copy() if state else self.State() self._tags = tags super().__init__(parent=parent, title=_t('dialog.perform_operations.title'), modal=True, mode=_dialog_base.Dialog.CLOSE) self._update_ui()