mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-18 19:46:19 +02:00
refactor: code cleanup and fixes
This commit is contained in:
@@ -12,7 +12,6 @@ from PySide6 import QtCore
|
||||
from PySide6.QtGui import QShortcut
|
||||
|
||||
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.ffmpeg_status import FfmpegStatus, FfprobeStatus
|
||||
from tagstudio.qt.mixed.file_attributes import FileAttributeData
|
||||
from tagstudio.qt.views.preview_panel_view import PreviewPanelView
|
||||
@@ -24,8 +23,8 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class PreviewPanel(PreviewPanelView):
|
||||
def __init__(self, library: Library, driver: "QtDriver") -> None:
|
||||
super().__init__(library, driver)
|
||||
def __init__(self, driver: "QtDriver") -> None:
|
||||
super().__init__(driver)
|
||||
|
||||
self.__current_stats: FileAttributeData | None = None
|
||||
self._thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning)
|
||||
@@ -45,29 +44,29 @@ class PreviewPanel(PreviewPanelView):
|
||||
self.add_tag_action.activated.connect(self._add_tag_button.setFocus)
|
||||
self.add_tag_action.activated.connect(self._add_tag_button.click)
|
||||
|
||||
self.tag_search.done.connect(self.tag_added_callback)
|
||||
self.tag_search.tags_updated.connect(self.update_added_callback)
|
||||
self.tag_search_box.done.connect(self.tag_added_callback)
|
||||
self.tag_search_box.tags_updated.connect(self.update_added_callback)
|
||||
|
||||
def _add_field_button_callback(self) -> None:
|
||||
# self.__add_field_modal.show()
|
||||
pass
|
||||
|
||||
def _add_tag_button_callback(self) -> None:
|
||||
self.tag_search.added = self._containers.tags
|
||||
self.tag_search.view.search_field.setDisabled(False)
|
||||
self.tag_search.setHidden(False)
|
||||
self.tag_search_box.added = self._containers.tags
|
||||
self.tag_search_box.layout().search_field.setDisabled(False)
|
||||
self.tag_search_box.setHidden(False)
|
||||
self._add_tag_button.setHidden(True)
|
||||
self._add_field_button.setHidden(True)
|
||||
|
||||
def tag_added_callback(self):
|
||||
self.tag_search.setHidden(True)
|
||||
self.tag_search_box.setHidden(True)
|
||||
self._add_tag_button.setHidden(False)
|
||||
self._add_field_button.setHidden(False)
|
||||
|
||||
self._add_tag_button.setFocus()
|
||||
|
||||
def update_added_callback(self):
|
||||
self.tag_search.added = self._containers.tags
|
||||
self.tag_search_box.added = self._containers.tags
|
||||
|
||||
def __thumb_stats_updated_callback(self, filepath: Path, stats: FileAttributeData) -> None:
|
||||
if len(self._selected) != 1:
|
||||
@@ -91,11 +90,11 @@ class PreviewPanel(PreviewPanelView):
|
||||
@override
|
||||
def _set_selection_callback(self) -> None:
|
||||
with catch_warnings(record=True):
|
||||
self.field_search.field_template_chosen.disconnect()
|
||||
self.tag_search.item_chosen.disconnect()
|
||||
self.field_search_box.field_template_chosen.disconnect()
|
||||
self.tag_search_box.item_chosen.disconnect()
|
||||
|
||||
self.field_search.field_template_chosen.connect(self._add_field_to_selected)
|
||||
self.tag_search.item_chosen.connect(self._add_tag_to_selected)
|
||||
self.field_search_box.field_template_chosen.connect(self._add_field_to_selected)
|
||||
self.tag_search_box.item_chosen.connect(self._add_tag_to_selected)
|
||||
|
||||
def _add_field_to_selected(self, template: BaseFieldTemplate) -> None:
|
||||
self._containers.add_field_to_selected(template)
|
||||
|
||||
@@ -6,9 +6,10 @@ from typing import TYPE_CHECKING, Any, override
|
||||
import structlog
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtGui import QShowEvent
|
||||
from PySide6.QtWidgets import QGraphicsOpacityEffect, QVBoxLayout
|
||||
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.controllers.autofill_line_edit import QtCore, QtGui
|
||||
from tagstudio.qt.views.panel_modal import PanelWidget
|
||||
from tagstudio.qt.views.stylesheets.stylesheets import (
|
||||
autofill_line_edit_style,
|
||||
@@ -41,93 +42,56 @@ def _item_name(item: object) -> str:
|
||||
raise AttributeError()
|
||||
|
||||
|
||||
class SuggestBox[T](PanelWidget):
|
||||
class SuggestBox[T](QWidget):
|
||||
item_chosen = Signal(int)
|
||||
done = Signal()
|
||||
tags_updated = Signal()
|
||||
|
||||
def __init__(
|
||||
self, view: SuggestBoxView, exclude: list[int] | None = None, is_chooser: bool = True
|
||||
) -> None:
|
||||
def __init__(self, driver: "QtDriver", view: SuggestBoxView) -> None:
|
||||
super().__init__()
|
||||
self.view = view
|
||||
self.is_chooser = is_chooser
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._layout.addWidget(self.view)
|
||||
self._driver: QtDriver | None = None
|
||||
self.exclude: list[int] = exclude or []
|
||||
self.added: list[int] = exclude or []
|
||||
self.create_and_add_button_in_layout: bool = False
|
||||
self.limit = 5
|
||||
self.shift_held = False
|
||||
|
||||
# Limits
|
||||
self._unlimited_limit_item_label: str = "All Items"
|
||||
self._limit_items: list[tuple[str, int]] = [
|
||||
("25", 25),
|
||||
("50", 50),
|
||||
("100", 100),
|
||||
("250", 250),
|
||||
("500", 500),
|
||||
(self._unlimited_limit_item_label, -1),
|
||||
]
|
||||
self._default_limit_index: int = 0
|
||||
self._previous_limit_index: int = self._default_limit_index
|
||||
|
||||
# Items
|
||||
self._layout = view
|
||||
self._driver = driver
|
||||
self._limit = 5
|
||||
self._is_shift_held = False
|
||||
self._search_results: list[T] = []
|
||||
self.added: list[int] = []
|
||||
self.excluded: list[int] = []
|
||||
|
||||
self._create_and_add_button_label_key: str = ""
|
||||
self.connect_callbacks()
|
||||
self.setLayout(self._layout)
|
||||
self._connect_callbacks()
|
||||
|
||||
def connect_callbacks(self) -> None:
|
||||
self.view.search_field.textChanged.connect(self.on_search_query_changed)
|
||||
self.view.search_field.editingFinished.connect(self.test_editing_finished)
|
||||
self.view.search_field.return_pressed.connect(
|
||||
lambda: self.on_search_query_submitted(self.view.search_field.text())
|
||||
def _connect_callbacks(self) -> None:
|
||||
self._layout.search_field.textChanged.connect(self.on_search_query_changed)
|
||||
self._layout.search_field.editingFinished.connect(self.test_editing_finished)
|
||||
self._layout.search_field.return_pressed.connect(
|
||||
lambda: self.on_search_query_submitted(self._layout.search_field.text())
|
||||
)
|
||||
self.view.search_field.shift_return_pressed.connect(
|
||||
self._layout.search_field.shift_return_pressed.connect(
|
||||
lambda: self.on_search_query_submitted(
|
||||
self.view.search_field.text(), always_create=True
|
||||
self._layout.search_field.text(), always_create=True
|
||||
)
|
||||
)
|
||||
|
||||
self.view.search_field.shift_holding.connect(lambda held: self.on_shift_held(held))
|
||||
self._layout.search_field.shift_holding.connect(lambda held: self._on_shift_held(held))
|
||||
|
||||
def on_shift_held(self, held: bool):
|
||||
def _on_shift_held(self, held: bool):
|
||||
if held:
|
||||
self.shift_held = True
|
||||
self._is_shift_held = True
|
||||
opacity_effect = QGraphicsOpacityEffect(self)
|
||||
opacity_effect.setOpacity(0.3)
|
||||
if self.view.content_layout.count() > 0:
|
||||
self.view.content_layout.itemAt(0).widget().setGraphicsEffect(opacity_effect)
|
||||
if self._layout.content_layout.count() > 0:
|
||||
self._layout.content_layout.itemAt(0).widget().setGraphicsEffect(opacity_effect)
|
||||
else:
|
||||
self.shift_held = False
|
||||
if self.view.content_layout.count() > 0:
|
||||
self.view.content_layout.itemAt(0).widget().setGraphicsEffect(None) # pyright: ignore[reportArgumentType]
|
||||
|
||||
def focus_search_box(self, select_all: bool = False) -> None:
|
||||
if not self.isHidden():
|
||||
self.view.search_field.setFocus()
|
||||
if select_all:
|
||||
self.view.search_field.selectAll()
|
||||
self._is_shift_held = False
|
||||
if self._layout.content_layout.count() > 0:
|
||||
self._layout.content_layout.itemAt(0).widget().setGraphicsEffect(None) # pyright: ignore[reportArgumentType]
|
||||
|
||||
def clear_search_query(self) -> None:
|
||||
self.view.search_field.setText("")
|
||||
self._layout.search_field.setText("")
|
||||
|
||||
def get_item_widget(self, index: int, library: Library) -> Any: # pyright: ignore[reportExplicitAny]
|
||||
return self.get_item_widget(index, library)
|
||||
|
||||
def set_driver(self, driver: "QtDriver") -> None:
|
||||
self._driver = driver
|
||||
|
||||
def _get_previous_limit(self) -> tuple[str, int]:
|
||||
return self._limit_items[self._previous_limit_index]
|
||||
|
||||
def _get_max_limit(self) -> int:
|
||||
raise NotImplementedError()
|
||||
|
||||
def on_search_query_changed(self, query: str) -> None:
|
||||
self.update_items(query)
|
||||
|
||||
@@ -136,38 +100,35 @@ class SuggestBox[T](PanelWidget):
|
||||
logger.info("Query submitted")
|
||||
if not query:
|
||||
self.done.emit()
|
||||
self.disappear()
|
||||
self.hide_and_reset()
|
||||
return
|
||||
elif not self.isHidden():
|
||||
self.view.search_field.setFocus()
|
||||
self._layout.search_field.setFocus()
|
||||
|
||||
# Create and add item if no search results
|
||||
if (len(self._search_results) <= 0) or always_create:
|
||||
self.on_item_create(add_to_entry=True)
|
||||
elif self.is_chooser:
|
||||
self.on_item_create()
|
||||
else:
|
||||
self._on_item_chosen(self._search_results[0])
|
||||
|
||||
self.clear_search_query()
|
||||
self.update_items()
|
||||
|
||||
def on_item_create(self, add_to_entry: bool = False) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
def on_item_create(self) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def on_item_edit(self, item: T) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
|
||||
def _on_item_remove(self, item: T) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
|
||||
def _on_item_chosen(self, item: T) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
|
||||
def _is_excluded(self, item: T) -> bool:
|
||||
return _item_id(item) in self.exclude
|
||||
return _item_id(item) in self.excluded
|
||||
|
||||
def update_items(self, query: str | None = None) -> None:
|
||||
"""Update the item list given a search query."""
|
||||
logger.info("[SearchPanel] Updating items", limit=self.limit)
|
||||
logger.info("[SearchPanel] Updating items", limit=self._limit)
|
||||
|
||||
# Get results for the search query
|
||||
query_lower = "" if not query else query.lower()
|
||||
@@ -199,24 +160,24 @@ class SuggestBox[T](PanelWidget):
|
||||
all_results.remove(item)
|
||||
all_results = all_results + already_added
|
||||
|
||||
if self.limit > 0:
|
||||
all_results = all_results[: self.limit]
|
||||
if self._limit > 0:
|
||||
all_results = all_results[: self._limit]
|
||||
|
||||
self._search_results = all_results
|
||||
logger.info("[SearchPanel] Search results", results=self._search_results)
|
||||
|
||||
for i in range(0, self.limit):
|
||||
for i in range(0, self._limit):
|
||||
item: T | None = all_results[i] if i < len(all_results) else None
|
||||
self.set_item_widget(item=item, index=i)
|
||||
|
||||
if self.view.content_layout.isEmpty():
|
||||
self.view.scroll_area.setHidden(True)
|
||||
self.view.content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.view.search_field.setStyleSheet(autofill_line_edit_style())
|
||||
if self._layout.content_layout.isEmpty():
|
||||
self._layout.scroll_area.setHidden(True)
|
||||
self._layout.content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._layout.search_field.setStyleSheet(autofill_line_edit_style())
|
||||
else:
|
||||
self.view.scroll_area.setHidden(False)
|
||||
self.view.content_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.view.search_field.setStyleSheet(autofill_line_edit_top_style())
|
||||
self._layout.scroll_area.setHidden(False)
|
||||
self._layout.content_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self._layout.search_field.setStyleSheet(autofill_line_edit_top_style())
|
||||
|
||||
def search_items(self, query: str) -> tuple[list[T], list[T]]: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
@@ -224,27 +185,41 @@ class SuggestBox[T](PanelWidget):
|
||||
def set_item_widget(self, item: T | None, index: int) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QShowEvent) -> None:
|
||||
self.update_items()
|
||||
self.on_shift_held(held=False)
|
||||
self.clear_search_query()
|
||||
return super().showEvent(event)
|
||||
|
||||
def test_editing_finished(self):
|
||||
logger.info("Editing finished")
|
||||
self.tags_updated.emit()
|
||||
if self.view.search_field.text() == "":
|
||||
if self._layout.search_field.text() == "":
|
||||
self.done.emit()
|
||||
self.disappear()
|
||||
self.hide_and_reset()
|
||||
|
||||
def disappear(self):
|
||||
def hide_and_reset(self):
|
||||
self.hide()
|
||||
self.view.search_field.setDisabled(True)
|
||||
self.on_shift_held(held=False)
|
||||
self._layout.search_field.setDisabled(True)
|
||||
self._on_shift_held(held=False)
|
||||
|
||||
def create_item(self, edit_item_panel: PanelWidget, choose_item: bool = False) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
def create_item_from_modal(self, edit_item_panel: PanelWidget) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
|
||||
def edit_item(self, edit_item_panel: PanelWidget) -> None: # pyright: ignore[reportUnusedParameter]
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QShowEvent) -> None:
|
||||
self.update_items()
|
||||
self._on_shift_held(held=False)
|
||||
self.clear_search_query()
|
||||
return super().showEvent(event)
|
||||
|
||||
@override
|
||||
def layout(self) -> SuggestBoxView:
|
||||
return self._layout
|
||||
|
||||
@override
|
||||
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
|
||||
# When Escape is pressed, focus back on the search box.
|
||||
if event.key() in {
|
||||
QtCore.Qt.Key.Key_Escape,
|
||||
QtCore.Qt.Key.Key_Enter,
|
||||
QtCore.Qt.Key.Key_Return,
|
||||
}:
|
||||
self.hide_and_reset()
|
||||
|
||||
@@ -2,49 +2,38 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import typing
|
||||
from typing import override
|
||||
from warnings import catch_warnings
|
||||
|
||||
import structlog
|
||||
from PySide6.QtWidgets import QGraphicsOpacityEffect, QMessageBox, QSizePolicy, QWidget
|
||||
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
|
||||
|
||||
from tagstudio.core.constants import RESERVED_TAG_END, RESERVED_TAG_START
|
||||
from tagstudio.core.library.alchemy.enums import BrowsingState
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag
|
||||
from tagstudio.qt.controllers.suggest_box_controller import SuggestBox
|
||||
from tagstudio.qt.mixed.build_tag import BuildTagPanel
|
||||
from tagstudio.qt.mixed.tag_widget import TagWidget
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.panel_modal import PanelModal, PanelWidget
|
||||
from tagstudio.qt.views.tag_suggest_box_view import TagSuggestBoxView
|
||||
from tagstudio.qt.views.suggest_box_view import SuggestBoxView
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class TagSuggestBox(SuggestBox[Tag]):
|
||||
def __init__(
|
||||
self,
|
||||
library: Library,
|
||||
exclude: list[int] | None = None,
|
||||
is_tag_chooser: bool = True,
|
||||
view: TagSuggestBoxView | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
view=view or TagSuggestBoxView(is_tag_chooser),
|
||||
exclude=exclude,
|
||||
is_chooser=is_tag_chooser,
|
||||
)
|
||||
self.__lib = library
|
||||
|
||||
self._unlimited_limit_item_label = Translations["tag.all_tags"]
|
||||
self._create_and_add_button_label_key = "tag.create_add"
|
||||
def __init__(self, driver: "QtDriver", view: SuggestBoxView | None = None):
|
||||
super().__init__(driver, view=view or SuggestBoxView())
|
||||
self._driver = driver
|
||||
self._lib = self._driver.lib
|
||||
self._edit_on_create = self._driver.settings.edit_tag_on_create
|
||||
|
||||
@override
|
||||
def _get_max_limit(self) -> int:
|
||||
return len(self.__lib.tags)
|
||||
|
||||
@override
|
||||
def on_item_create(self, add_to_entry: bool = False) -> None:
|
||||
def on_item_create(self) -> None:
|
||||
"""Opens panel to create a new tag and optionally add it to an entry.
|
||||
|
||||
Populates name field using current search query.
|
||||
@@ -52,67 +41,36 @@ class TagSuggestBox(SuggestBox[Tag]):
|
||||
Args:
|
||||
add_to_entry (bool): Should this item be added to currently selected entries?
|
||||
"""
|
||||
# TODO: Move this to a top-level import
|
||||
query: str = self._layout.search_field.text()
|
||||
|
||||
query: str = self.view.search_field.text()
|
||||
if self._edit_on_create:
|
||||
panel: BuildTagPanel = BuildTagPanel(self._lib)
|
||||
modal: PanelModal = PanelModal(
|
||||
panel, Translations["tag.new"], Translations["tag.new"], is_savable=True
|
||||
)
|
||||
if query.strip():
|
||||
panel.name_field.setText(query)
|
||||
|
||||
# panel: BuildTagPanel = BuildTagPanel(self.__lib)
|
||||
# modal: PanelModal = PanelModal(
|
||||
# panel,
|
||||
# Translations["tag.new"],
|
||||
# Translations["tag.add"] if add_to_entry else Translations["tag.new"],
|
||||
# is_savable=True,
|
||||
# )
|
||||
|
||||
# if query.strip():
|
||||
# panel.name_field.setText(query)
|
||||
|
||||
# modal.saved.connect(lambda: self.create_item(panel, choose_item=add_to_entry))
|
||||
# modal.show()
|
||||
tag = Tag(name=query)
|
||||
self.__lib.add_tag(tag)
|
||||
if add_to_entry:
|
||||
modal.saved.connect(lambda: self.create_item_from_modal(panel))
|
||||
modal.show()
|
||||
else:
|
||||
tag = Tag(name=query)
|
||||
self._lib.add_tag(tag)
|
||||
self._on_item_chosen(tag)
|
||||
self.clear_search_query()
|
||||
|
||||
@override
|
||||
def on_item_edit(self, item: Tag) -> None:
|
||||
# TODO: Move this to a top-level import
|
||||
from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports
|
||||
|
||||
edit_tag_panel: BuildTagPanel = BuildTagPanel(self.__lib, tag=item)
|
||||
edit_tag_panel: BuildTagPanel = BuildTagPanel(self._lib, tag=item)
|
||||
edit_tag_modal: PanelModal = PanelModal(
|
||||
edit_tag_panel,
|
||||
self.__lib.tag_display_name(item),
|
||||
self._lib.tag_display_name(item),
|
||||
Translations["tag.edit"],
|
||||
is_savable=True,
|
||||
)
|
||||
edit_tag_modal.saved.connect(lambda: self.edit_item(edit_tag_panel))
|
||||
edit_tag_modal.show()
|
||||
|
||||
@override
|
||||
def _on_item_remove(self, item: Tag) -> None:
|
||||
if self.is_chooser:
|
||||
return
|
||||
|
||||
if item.id in range(RESERVED_TAG_START, RESERVED_TAG_END):
|
||||
return
|
||||
|
||||
message_box = QMessageBox(
|
||||
QMessageBox.Icon.Question,
|
||||
Translations["tag.remove"],
|
||||
Translations.format("tag.confirm_delete", tag_name=self.__lib.tag_display_name(item)),
|
||||
QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
|
||||
)
|
||||
|
||||
result = message_box.exec()
|
||||
|
||||
if result != QMessageBox.StandardButton.Ok:
|
||||
return
|
||||
|
||||
self.__lib.remove_tag(item.id)
|
||||
self.update_items(self.view.search_field.text())
|
||||
|
||||
@override
|
||||
def _on_item_chosen(self, item: Tag) -> None:
|
||||
self.item_chosen.emit(item.id)
|
||||
@@ -121,80 +79,55 @@ class TagSuggestBox(SuggestBox[Tag]):
|
||||
@override
|
||||
def search_items(self, query: str) -> tuple[list[Tag], list[Tag]]:
|
||||
if query != "":
|
||||
# return self.__lib.search_tags(name=query, limit=self._get_limit()[1])
|
||||
return self.__lib.search_tags(name=query, limit=0)
|
||||
return self._lib.search_tags(name=query, limit=0)
|
||||
else:
|
||||
return ([], [])
|
||||
|
||||
@override
|
||||
def set_item_widget(self, item: Tag | None, index: int) -> None:
|
||||
"""Set the tag of a tag widget at a specific index."""
|
||||
tag_widget: TagWidget = self.get_item_widget(index, self.__lib)
|
||||
tag_widget: TagWidget = self.get_item_widget(index, self._lib)
|
||||
tag_widget.has_remove = False
|
||||
tag_widget.set_tag(item)
|
||||
tag_widget.setHidden(item is None)
|
||||
opacity_effect = QGraphicsOpacityEffect(self)
|
||||
opacity_effect.setOpacity(0.3)
|
||||
if item and item.id in self.added:
|
||||
opacity_effect = QGraphicsOpacityEffect(self)
|
||||
opacity_effect.setOpacity(0.3)
|
||||
tag_widget.setGraphicsEffect(opacity_effect)
|
||||
else:
|
||||
tag_widget.setGraphicsEffect(None) # pyright: ignore[reportArgumentType]
|
||||
|
||||
if item is None:
|
||||
return
|
||||
assert item is not None
|
||||
|
||||
tag_widget.has_remove = not self.is_chooser and item.id not in range(
|
||||
RESERVED_TAG_START, RESERVED_TAG_END
|
||||
)
|
||||
|
||||
# Disconnect previous callbacks
|
||||
with catch_warnings(record=True):
|
||||
tag_widget.on_edit.disconnect()
|
||||
tag_widget.on_remove.disconnect()
|
||||
tag_widget.bg_button.clicked.disconnect()
|
||||
tag_widget.search_for_tag_action.triggered.disconnect()
|
||||
|
||||
# Connect callbacks
|
||||
tag_widget.on_edit.connect(lambda edit_tag=item: self.on_item_edit(edit_tag))
|
||||
tag_widget.on_remove.connect(lambda remove_tag=item: self._on_item_remove(remove_tag))
|
||||
if self.is_chooser:
|
||||
tag_widget.bg_button.clicked.connect(
|
||||
lambda checked=False, tag=item: self._on_item_chosen(tag)
|
||||
)
|
||||
else:
|
||||
tag_widget.bg_button.clicked.connect(
|
||||
lambda checked=False, edit_tag=item: self.on_item_edit(edit_tag)
|
||||
)
|
||||
|
||||
# Connect search action
|
||||
if self._driver is not None:
|
||||
tag_widget.search_for_tag_action.triggered.connect(
|
||||
lambda checked=False, tag_id=item.id: self.search_for_tag(tag_id)
|
||||
)
|
||||
tag_widget.search_for_tag_action.setEnabled(True)
|
||||
else:
|
||||
logger.warning(
|
||||
"[TagSearchPanel] No driver was set for this TagSearchPanel. Was this on purpose?"
|
||||
)
|
||||
tag_widget.search_for_tag_action.setEnabled(False)
|
||||
tag_widget.bg_button.clicked.connect(
|
||||
lambda checked=False, tag=item: self._on_item_chosen(tag)
|
||||
)
|
||||
tag_widget.search_for_tag_action.triggered.connect(
|
||||
lambda checked=False, tag_id=item.id: self.search_for_tag(tag_id)
|
||||
)
|
||||
tag_widget.search_for_tag_action.setEnabled(True)
|
||||
|
||||
@override
|
||||
def create_item(self, edit_item_panel: PanelWidget, choose_item: bool = False) -> None:
|
||||
# TODO: Move this to a top-level import
|
||||
from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports
|
||||
|
||||
def create_item_from_modal(self, edit_item_panel: PanelWidget) -> None:
|
||||
if isinstance(edit_item_panel, BuildTagPanel):
|
||||
tag: Tag = edit_item_panel.build_tag()
|
||||
self.__lib.add_tag(
|
||||
self._lib.add_tag(
|
||||
tag, parent_ids=edit_item_panel.parent_ids, aliases=edit_item_panel.aliases
|
||||
)
|
||||
|
||||
if choose_item:
|
||||
self._on_item_chosen(tag)
|
||||
self.clear_search_query()
|
||||
self._on_item_chosen(tag)
|
||||
self.clear_search_query()
|
||||
|
||||
edit_item_panel.hide()
|
||||
self.on_search_query_changed(self.view.search_field.text())
|
||||
self.on_search_query_changed(self._layout.search_field.text())
|
||||
|
||||
@override
|
||||
def edit_item(self, edit_item_panel: PanelWidget) -> None:
|
||||
@@ -204,17 +137,14 @@ class TagSuggestBox(SuggestBox[Tag]):
|
||||
if not isinstance(edit_item_panel, BuildTagPanel):
|
||||
return
|
||||
|
||||
self.__lib.update_tag(
|
||||
self._lib.update_tag(
|
||||
tag=edit_item_panel.build_tag(),
|
||||
parent_ids=edit_item_panel.parent_ids,
|
||||
aliases=edit_item_panel.aliases,
|
||||
)
|
||||
self.update_items(self.view.search_field.text())
|
||||
self.update_items(self._layout.search_field.text())
|
||||
|
||||
def search_for_tag(self, tag_id: int) -> None:
|
||||
if self._driver is None:
|
||||
return
|
||||
|
||||
self._driver.main_window.search_field.setText(f"tag_id:{tag_id}")
|
||||
self._driver.update_browsing_state(
|
||||
BrowsingState.from_tag_id(tag_id, self._driver.browsing_history.current)
|
||||
@@ -224,50 +154,13 @@ class TagSuggestBox(SuggestBox[Tag]):
|
||||
def get_item_widget(self, index: int, library: Library | None) -> TagWidget:
|
||||
"""Gets the item widget at a specific index."""
|
||||
# Create any new item widgets needed up to the given index
|
||||
if self.view.content_layout.count() <= index:
|
||||
# opacity_effect = QGraphicsOpacityEffect(self)
|
||||
# opacity_effect.setOpacity(0.3)
|
||||
while self.view.content_layout.count() <= index:
|
||||
if self._layout.content_layout.count() <= index:
|
||||
while self._layout.content_layout.count() <= index:
|
||||
tag_widget = TagWidget(tag=None, has_edit=True, has_remove=True, library=library)
|
||||
tag_widget.on_remove.connect(self.update_items)
|
||||
tag_widget.bg_button.setSizePolicy(
|
||||
QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum
|
||||
)
|
||||
tag_widget.setHidden(True)
|
||||
# if index > 0:
|
||||
# tag_widget.setGraphicsEffect(opacity_effect)
|
||||
self.view.content_layout.addWidget(tag_widget)
|
||||
self._layout.content_layout.addWidget(tag_widget)
|
||||
|
||||
tag_widget: QWidget = self.view.content_layout.itemAt(index).widget()
|
||||
tag_widget: QWidget = self._layout.content_layout.itemAt(index).widget()
|
||||
assert isinstance(tag_widget, TagWidget)
|
||||
return tag_widget
|
||||
|
||||
# @override
|
||||
# def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
|
||||
# # When Escape is pressed, focus back on the search box.
|
||||
# # If focus is already on the search box, close the modal.
|
||||
# pass
|
||||
# # if event.key() in {QtCore.Qt.Key.Key_Escape, QtCore.Qt.Key.Key_Backspace, }:
|
||||
# # if self.search_field.hasFocus():
|
||||
|
||||
# # self.hide()
|
||||
|
||||
# @override
|
||||
# def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
|
||||
# # When Escape is pressed, focus back on the search box.
|
||||
# # If focus is already on the search box, close the modal.
|
||||
# # if event.key() == QtCore.Qt.Key.Key_Escape:
|
||||
# # if self.search_field.hasFocus():
|
||||
# # self.hide()
|
||||
# logger.info(event.key)
|
||||
# if event.key() in {
|
||||
# QtCore.Qt.Key.Key_Escape,
|
||||
# QtCore.Qt.Key.Key_Enter,
|
||||
# QtCore.Qt.Key.Key_Return,
|
||||
# }:
|
||||
# if self.search_field.hasFocus():
|
||||
# logger.info("Hiding")
|
||||
# self.hide()
|
||||
# elif event.key() in {QtCore.Qt.Key.Key_Backspace, QtCore.Qt.Key.Key_Delete}:
|
||||
# if self.search_field.hasFocus() and self.search_field.text() == "":
|
||||
# # self.hide()
|
||||
|
||||
@@ -77,6 +77,7 @@ class GlobalSettings(BaseModel):
|
||||
theme: Theme = Field(default=Theme.SYSTEM)
|
||||
splash: Splash = Field(default=Splash.DEFAULT)
|
||||
windows_start_command: bool = Field(default=False)
|
||||
edit_tag_on_create: bool = Field(default=False)
|
||||
|
||||
date_format: str = Field(default="%x")
|
||||
hour_format: bool = Field(default=True)
|
||||
|
||||
@@ -701,7 +701,7 @@ class MainWindow(QMainWindow):
|
||||
self.content_splitter.addWidget(self.entry_list_container)
|
||||
|
||||
def setup_preview_panel(self, driver: "QtDriver"):
|
||||
self.preview_panel = PreviewPanel(driver.lib, driver)
|
||||
self.preview_panel = PreviewPanel(driver)
|
||||
self.content_splitter.addWidget(self.preview_panel)
|
||||
|
||||
def setup_status_bar(self):
|
||||
|
||||
@@ -13,7 +13,6 @@ from PySide6.QtGui import QDesktopServices
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QSplitter, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.constants import FFMPEG_HELP_URL
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchPanel
|
||||
@@ -26,7 +25,7 @@ from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.field_template_search_panel_view import FieldTemplateSearchPanelView
|
||||
from tagstudio.qt.views.stylesheets.stylesheets import button_style, preview_warning_style
|
||||
from tagstudio.qt.views.tag_suggest_box_view import TagSuggestBoxView
|
||||
from tagstudio.qt.views.suggest_box_view import SuggestBoxView
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
@@ -35,31 +34,31 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class PreviewPanelView(QWidget):
|
||||
lib: Library
|
||||
_selected: list[int]
|
||||
|
||||
def __init__(self, library: Library, driver: "QtDriver") -> None:
|
||||
def __init__(self, driver: "QtDriver") -> None:
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self._lib = driver.lib
|
||||
rm = ResourceManager()
|
||||
|
||||
self.field_search: FieldTemplateSearchPanel = FieldTemplateSearchPanel(
|
||||
library,
|
||||
self.field_search_box: FieldTemplateSearchPanel = FieldTemplateSearchPanel(
|
||||
self._lib,
|
||||
is_field_template_chooser=True,
|
||||
view=FieldTemplateSearchPanelView(is_field_template_chooser=True),
|
||||
)
|
||||
self.tag_search = TagSuggestBox(
|
||||
library,
|
||||
is_tag_chooser=True,
|
||||
view=TagSuggestBoxView(is_tag_chooser=True),
|
||||
)
|
||||
self.tag_search.set_driver(driver)
|
||||
self.tag_search.hide()
|
||||
|
||||
self._thumb = PreviewThumb(self.lib, driver)
|
||||
self._file_attrs = FileAttributes(self.lib, driver)
|
||||
tag_placeholder_text = (
|
||||
f"{Translations['home.search_tags']} {Translations['home.search.how_to_exit']}"
|
||||
)
|
||||
self.tag_search_box = TagSuggestBox(
|
||||
driver, view=SuggestBoxView(placeholder=tag_placeholder_text)
|
||||
)
|
||||
self.tag_search_box.hide()
|
||||
|
||||
self._thumb = PreviewThumb(self._lib, driver)
|
||||
self._file_attrs = FileAttributes(self._lib, driver)
|
||||
self._containers = FieldContainers(
|
||||
self.lib, driver
|
||||
self._lib, driver
|
||||
) # TODO: this should be name mangled, but is still needed on the controller side atm
|
||||
|
||||
# Visual Preview
|
||||
@@ -124,7 +123,7 @@ class PreviewPanelView(QWidget):
|
||||
|
||||
add_buttons_layout.addWidget(self._add_tag_button)
|
||||
add_buttons_layout.addWidget(self._add_field_button)
|
||||
add_buttons_layout.addWidget(self.tag_search)
|
||||
add_buttons_layout.addWidget(self.tag_search_box)
|
||||
# add_buttons_layout.addWidget(self.field_search)
|
||||
|
||||
preview_layout.addWidget(self._thumb)
|
||||
@@ -166,14 +165,14 @@ class PreviewPanelView(QWidget):
|
||||
self._add_field_button.setEnabled(False)
|
||||
self._add_tag_button.setHidden(False)
|
||||
self._add_field_button.setHidden(False)
|
||||
self.tag_search.disappear()
|
||||
self.tag_search_box.hide_and_reset()
|
||||
|
||||
# One Item Selected
|
||||
elif len(selected) == 1:
|
||||
entry_id = selected[0]
|
||||
entry: Entry = unwrap(self.lib.get_entry(entry_id))
|
||||
entry: Entry = unwrap(self._lib.get_entry(entry_id))
|
||||
|
||||
filepath: Path = unwrap(self.lib.library_dir) / entry.path
|
||||
filepath: Path = unwrap(self._lib.library_dir) / entry.path
|
||||
if filepath != self._thumb.current_file:
|
||||
self.__current_stats = None
|
||||
|
||||
@@ -190,7 +189,7 @@ class PreviewPanelView(QWidget):
|
||||
self._add_field_button.setEnabled(True)
|
||||
self._add_tag_button.setHidden(False)
|
||||
self._add_field_button.setHidden(False)
|
||||
self.tag_search.disappear()
|
||||
self.tag_search_box.hide_and_reset()
|
||||
|
||||
# Multiple Selected Items
|
||||
elif len(selected) > 1:
|
||||
@@ -207,7 +206,7 @@ class PreviewPanelView(QWidget):
|
||||
self._add_field_button.setEnabled(True)
|
||||
self._add_tag_button.setHidden(False)
|
||||
self._add_field_button.setHidden(False)
|
||||
self.tag_search.disappear()
|
||||
self.tag_search_box.hide_and_reset()
|
||||
|
||||
except Exception as e:
|
||||
logger.error("[Preview Panel] Error updating selection", error=e)
|
||||
|
||||
@@ -26,38 +26,15 @@ if TYPE_CHECKING:
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class SuggestBoxView(QWidget):
|
||||
def __init__(self, is_chooser: bool) -> None:
|
||||
self.is_chooser: bool = is_chooser
|
||||
class SuggestBoxView(QVBoxLayout):
|
||||
def __init__(self, placeholder: str = "") -> None:
|
||||
super().__init__()
|
||||
|
||||
self._root_layout = QVBoxLayout(self)
|
||||
self._root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._root_layout.setSpacing(0)
|
||||
|
||||
# Scroll area
|
||||
self.contents = QWidget()
|
||||
|
||||
self.content_layout = QHBoxLayout(self.contents)
|
||||
self.content_layout.setSpacing(6)
|
||||
self.content_layout.setAlignment(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft)
|
||||
self.content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.scroll_area_container = QWidget()
|
||||
self.scroll_area_container.setObjectName("container")
|
||||
self.scroll_area_container_layout = QHBoxLayout(self.scroll_area_container)
|
||||
self.scroll_area_container_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.scroll_area_container_layout.setSpacing(0)
|
||||
self.scroll_area_container.setStyleSheet(autofill_scroll_top_style("container"))
|
||||
|
||||
# Search field
|
||||
self.search_field = AutofillLineEdit(self.scroll_area_container)
|
||||
self.search_field.setStyleSheet(autofill_line_edit_style())
|
||||
self.search_field.setObjectName("search_field")
|
||||
self.search_field.setMinimumHeight(28)
|
||||
# Init layout
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setSpacing(0)
|
||||
|
||||
# HACK: The transparent border allows for the focus border color to
|
||||
# still show above the tags at the edges. Sort of.
|
||||
# still show above the tags at the edges... sort of (overlaps on left when h-scrolling)
|
||||
scroll_area_style = """
|
||||
QScrollArea{
|
||||
background: transparent;
|
||||
@@ -71,11 +48,22 @@ class SuggestBoxView(QWidget):
|
||||
}
|
||||
"""
|
||||
|
||||
# Autocomplete ScrollArea
|
||||
contents = QWidget()
|
||||
self.content_layout = QHBoxLayout(contents)
|
||||
self.content_layout.setSpacing(6)
|
||||
self.content_layout.setAlignment(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft)
|
||||
self.content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
scroll_area_container = QWidget()
|
||||
scroll_area_container.setObjectName("container")
|
||||
scroll_area_container_layout = QHBoxLayout(scroll_area_container)
|
||||
scroll_area_container_layout.setContentsMargins(0, 0, 0, 0)
|
||||
scroll_area_container_layout.setSpacing(0)
|
||||
scroll_area_container.setStyleSheet(autofill_scroll_top_style("container"))
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setFocusProxy(self.search_field)
|
||||
self.scroll_area.setStyleSheet(scroll_area_style)
|
||||
self.scroll_area_container_layout.addWidget(self.scroll_area)
|
||||
self.scroll_area.setWidget(self.contents)
|
||||
scroll_area_container_layout.addWidget(self.scroll_area)
|
||||
self.scroll_area.setWidget(contents)
|
||||
self.scroll_area.setMaximumHeight(28)
|
||||
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.scroll_area.verticalScrollBar().setEnabled(False)
|
||||
@@ -86,5 +74,14 @@ class SuggestBoxView(QWidget):
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.scroll_area.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
|
||||
|
||||
self._root_layout.addWidget(self.scroll_area_container)
|
||||
self._root_layout.addWidget(self.search_field)
|
||||
# Search Field
|
||||
self.search_field = AutofillLineEdit(scroll_area_container)
|
||||
self.search_field.setStyleSheet(autofill_line_edit_style())
|
||||
self.search_field.setObjectName("search_field")
|
||||
self.search_field.setMinimumHeight(28)
|
||||
self.search_field.setPlaceholderText(placeholder)
|
||||
self.scroll_area.setFocusProxy(self.search_field)
|
||||
|
||||
# Finalize layout
|
||||
self.addWidget(scroll_area_container)
|
||||
self.addWidget(self.search_field)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.suggest_box_view import SuggestBoxView
|
||||
|
||||
|
||||
# TODO: Get rid of this class
|
||||
class TagSuggestBoxView(SuggestBoxView):
|
||||
def __init__(self, is_tag_chooser: bool) -> None:
|
||||
super().__init__(is_tag_chooser)
|
||||
placeholder = (
|
||||
f"{Translations['home.search_tags']} {Translations['home.search.how_to_exit']}"
|
||||
)
|
||||
self.search_field.setPlaceholderText(placeholder)
|
||||
@@ -2,15 +2,14 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.controllers.preview_panel_controller import PreviewPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
def test_update_selection_empty(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_update_selection_empty(qt_driver: QtDriver):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Clear the library selection (selecting 1 then unselecting 1)
|
||||
qt_driver.toggle_item_selection(1, append=False, bridge=False)
|
||||
@@ -22,8 +21,8 @@ def test_update_selection_empty(qt_driver: QtDriver, library: Library):
|
||||
assert container.isHidden()
|
||||
|
||||
|
||||
def test_update_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_update_selection_single(qt_driver: QtDriver, entry_full: Entry):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Select the single entry
|
||||
qt_driver.toggle_item_selection(entry_full.id, append=False, bridge=False)
|
||||
@@ -34,10 +33,10 @@ def test_update_selection_single(qt_driver: QtDriver, library: Library, entry_fu
|
||||
assert not container.isHidden()
|
||||
|
||||
|
||||
def test_update_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
def test_update_selection_multiple(qt_driver: QtDriver):
|
||||
# TODO: Implement mixed field editing. Currently these containers will be hidden,
|
||||
# same as the empty selection behavior.
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Select the multiple entries
|
||||
qt_driver.toggle_item_selection(1, append=False, bridge=False)
|
||||
@@ -49,8 +48,8 @@ def test_update_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
assert container.isHidden()
|
||||
|
||||
|
||||
def test_add_tag_to_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_add_tag_to_selection_single(qt_driver: QtDriver, entry_full: Entry):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
assert {t.id for t in entry_full.tags} == {1000}
|
||||
|
||||
@@ -62,12 +61,12 @@ def test_add_tag_to_selection_single(qt_driver: QtDriver, library: Library, entr
|
||||
panel.field_containers_widget.add_tags_to_selected(2000)
|
||||
|
||||
# Then reload entry
|
||||
refreshed_entry: Entry = next(library.all_entries(with_joins=True))
|
||||
refreshed_entry: Entry = next(qt_driver.lib.all_entries(with_joins=True))
|
||||
assert {t.id for t in refreshed_entry.tags} == {1000, 2000}
|
||||
|
||||
|
||||
def test_add_same_tag_to_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_add_same_tag_to_selection_single(qt_driver: QtDriver, entry_full: Entry):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
assert {t.id for t in entry_full.tags} == {1000}
|
||||
|
||||
@@ -79,13 +78,13 @@ def test_add_same_tag_to_selection_single(qt_driver: QtDriver, library: Library,
|
||||
panel.field_containers_widget.add_tags_to_selected(1000)
|
||||
|
||||
# Then reload entry
|
||||
refreshed_entry = next(library.all_entries(with_joins=True))
|
||||
refreshed_entry = next(qt_driver.lib.all_entries(with_joins=True))
|
||||
assert {t.id for t in refreshed_entry.tags} == {1000}
|
||||
|
||||
|
||||
def test_add_tag_to_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
all_entries = library.all_entries(with_joins=True)
|
||||
def test_add_tag_to_selection_multiple(qt_driver: QtDriver):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
all_entries = qt_driver.lib.all_entries(with_joins=True)
|
||||
|
||||
# We want to verify that tag 1000 is on some, but not all entries already.
|
||||
tag_present_on_some: bool = False
|
||||
@@ -101,7 +100,7 @@ def test_add_tag_to_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
assert tag_absent_on_some
|
||||
|
||||
# Select the multiple entries
|
||||
for i, e in enumerate(library.all_entries(with_joins=True), start=0):
|
||||
for i, e in enumerate(qt_driver.lib.all_entries(with_joins=True), start=0):
|
||||
qt_driver.toggle_item_selection(e.id, append=(True if i == 0 else False), bridge=False) # noqa: SIM210
|
||||
panel.set_selection(qt_driver.selected)
|
||||
|
||||
@@ -109,7 +108,7 @@ def test_add_tag_to_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
panel.field_containers_widget.add_tags_to_selected(1000)
|
||||
|
||||
# Then reload all entries and recheck the presence of tag 1000
|
||||
refreshed_entries = library.all_entries(with_joins=True)
|
||||
refreshed_entries = qt_driver.lib.all_entries(with_joins=True)
|
||||
tag_present_on_some = False
|
||||
tag_absent_on_some = False
|
||||
|
||||
@@ -123,11 +122,11 @@ def test_add_tag_to_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
assert not tag_absent_on_some
|
||||
|
||||
|
||||
def test_meta_tag_category(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_meta_tag_category(qt_driver: QtDriver, entry_full: Entry):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Ensure the Favorite tag is on entry_full
|
||||
library.add_tags_to_entries(1, entry_full.id)
|
||||
qt_driver.lib.add_tags_to_entries(1, entry_full.id)
|
||||
|
||||
# Select the single entry
|
||||
qt_driver.toggle_item_selection(entry_full.id, append=False, bridge=False)
|
||||
@@ -139,7 +138,7 @@ def test_meta_tag_category(qt_driver: QtDriver, library: Library, entry_full: En
|
||||
match i:
|
||||
case 0:
|
||||
# Check if the container is the Meta Tags category
|
||||
tag: Tag = unwrap(library.get_tag(2))
|
||||
tag: Tag = unwrap(qt_driver.lib.get_tag(2))
|
||||
assert container.title == f"<h4>{tag.name}</h4>"
|
||||
case 1:
|
||||
# Check if the container is the Tags category
|
||||
@@ -151,18 +150,16 @@ def test_meta_tag_category(qt_driver: QtDriver, library: Library, entry_full: En
|
||||
pass
|
||||
|
||||
|
||||
def test_custom_tag_category(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_custom_tag_category(qt_driver: QtDriver, entry_full: Entry):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Set tag 1000 (foo) as a category
|
||||
tag: Tag = unwrap(library.get_tag(1000))
|
||||
tag: Tag = unwrap(qt_driver.lib.get_tag(1000))
|
||||
tag.is_category = True
|
||||
library.update_tag(
|
||||
tag,
|
||||
)
|
||||
qt_driver.lib.update_tag(tag)
|
||||
|
||||
# Ensure the Favorite tag is on entry_full
|
||||
library.add_tags_to_entries(1, entry_full.id)
|
||||
qt_driver.lib.add_tags_to_entries(1, entry_full.id)
|
||||
|
||||
# Select the single entry
|
||||
qt_driver.toggle_item_selection(entry_full.id, append=False, bridge=False)
|
||||
@@ -174,7 +171,7 @@ def test_custom_tag_category(qt_driver: QtDriver, library: Library, entry_full:
|
||||
match i:
|
||||
case 0:
|
||||
# Check if the container is the Meta Tags category
|
||||
tag_2: Tag = unwrap(library.get_tag(2))
|
||||
tag_2: Tag = unwrap(qt_driver.lib.get_tag(2))
|
||||
assert container.title == f"<h4>{tag_2.name}</h4>"
|
||||
case 1:
|
||||
# Check if the container is the custom "foo" category
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# pyright: reportPrivateUsage=false, reportAttributeAccessIssue=false
|
||||
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
@@ -8,9 +9,7 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from PySide6.QtGui import (
|
||||
QAction,
|
||||
)
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import QMenu, QMenuBar
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
@@ -60,11 +59,10 @@ def test_filepath_setting(qtbot: QtBot, qt_driver: QtDriver, filepath_option: Sh
|
||||
)
|
||||
def test_file_path_display(
|
||||
qt_driver: QtDriver,
|
||||
library: Library,
|
||||
filepath_option: ShowFilepathOption,
|
||||
expected_path: Callable[[Library], Path],
|
||||
):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Select 2
|
||||
qt_driver.toggle_item_selection(2, append=False, bridge=False)
|
||||
@@ -73,15 +71,17 @@ def test_file_path_display(
|
||||
qt_driver.settings.show_filepath = filepath_option
|
||||
|
||||
# Apply the mock value
|
||||
entry = library.get_entry(2)
|
||||
entry = qt_driver.lib.get_entry(2)
|
||||
assert isinstance(entry, Entry)
|
||||
filename = entry.path
|
||||
panel._file_attributes_widget.update_stats(filepath=unwrap(library.library_dir) / filename) # pyright: ignore[reportPrivateUsage]
|
||||
panel._file_attributes_widget.update_stats(
|
||||
filepath=unwrap(qt_driver.lib.library_dir) / filename
|
||||
)
|
||||
|
||||
# Generate the expected file string.
|
||||
# This is copied directly from the file_attributes.py file
|
||||
# can be imported as a function in the future
|
||||
display_path: Path = expected_path(library)
|
||||
display_path: Path = expected_path(qt_driver.lib)
|
||||
file_str: str = ""
|
||||
separator: str = f"<a style='color: #777777'><b>{os.path.sep}</a>" # Gray
|
||||
for i, part in enumerate(display_path.parts):
|
||||
@@ -94,7 +94,7 @@ def test_file_path_display(
|
||||
file_str += f"<b>{'\u200b'.join(part_)}</b>"
|
||||
|
||||
# Assert the file path is displayed correctly
|
||||
assert panel._file_attributes_widget.file_label.text() == file_str # pyright: ignore[reportPrivateUsage]
|
||||
assert panel._file_attributes_widget.file_label.text() == file_str
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -146,7 +146,7 @@ def test_title_update(
|
||||
qt_driver.main_window.menu_bar.folders_to_tags_action = QAction(menu_bar)
|
||||
|
||||
# Trigger the update
|
||||
qt_driver._init_library(library_dir, open_status) # pyright: ignore[reportPrivateUsage]
|
||||
qt_driver._init_library(library_dir, open_status)
|
||||
|
||||
# Assert the title is updated correctly
|
||||
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(library_dir, base_title)) # pyright: ignore[reportAttributeAccessIssue]
|
||||
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(library_dir, base_title))
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.qt.controllers.preview_panel_controller import PreviewPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
def test_update_selection_empty(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_update_selection_empty(qt_driver: QtDriver):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Clear the library selection (selecting 1 then unselecting 1)
|
||||
qt_driver.toggle_item_selection(1, append=False, bridge=False)
|
||||
@@ -20,8 +19,8 @@ def test_update_selection_empty(qt_driver: QtDriver, library: Library):
|
||||
assert not panel.add_buttons_enabled
|
||||
|
||||
|
||||
def test_update_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_update_selection_single(qt_driver: QtDriver, entry_full: Entry):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Select the single entry
|
||||
qt_driver.toggle_item_selection(entry_full.id, append=False, bridge=False)
|
||||
@@ -31,8 +30,8 @@ def test_update_selection_single(qt_driver: QtDriver, library: Library, entry_fu
|
||||
assert panel.add_buttons_enabled
|
||||
|
||||
|
||||
def test_update_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
def test_update_selection_multiple(qt_driver: QtDriver):
|
||||
panel = PreviewPanel(qt_driver)
|
||||
|
||||
# Select the multiple entries
|
||||
qt_driver.toggle_item_selection(1, append=False, bridge=False)
|
||||
|
||||
Reference in New Issue
Block a user