示例#1
0
    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
        )
示例#2
0
 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
示例#5
0
    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, ''
示例#6
0
    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
示例#7
0
    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)
示例#10
0
 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)
示例#11
0
 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, ''
示例#12
0
 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
示例#14
0
    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_}')
示例#16
0
    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())
示例#17
0
 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)
示例#19
0
 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))
示例#20
0
    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
        )
示例#21
0
    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
示例#22
0
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()
示例#23
0
    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()
示例#25
0
    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)
示例#26
0
    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
示例#27
0
    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
示例#28
0
    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()