Compare commits

...

2 Commits

Author SHA1 Message Date
Travis Abendshien f10d2f29de fix: remove deprecated methods 2026-07-21 17:40:25 -07:00
Travis Abendshien 6f0dda1633 refactor(ui): refactor SearchPanel classes to use newer MVC pattern 2026-07-21 17:40:17 -07:00
15 changed files with 268 additions and 389 deletions
@@ -2,7 +2,6 @@
# SPDX-License-Identifier: GPL-3.0-only
import typing
from typing import override
from warnings import catch_warnings
@@ -18,18 +17,15 @@ from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.suggest_box import SuggestBox
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.global_settings import GlobalSettings
from tagstudio.qt.translations import Translations
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
logger = structlog.get_logger(__name__)
class FieldSuggestBox(SuggestBox[BaseFieldTemplate]):
def __init__(self, driver: "QtDriver", placeholder_text: str = ""):
super().__init__(driver, placeholder_text)
self._lib = self._driver.lib
def __init__(self, library: Library, settings: GlobalSettings, placeholder_text: str = ""):
super().__init__(library, settings, placeholder_text)
# Context Menu Actions
edit_field_on_add_action = QAction(Translations["settings.edit_field_on_add"], self)
@@ -38,15 +34,15 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]):
self.addAction(edit_field_on_add_action)
self.layout().search_field.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
self.layout().search_field.addAction(edit_field_on_add_action)
edit_field_on_add_action.setChecked(self._driver.settings.edit_field_on_add)
edit_field_on_add_action.setChecked(self._settings.edit_field_on_add)
edit_field_on_add_action.triggered.connect(
lambda checked: self.toggle_edit_on_field_add(checked)
)
def toggle_edit_on_field_add(self, checked: bool) -> None:
"""Toggle the setting for opening the edit window after adding a field."""
self._driver.settings.edit_field_on_add = checked
self._driver.settings.save()
self._settings.edit_field_on_add = checked
self._settings.save()
@override
def _on_item_create(self) -> None:
@@ -7,7 +7,7 @@ from warnings import catch_warnings
import structlog
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QMessageBox
from PySide6.QtWidgets import QMessageBox, QWidget
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
from tagstudio.core.library.alchemy.library import Library
@@ -17,44 +17,30 @@ from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.search_panel_controller import SearchPanel
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.field_template_search_panel_view import FieldTemplateSearchPanelView
from tagstudio.qt.views.search_panel_view import SearchPanelView
logger = structlog.get_logger(__name__)
class FieldTemplateSearchModal(Modal):
def __init__(
self,
library: Library,
is_field_template_chooser: bool = True,
has_save: bool = False,
) -> None:
self.search_panel: FieldTemplateSearchPanel = FieldTemplateSearchPanel(
library,
is_field_template_chooser,
view=FieldTemplateSearchPanelView(is_field_template_chooser),
)
super().__init__(self.search_panel, Translations["field.add.plural"], is_savable=has_save)
class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
field_template_chosen = Signal(object)
def __init__(
self,
library: Library,
is_field_template_chooser: bool = True,
view: FieldTemplateSearchPanelView | None = None,
is_chooser: bool = True,
view: SearchPanelView | None = None,
) -> None:
super().__init__(
view=view or FieldTemplateSearchPanelView(is_field_template_chooser),
view=view
or SearchPanelView(Translations["home.search_field_templates"], is_chooser=is_chooser),
exclude=[],
is_chooser=is_field_template_chooser,
is_chooser=is_chooser,
)
self.__lib = library
self._unlimited_limit_item_label = Translations["field_template.all_field_templates"]
self._create_and_add_button_label_key = "field_template.create_add"
self._create_and_add_button_key = "field_template.create_add"
@override
def _get_max_limit(self) -> int:
@@ -102,7 +88,7 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
@override
def _on_item_remove(self, item: BaseFieldTemplate) -> None:
if self.is_chooser:
if self._is_chooser:
return
message_box = QMessageBox(
@@ -138,7 +124,7 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
if item is None:
return
field_template_widget.has_remove = not self.is_chooser
field_template_widget.has_remove = not self._is_chooser
# Disconnect previous callbacks
with catch_warnings(record=True):
@@ -175,4 +161,18 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
self.__lib.update_field_template(
edit_item_panel.old_field_type, edit_item_panel.build_field_template()
)
self.update_items(self.search_field.text())
self.update_items(self.layout().search_field.text())
@override
def get_item_widget(self, index: int, library: Library | None) -> FieldTemplateWidget:
"""Gets the item widget at a specific index."""
# Create any new item widgets needed up to the given index
if self.layout().scroll_layout.count() <= index:
while self.layout().scroll_layout.count() <= index:
pad_field_template_widget = FieldTemplateWidget()
pad_field_template_widget.setHidden(True)
self.layout().scroll_layout.addWidget(pad_field_template_widget)
field_template_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget()
assert isinstance(field_template_widget, FieldTemplateWidget)
return field_template_widget
@@ -8,7 +8,7 @@ import structlog
from PySide6 import QtCore, QtGui
from PySide6.QtCore import Signal
from PySide6.QtGui import QShowEvent
from PySide6.QtWidgets import QVBoxLayout, QWidget
from PySide6.QtWidgets import QWidget
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.modal_content import ModalContent
@@ -50,18 +50,18 @@ class SearchPanel[T](ModalContent):
is_chooser: bool = True,
) -> None:
super().__init__()
self.view = view
self.is_chooser = is_chooser
self.setLayout(QVBoxLayout(self))
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(self.view)
self.view.connect_callbacks(self)
self._driver: QtDriver | None = None
self.exclude: list[int] = exclude or []
self._is_chooser = is_chooser
self._create_and_add_button_in_layout = False
self._create_and_add_button_key: str = ""
# Items
self._excluded: list[int] = exclude or []
self._search_results: list[T] = []
# Limits
self._unlimited_limit_item_label: str = "All Items"
self.__limit_items: list[tuple[str, int]] = [
self._limit_items: list[tuple[str, int]] = [
("25", 25),
("50", 50),
("100", 100),
@@ -69,66 +69,102 @@ class SearchPanel[T](ModalContent):
("500", 500),
(self._unlimited_limit_item_label, -1),
]
self.__default_limit_index: int = 0 # 25 Limit (Default)
self.__previous_limit_index: int = self.__default_limit_index
self._default_limit_index: int = 0 # 25 Limit (Default)
self._previous_limit_index: int = self._default_limit_index
self.view.set_limit_items(self.__limit_items)
self.view.set_limit_index(self.__default_limit_index)
self.setLayout(view)
self.set_limit_items(self._limit_items)
self.set_limit_index(self._default_limit_index)
self.setMinimumSize(300, 400)
self.connect_callbacks(self)
# Items
self._search_results: list[T] = []
def connect_callbacks(self, controller: "SearchPanel[Any]") -> None: # pyright: ignore[reportExplicitAny]
self.layout().limit_combobox.currentIndexChanged.connect(controller.on_limit_changed)
self.layout().search_field.textChanged.connect(controller.on_search_query_changed)
self.layout().search_field.returnPressed.connect(
lambda: controller.on_search_query_submitted(self.get_search_query())
)
self.layout().create_button.clicked.connect(controller.on_item_create)
self.layout().create_and_add_button.clicked.connect(
lambda: controller.on_item_create(add_to_entry=True)
)
self._create_and_add_button_label_key: str = ""
def set_limit_items(self, limit_items: list[tuple[str, int]]) -> None:
# Remove existing limit items
for i in reversed(range(self.layout().limit_combobox.count())):
self.layout().limit_combobox.removeItem(i)
@property
def search_field(self):
return self.view.search_field
# Add new limit items
self.layout().limit_combobox.addItems([limit_item[0] for limit_item in limit_items])
@property
def create_and_add_button(self):
return self.view.create_and_add_button
def get_limit_index(self) -> int:
return self.layout().limit_combobox.currentIndex()
def set_limit_index(self, index: int) -> None:
self.layout().limit_combobox.setCurrentIndex(index)
def focus_search_box(self, select_all: bool = False) -> None:
self.layout().search_field.setFocus()
if select_all:
self.layout().search_field.selectAll()
def get_search_query(self) -> str:
return self.view.get_search_query()
return self.layout().search_field.text()
def clear_search_query(self) -> None:
self.view.clear_search_query()
self.layout().search_field.setText("")
self.focus_search_box()
def get_item_widget(self, index: int, library: Library):
return self.view.get_item_widget(index, library)
# Item list
def scroll_to(self, position: int) -> None:
self.layout().scroll_area.verticalScrollBar().setValue(position)
def set_driver(self, driver: "QtDriver") -> None:
self._driver = driver
def add_create_and_add_button(self) -> None:
if self._create_and_add_button_in_layout:
return
self.layout().scroll_layout.addWidget(self.layout().create_and_add_button)
self.layout().create_and_add_button.show()
self._create_and_add_button_in_layout = True
def remove_create_and_add_button(self) -> None:
if not self._create_and_add_button_in_layout:
return
self.layout().scroll_layout.removeWidget(self.layout().create_and_add_button)
self.layout().create_and_add_button.hide()
self._create_and_add_button_in_layout = False
def get_item_widget(self, index: int, library: Library) -> Any: # pyright: ignore[reportExplicitAny]
return self.get_item_widget(index, library)
def on_limit_changed(self, index: int) -> None:
# Method was called outside the limit_combobox callback
if index != self.view.get_limit_index():
self.view.set_limit_index(index)
if index != self.get_limit_index():
self.set_limit_index(index)
if self.__previous_limit_index == index:
if self._previous_limit_index == index:
return
self.update_items(self.search_field.text())
self.update_items(self.layout().search_field.text())
def _get_limit(self) -> tuple[str, int]:
return self.__limit_items[self.view.get_limit_index()]
return self._limit_items[self.get_limit_index()]
def _get_previous_limit(self) -> tuple[str, int]:
return self.__limit_items[self.__previous_limit_index]
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.create_and_add_button.setText(
Translations.format(self._create_and_add_button_label_key, query=query)
self.layout().create_and_add_button.setText(
Translations.format(self._create_and_add_button_key, query=query)
)
self.update_items(query)
def on_search_query_submitted(self, query: str) -> None:
# Focus search field if no query
if not query:
self.search_field.setFocus()
self.layout().search_field.setFocus()
parent: QWidget | None = self.parentWidget()
if parent is not None: # pyright: ignore[reportUnnecessaryComparison]
parent.hide()
@@ -137,7 +173,7 @@ class SearchPanel[T](ModalContent):
# Create and add item if no search results
if len(self._search_results) <= 0:
self.on_item_create(add_to_entry=True)
elif self.is_chooser:
elif self._is_chooser:
self._on_item_chosen(self._search_results[0])
self.clear_search_query()
@@ -156,14 +192,14 @@ class SearchPanel[T](ModalContent):
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._get_limit()[1])
# Remove the "Create & Add" button if one exists
self.view.remove_create_and_add_button()
self.remove_create_and_add_button()
# Get results for the search query
query_lower = "" if not query else query.lower()
@@ -205,11 +241,11 @@ class SearchPanel[T](ModalContent):
item: T | None = all_results[i] if i < len(all_results) else None
self.set_item_widget(item=item, index=i)
self.__previous_limit_index = self.view.get_limit_index()
self._previous_limit_index = self.get_limit_index()
# Add back the "Create & Add" button
if query and query.strip():
self.view.add_create_and_add_button()
self.add_create_and_add_button()
def search_items(self, query: str) -> tuple[list[T], list[T]]: # pyright: ignore[reportUnusedParameter]
raise NotImplementedError()
@@ -217,11 +253,16 @@ class SearchPanel[T](ModalContent):
def set_item_widget(self, item: T | None, index: int) -> None: # pyright: ignore[reportUnusedParameter]
raise NotImplementedError()
@override
def layout(self) -> SearchPanelView:
"""Return the typed layout for this widget."""
return super().layout() # pyright: ignore[reportReturnType]
@override
def showEvent(self, event: QShowEvent) -> None: # noqa N802
self.update_items()
self.view.scroll_to(0)
self.view.clear_search_query()
self.scroll_to(0)
self.clear_search_query()
return super().showEvent(event)
@override
@@ -229,10 +270,10 @@ class SearchPanel[T](ModalContent):
# 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():
if self.layout().search_field.hasFocus():
super().keyPressEvent(event)
else:
self.view.focus_search_box(select_all=True)
self.focus_search_box(select_all=True)
def create_item(self, edit_item_panel: ModalContent, choose_item: bool = False) -> None: # pyright: ignore[reportUnusedParameter]
raise NotImplementedError()
+7 -6
View File
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from typing import TYPE_CHECKING, Any, override
from typing import Any, override
import structlog
from PySide6.QtCore import Signal
@@ -12,6 +12,7 @@ from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.autofill_line_edit import QtCore, QtGui
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.global_settings import GlobalSettings
from tagstudio.qt.views.stylesheets.stylesheets import (
autofill_line_edit_style,
autofill_line_edit_top_style,
@@ -20,9 +21,6 @@ from tagstudio.qt.views.suggest_box_view import SuggestBoxView
logger = structlog.get_logger(__name__)
if TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
def _item_id(item: object) -> int:
item_id: Any = getattr(item, "id") # noqa: B009 # pyright: ignore[reportExplicitAny]
@@ -46,9 +44,12 @@ class SuggestBox[T](QWidget):
item_chosen = Signal(object)
done = Signal()
def __init__(self, driver: "QtDriver", placeholder_text: str = "") -> None:
def __init__(
self, library: Library, settings: GlobalSettings, placeholder_text: str = ""
) -> None:
super().__init__()
self._driver = driver
self._lib = library
self._settings = settings
self._limit = 5
self._is_shift_held = False
self._search_results: list[T] = []
@@ -6,10 +6,10 @@ from typing import override
from warnings import catch_warnings
import structlog
from PySide6.QtWidgets import QMessageBox
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QMessageBox, 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.modal import Modal
@@ -17,47 +17,33 @@ from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.search_panel_controller import SearchPanel
from tagstudio.qt.mixed.tag_widget import TagWidget
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView
from tagstudio.qt.views.search_panel_view import SearchPanelView
logger = structlog.get_logger(__name__)
class TagSearchModal(Modal):
def __init__(
self,
library: Library,
title: str,
exclude: list[int] | None = None,
is_tag_chooser: bool = True,
has_save: bool = False,
):
self.tsp = TagSearchPanel(
library, exclude, is_tag_chooser, view=TagSearchPanelView(is_tag_chooser)
)
super().__init__(content_widget=self.tsp, title=title, is_savable=has_save)
class TagSearchPanel(SearchPanel[Tag]):
search_for_tag = Signal(int)
def __init__(
self,
library: Library,
exclude: list[int] | None = None,
is_tag_chooser: bool = True,
view: TagSearchPanelView | None = None,
is_chooser: bool = True,
view: SearchPanelView | None = None,
):
super().__init__(
view=view or TagSearchPanelView(is_tag_chooser),
view=view or SearchPanelView(Translations["home.search_tags"], is_chooser=is_chooser),
exclude=exclude,
is_chooser=is_tag_chooser,
is_chooser=is_chooser,
)
self.__lib = library
self._lib = library
self._unlimited_limit_item_label = Translations["tag.all_tags"]
self._create_and_add_button_label_key = "tag.create_add"
self._create_and_add_button_key = "tag.create_add"
@override
def _get_max_limit(self) -> int:
return len(self.__lib.tags)
return len(self._lib.tags)
@override
def on_item_create(self, add_to_entry: bool = False) -> None:
@@ -72,8 +58,7 @@ class TagSearchPanel(SearchPanel[Tag]):
from tagstudio.qt.mixed.build_tag import BuildTagPanel # here due to circular imports
query: str = self.get_search_query()
panel: BuildTagPanel = BuildTagPanel(self.__lib)
panel: BuildTagPanel = BuildTagPanel(self._lib)
modal: Modal = Modal(
panel,
Translations["tag.new"],
@@ -92,10 +77,10 @@ class TagSearchPanel(SearchPanel[Tag]):
# 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: Modal = Modal(
edit_tag_panel,
self.__lib.tag_display_name(item),
self._lib.tag_display_name(item),
Translations["tag.edit"],
is_savable=True,
)
@@ -104,7 +89,7 @@ class TagSearchPanel(SearchPanel[Tag]):
@override
def _on_item_remove(self, item: Tag) -> None:
if self.is_chooser:
if self._is_chooser:
return
if item.id in range(RESERVED_TAG_START, RESERVED_TAG_END):
@@ -113,7 +98,7 @@ class TagSearchPanel(SearchPanel[Tag]):
message_box = QMessageBox(
QMessageBox.Icon.Question,
Translations["tag.remove"],
Translations.format("tag.confirm_delete", tag_name=self.__lib.tag_display_name(item)),
Translations.format("tag.confirm_delete", tag_name=self._lib.tag_display_name(item)),
QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel,
)
@@ -122,7 +107,7 @@ class TagSearchPanel(SearchPanel[Tag]):
if result != QMessageBox.StandardButton.Ok:
return
self.__lib.remove_tag(item.id)
self._lib.remove_tag(item.id)
self.update_items(self.get_search_query())
@override
@@ -131,12 +116,12 @@ class TagSearchPanel(SearchPanel[Tag]):
@override
def search_items(self, query: str) -> tuple[list[Tag], list[Tag]]:
return self.__lib.search_tags(name=query, limit=self._get_limit()[1])
return self._lib.search_tags(name=query, limit=self._get_limit()[1])
@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.set_tag(item)
tag_widget.setHidden(item is None)
@@ -144,7 +129,7 @@ class TagSearchPanel(SearchPanel[Tag]):
return
assert item is not None
tag_widget.has_remove = not self.is_chooser and item.id not in range(
tag_widget.has_remove = not self._is_chooser and item.id not in range(
RESERVED_TAG_START, RESERVED_TAG_END
)
@@ -158,7 +143,7 @@ class TagSearchPanel(SearchPanel[Tag]):
# 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:
if self._is_chooser:
tag_widget.bg_button.clicked.connect(
lambda checked=False, tag=item: self._on_item_chosen(tag)
)
@@ -168,16 +153,10 @@ class TagSearchPanel(SearchPanel[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.search_for_tag_action.triggered.connect(
lambda checked=False, tag_id=item.id: self.search_for_tag.emit(tag_id)
)
tag_widget.search_for_tag_action.setEnabled(True)
@override
def create_item(self, edit_item_panel: ModalContent, choose_item: bool = False) -> None:
@@ -186,7 +165,7 @@ class TagSearchPanel(SearchPanel[Tag]):
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
)
@@ -205,18 +184,25 @@ class TagSearchPanel(SearchPanel[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.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
@override
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.layout().scroll_layout.count() <= index:
while self.layout().scroll_layout.count() <= index:
pad_tag_widget = TagWidget(
tag=None, has_edit=True, has_remove=True, library=library
)
pad_tag_widget.setHidden(True)
self.layout().scroll_layout.addWidget(pad_tag_widget)
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)
)
tag_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget()
assert isinstance(tag_widget, TagWidget)
return tag_widget
+13 -20
View File
@@ -2,36 +2,35 @@
# SPDX-License-Identifier: GPL-3.0-only
import typing
from typing import override
from warnings import catch_warnings
import structlog
from PySide6.QtCore import Signal
from PySide6.QtGui import QAction, Qt
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
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.modal import Modal
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.suggest_box import SuggestBox
from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget
from tagstudio.qt.global_settings import GlobalSettings
from tagstudio.qt.mixed.build_tag import BuildTagPanel
from tagstudio.qt.mixed.tag_widget import TagWidget
from tagstudio.qt.translations import Translations
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
logger = structlog.get_logger(__name__)
class TagSuggestBox(SuggestBox[Tag]):
def __init__(self, driver: "QtDriver", placeholder_text: str = "") -> None:
super().__init__(driver, placeholder_text)
self._driver = driver
self._lib = self._driver.lib
search_for_tag = Signal(int)
def __init__(
self, library: Library, settings: GlobalSettings, placeholder_text: str = ""
) -> None:
super().__init__(library, settings, placeholder_text)
# Context Menu Actions
edit_tag_on_create_action = QAction(Translations["settings.edit_tag_on_create"], self)
@@ -40,21 +39,15 @@ class TagSuggestBox(SuggestBox[Tag]):
self.addAction(edit_tag_on_create_action)
self.layout().search_field.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
self.layout().search_field.addAction(edit_tag_on_create_action)
edit_tag_on_create_action.setChecked(self._driver.settings.edit_tag_on_create)
edit_tag_on_create_action.setChecked(self._settings.edit_tag_on_create)
edit_tag_on_create_action.triggered.connect(
lambda checked: self.toggle_edit_on_tag_create(checked)
)
def _search_for_tag_callback(self, tag_id: int) -> None:
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)
)
def toggle_edit_on_tag_create(self, checked: bool) -> None:
"""Toggle the setting for opening the edit window after creating a tag."""
self._driver.settings.edit_tag_on_create = checked
self._driver.settings.save()
self._settings.edit_tag_on_create = checked
self._settings.save()
@override
def _on_item_create(self) -> None:
@@ -65,7 +58,7 @@ class TagSuggestBox(SuggestBox[Tag]):
"""
query: str = self.layout().search_field.text()
if self._driver.settings.edit_tag_on_create:
if self._settings.edit_tag_on_create:
panel: BuildTagPanel = BuildTagPanel(self._lib)
modal: Modal = Modal(
panel, Translations["tag.new"], Translations["tag.new"], is_savable=True
@@ -139,7 +132,7 @@ class TagSuggestBox(SuggestBox[Tag]):
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_callback(tag_id)
lambda checked=False, tag_id=item.id: self.search_for_tag.emit(tag_id)
)
tag_widget.search_for_tag_action.setEnabled(True)
+16 -15
View File
@@ -30,11 +30,12 @@ from tagstudio.core.library.alchemy.models import Tag, TagAlias, TagColorGroup
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchPanel
from tagstudio.qt.mixed.tag_color_preview import TagColorPreview
from tagstudio.qt.mixed.tag_color_selection import TagColorSelection
from tagstudio.qt.mixed.tag_widget import TagWidget
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.search_panel_view import SearchPanelView
from tagstudio.qt.views.stylesheets.stylesheets import (
checkbox_style,
colored_radio_button_style,
@@ -79,7 +80,7 @@ class BuildTagPanel(ModalContent):
def __init__(self, library: Library, tag: Tag | None = None) -> None:
super().__init__()
self.lib = library
self._lib = library
self.tag: Tag # NOTE: This gets set at the end of the init.
self.tag_color_namespace: str | None
self.tag_color_slug: str | None
@@ -154,7 +155,6 @@ class BuildTagPanel(ModalContent):
self.parent_tags_title = QLabel(Translations["tag.parent_tags"])
self.parent_tags_layout.addWidget(self.parent_tags_title)
self.scroll_contents = QWidget()
self.parent_tags_scroll_layout = QVBoxLayout(self.scroll_contents)
self.parent_tags_scroll_layout.setContentsMargins(6, 6, 6, 0)
@@ -177,10 +177,11 @@ class BuildTagPanel(ModalContent):
if tag is not None:
exclude_ids.append(tag.id)
self.add_tag_modal = TagSearchModal(
self.lib, title=Translations["tag.add.plural"], exclude=exclude_ids
)
self.add_tag_modal.tsp.item_chosen.connect(lambda x: self._add_parent_tag_callback(x))
tsp_view = SearchPanelView(placeholder_text=Translations["home.search_tags"])
tsp = TagSearchPanel(self._lib, exclude=exclude_ids, view=tsp_view)
self.add_tag_modal = Modal(tsp, title=Translations["tag.add.plural"])
tsp.item_chosen.connect(lambda x: self._add_parent_tag_callback(x))
self.parent_tags_add_button.clicked.connect(self.add_tag_modal.show)
# Color ----------------------------------------------------------------
@@ -195,12 +196,12 @@ class BuildTagPanel(ModalContent):
self.color_button: TagColorPreview
try:
assert tag is not None
self.color_button = TagColorPreview(self.lib, tag.color)
self.color_button = TagColorPreview(self._lib, tag.color)
except Exception as e:
# TODO: Investigate why this happens during tests
logger.error("[BuildTag] Could not access Tag member attributes", error=e)
self.color_button = TagColorPreview(self.lib, None)
self.tag_color_selection = TagColorSelection(self.lib)
self.color_button = TagColorPreview(self._lib, None)
self.tag_color_selection = TagColorSelection(self._lib)
chose_tag_color_title = Translations["tag.choose_color"]
self.choose_color_modal = Modal(
self.tag_color_selection, chose_tag_color_title, chose_tag_color_title
@@ -326,7 +327,7 @@ class BuildTagPanel(ModalContent):
next_tab: QWidget = last_tab
for parent_id in self.parent_ids:
tag = self.lib.get_tag(parent_id)
tag = self._lib.get_tag(parent_id)
if not tag:
continue
is_disam = parent_id == self.disambiguation_id
@@ -364,7 +365,7 @@ class BuildTagPanel(ModalContent):
text_color = get_tag_text_color(primary_color, highlight_color)
def update_parent_tag_callback(build_tag_panel: BuildTagPanel):
self.lib.update_tag(
self._lib.update_tag(
build_tag_panel.build_tag(),
parent_ids=set(build_tag_panel.parent_ids),
aliases=set(build_tag_panel.aliases),
@@ -372,10 +373,10 @@ class BuildTagPanel(ModalContent):
self.set_parent_tags()
def on_parent_tag_edit(tag: Tag) -> None:
build_tag_panel = BuildTagPanel(self.lib, tag=tag)
build_tag_panel = BuildTagPanel(self._lib, tag=tag)
edit_modal = Modal(
build_tag_panel,
self.lib.tag_display_name(tag),
self._lib.tag_display_name(tag),
"Edit Tag",
is_savable=True,
)
@@ -383,7 +384,7 @@ class BuildTagPanel(ModalContent):
edit_modal.show()
# Add Tag Widget
tag_widget = TagWidget(tag, library=self.lib, has_edit=True, has_remove=True)
tag_widget = TagWidget(tag, library=self._lib, has_edit=True, has_remove=True)
tag_widget.on_remove.connect(lambda t=parent_id: self._remove_parent_tag_callback(t))
tag_widget.on_edit.connect(partial(on_parent_tag_edit, tag))
-3
View File
@@ -141,9 +141,6 @@ class TagWidget(QWidget):
self.search_for_tag_action = QAction(self)
self.search_for_tag_action.setText(Translations["tag.search_for_tag"])
self.bg_button.addAction(self.search_for_tag_action)
# add_to_search_action = QAction(self)
# add_to_search_action.setText(Translations.translate_formatted("tag.add_to_search"))
# self.bg_button.addAction(add_to_search_action)
self.inner_layout = QHBoxLayout()
self.inner_layout.setObjectName("innerLayout")
+24 -16
View File
@@ -62,7 +62,7 @@ from tagstudio.qt.controllers.fix_ignored_modal_controller import FixIgnoredEntr
from tagstudio.qt.controllers.ignore_modal_controller import IgnoreModal
from tagstudio.qt.controllers.library_info_window_controller import LibraryInfoWindow
from tagstudio.qt.controllers.modal import Modal
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchPanel
from tagstudio.qt.controllers.update_available_message_box import UpdateAvailableMessageBox
from tagstudio.qt.global_settings import DEFAULT_GLOBAL_SETTINGS_PATH, GlobalSettings, Theme
from tagstudio.qt.mixed.about_modal import AboutModal
@@ -83,8 +83,8 @@ from tagstudio.qt.translations import Translations
from tagstudio.qt.utils.custom_runnable import CustomRunnable
from tagstudio.qt.utils.file_deleter import delete_file
from tagstudio.qt.utils.function_iterator import FunctionIterator
from tagstudio.qt.views.field_template_search_panel_view import FieldTemplateSearchPanelView
from tagstudio.qt.views.main_window import MainWindow
from tagstudio.qt.views.search_panel_view import SearchPanelView
from tagstudio.qt.views.splash import SplashScreen
from tagstudio.qt.views.stylesheets.stylesheets import header
@@ -364,14 +364,14 @@ class QtDriver(DriverMixin, QObject):
if platform.system() != "Windows":
self.app.setDesktopFileName("tagstudio")
# Initialize the Tag Manager panel
self.tag_manager = TagSearchModal(
self.lib,
title=Translations["tag_manager.title"],
is_tag_chooser=False,
tsp_view = SearchPanelView(
placeholder_text=Translations["home.search_tags"],
create_text=Translations["tag.create"],
is_chooser=False,
)
self.tag_manager.tsp.set_driver(self)
tsp = TagSearchPanel(self.lib, is_chooser=False, view=tsp_view)
tsp.search_for_tag.connect(self.search_for_tag_callback)
self.tag_manager = Modal(tsp, Translations["tag_manager.title"])
self.tag_manager.done.connect(
lambda checked=False: self.main_window.preview_panel.set_selection(
self.selected, update_preview=False
@@ -385,8 +385,8 @@ class QtDriver(DriverMixin, QObject):
self.field_template_manager = Modal(
content_widget=FieldTemplateSearchPanel(
self.lib,
is_field_template_chooser=False,
view=FieldTemplateSearchPanelView(is_field_template_chooser=False),
is_chooser=False,
view=SearchPanelView(Translations["home.search_field_templates"], is_chooser=False),
),
title=Translations["field_template_manager.title"],
is_savable=False,
@@ -397,12 +397,15 @@ class QtDriver(DriverMixin, QObject):
)
)
# TODO: Remove/replace this with a suite of persistently visible tagging panels, including
# tag search. These will be docked on the left-hand side of the main window.
# Initialize the "Add Tag" panel
self.add_tag_modal = TagSearchModal(
self.lib, title=Translations["tag.add.plural"], is_tag_chooser=True
)
self.add_tag_modal.tsp.set_driver(self)
self.add_tag_modal.tsp.item_chosen.connect(
add_tag_tsp_view = SearchPanelView(placeholder_text=Translations["home.search_tags"])
add_tag_tsp = TagSearchPanel(self.lib, view=add_tag_tsp_view)
add_tag_tsp.search_for_tag.connect(self.search_for_tag_callback)
self.add_tag_modal = Modal(add_tag_tsp, Translations["tag.add.plural"])
add_tag_tsp.item_chosen.connect(
lambda chosen_tag: (
self.add_tags_to_selected_callback([chosen_tag]),
self.main_window.preview_panel.set_selection(self.selected),
@@ -890,6 +893,11 @@ class QtDriver(DriverMixin, QObject):
)
self.modal.show()
def search_for_tag_callback(self, tag_id: int) -> None:
"""Callback to search for a given Tag ID."""
self.main_window.search_field.setText(f"tag_id:{tag_id}")
self.update_browsing_state(BrowsingState.from_tag_id(tag_id, self.browsing_history.current))
def select_all_action_callback(self):
"""Set the selection to all visible items."""
self.select_all()
@@ -1,33 +0,0 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from typing import override
from PySide6.QtWidgets import QWidget
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.field_template_widget_controller import FieldTemplateWidget
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.search_panel_view import SearchPanelView
class FieldTemplateSearchPanelView(SearchPanelView):
def __init__(self, is_field_template_chooser: bool) -> None:
super().__init__(is_field_template_chooser)
self.search_field.setPlaceholderText(Translations["home.search_field_templates"])
self.create_button.setText(Translations["field_template.create"])
@override
def get_item_widget(self, index: int, library: Library | None) -> FieldTemplateWidget:
"""Gets the item widget at a specific index."""
# Create any new item widgets needed up to the given index
if self._scroll_layout.count() <= index:
while self._scroll_layout.count() <= index:
pad_field_template_widget = FieldTemplateWidget()
pad_field_template_widget.setHidden(True)
self._scroll_layout.addWidget(pad_field_template_widget)
field_template_widget: QWidget = self._scroll_layout.itemAt(index).widget()
assert isinstance(field_template_widget, FieldTemplateWidget)
return field_template_widget
+7 -3
View File
@@ -34,12 +34,16 @@ class PreviewPanelView(QVBoxLayout):
self.setSpacing(6)
rm = ResourceManager()
# Search/Create Boxes
def ph_text(key: str) -> str:
return " ".join([Translations[key], Translations["home.search.how_to_exit"]])
# Search/Create Boxes
self.field_search_box = FieldSuggestBox(driver, ph_text("home.search_or_create_fields"))
self.tag_search_box = TagSuggestBox(driver, ph_text("home.search_or_create_tags"))
self.field_search_box = FieldSuggestBox(
driver.lib, driver.settings, ph_text("home.search_or_create_fields")
)
self.tag_search_box = TagSuggestBox(
driver.lib, driver.settings, ph_text("home.search_or_create_tags")
)
self.preview_thumb = PreviewThumb(driver.lib, driver)
self.file_attrs = FileAttributes(driver.lib, driver)
+32 -111
View File
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING
from PySide6.QtCore import QSize, Qt
from PySide6.QtWidgets import (
@@ -16,144 +16,65 @@ from PySide6.QtWidgets import (
QWidget,
)
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.modal_content import ModalContent
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.stylesheets.stylesheets import list_button_style
if TYPE_CHECKING:
from tagstudio.qt.controllers.search_panel_controller import SearchPanel
pass
class SearchPanelView(ModalContent):
def __init__(self, is_chooser: bool) -> None:
class SearchPanelView(QVBoxLayout):
def __init__(
self, placeholder_text: str, create_text: str = "", is_chooser: bool = True
) -> None:
self.is_chooser: bool = is_chooser
super().__init__()
self.__root_layout = QVBoxLayout(self)
self.__root_layout.setContentsMargins(6, 0, 6, 0)
self.setMinimumSize(300, 400)
self.setContentsMargins(6, 0, 6, 0)
# Limit container
self.__limit_container = QWidget()
self.__root_layout.addWidget(self.__limit_container)
self.__limit_layout = QHBoxLayout(self.__limit_container)
self.__limit_layout.setContentsMargins(0, 0, 0, 0)
self.__limit_layout.setSpacing(12)
self.__limit_layout.addStretch(1)
self.__limit_title = QLabel(Translations["home.search.view_limit"])
self.__limit_layout.addWidget(self.__limit_title)
self.limit_container = QWidget()
self.limit_layout = QHBoxLayout(self.limit_container)
self.limit_layout.setContentsMargins(0, 0, 0, 0)
self.limit_layout.setSpacing(12)
self.limit_layout.addStretch(1)
self.limit_title = QLabel(Translations["home.search.view_limit"])
self.limit_layout.addWidget(self.limit_title)
self.addWidget(self.limit_container)
# Limit dropdown
self.limit_combobox = QComboBox()
self.__limit_layout.addWidget(self.limit_combobox)
self.__limit_layout.addStretch(1)
self.limit_layout.addWidget(self.limit_combobox)
self.limit_layout.addStretch(1)
self.limit_combobox.setEditable(False)
# Search field
self.search_field = QLineEdit()
self.search_field.setPlaceholderText(placeholder_text)
self.search_field.setObjectName("search_field")
self.__root_layout.addWidget(self.search_field)
self.search_field.setMinimumSize(QSize(0, 32))
self.addWidget(self.search_field)
# Scroll area
self.__scroll_contents = QWidget()
self._scroll_layout = QVBoxLayout(self.__scroll_contents)
self._scroll_layout.setContentsMargins(6, 0, 6, 0)
self._scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.__scroll_area = QScrollArea()
self.__scroll_area.setWidget(self.__scroll_contents)
self.__root_layout.addWidget(self.__scroll_area)
self.__scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.__scroll_area.setWidgetResizable(True)
self.__scroll_area.setFrameShadow(QFrame.Shadow.Plain)
self.__scroll_area.setFrameShape(QFrame.Shape.NoFrame)
self.scroll_contents = QWidget()
self.scroll_layout = QVBoxLayout(self.scroll_contents)
self.scroll_layout.setContentsMargins(6, 0, 6, 0)
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.scroll_area = QScrollArea()
self.scroll_area.setWidget(self.scroll_contents)
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setFrameShadow(QFrame.Shadow.Plain)
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
self.addWidget(self.scroll_area)
# Create button
self.create_button = QPushButton("")
self.create_button = QPushButton(create_text)
if not self.is_chooser:
self.__root_layout.addWidget(self.create_button)
self.addWidget(self.create_button)
# Create and add button
self.create_and_add_button_in_layout: bool = False
self.create_and_add_button = QPushButton()
self.create_and_add_button.setFlat(True)
self.create_and_add_button.setMinimumSize(22, 22)
self.create_and_add_button.setStyleSheet(list_button_style(border_style="dashed"))
@property
def scroll_layout(self) -> QVBoxLayout:
return self._scroll_layout
@property
def scroll_area(self) -> QScrollArea:
return self.__scroll_area
def connect_callbacks(self, controller: "SearchPanel[Any]") -> None: # pyright: ignore[reportExplicitAny]
self.limit_combobox.currentIndexChanged.connect(controller.on_limit_changed)
self.search_field.textChanged.connect(controller.on_search_query_changed)
self.search_field.returnPressed.connect(
lambda: controller.on_search_query_submitted(self.get_search_query())
)
self.create_button.clicked.connect(controller.on_item_create)
self.create_and_add_button.clicked.connect(
lambda: controller.on_item_create(add_to_entry=True)
)
def set_limit_items(self, limit_items: list[tuple[str, int]]) -> None:
# Remove existing limit items
for i in reversed(range(self.limit_combobox.count())):
self.limit_combobox.removeItem(i)
# Add new limit items
self.limit_combobox.addItems([limit_item[0] for limit_item in limit_items])
def get_limit_index(self) -> int:
return self.limit_combobox.currentIndex()
def set_limit_index(self, index: int) -> None:
self.limit_combobox.setCurrentIndex(index)
def focus_search_box(self, select_all: bool = False) -> None:
self.search_field.setFocus()
if select_all:
self.search_field.selectAll()
def get_search_query(self) -> str:
return self.search_field.text()
def clear_search_query(self) -> None:
self.search_field.setText("")
self.focus_search_box()
# Item list
def scroll_to(self, position: int) -> None:
self.__scroll_area.verticalScrollBar().setValue(position)
def get_item_widget(self, index: int, library: Library | None) -> Any: # pyright: ignore[reportUnusedParameter, reportExplicitAny]
raise NotImplementedError()
def add_create_and_add_button(self) -> None:
if self.create_and_add_button_in_layout:
return
self._scroll_layout.addWidget(self.create_and_add_button)
self.create_and_add_button.show()
self.create_and_add_button_in_layout = True
def remove_create_and_add_button(self) -> None:
if not self.create_and_add_button_in_layout:
return
self._scroll_layout.removeWidget(self.create_and_add_button)
self.create_and_add_button.hide()
self.create_and_add_button_in_layout = False
@@ -1,35 +0,0 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
from typing import override
from PySide6.QtWidgets import QWidget
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.mixed.tag_widget import TagWidget
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.search_panel_view import SearchPanelView
class TagSearchPanelView(SearchPanelView):
def __init__(self, is_tag_chooser: bool) -> None:
super().__init__(is_tag_chooser)
self.search_field.setPlaceholderText(Translations["home.search_tags"])
self.create_button.setText(Translations["tag.create"])
@override
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._scroll_layout.count() <= index:
while self._scroll_layout.count() <= index:
pad_tag_widget = TagWidget(
tag=None, has_edit=True, has_remove=True, library=library
)
pad_tag_widget.setHidden(True)
self._scroll_layout.addWidget(pad_tag_widget)
tag_widget: QWidget = self._scroll_layout.itemAt(index).widget()
assert isinstance(tag_widget, TagWidget)
return tag_widget
+4 -3
View File
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# pyright: reportPrivateUsage = false
from collections.abc import Callable
@@ -22,7 +23,7 @@ def test_build_tag_panel_add_sub_tag_callback(
panel: BuildTagPanel = BuildTagPanel(library, child)
qtbot.addWidget(panel)
panel._add_parent_tag_callback(parent.id) # pyright: ignore[reportPrivateUsage]
panel._add_parent_tag_callback(parent.id)
assert len(panel.parent_ids) == 1
@@ -40,7 +41,7 @@ def test_build_tag_panel_remove_subtag_callback(
panel: BuildTagPanel = BuildTagPanel(library, child)
qtbot.addWidget(panel)
panel._remove_parent_tag_callback(parent.id) # pyright: ignore[reportPrivateUsage]
panel._remove_parent_tag_callback(parent.id)
assert len(panel.parent_ids) == 0
@@ -58,7 +59,7 @@ def test_build_tag_panel_add_alias_callback(
panel: BuildTagPanel = BuildTagPanel(library, tag)
qtbot.addWidget(panel)
panel._create_alias_callback() # pyright: ignore[reportPrivateUsage]
panel._create_alias_callback()
assert panel.aliases_table.rowCount() == 1
+4 -6
View File
@@ -8,13 +8,12 @@ from pytestqt.qtbot import QtBot
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchPanel
from tagstudio.qt.mixed.tag_widget import TagWidget
from tagstudio.qt.ts_qt import QtDriver
from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView
from tagstudio.qt.views.search_panel_view import SearchPanelView
def test_update_tags(qtbot: QtBot, library: Library):
# Given
panel = TagSearchPanel(library, view=TagSearchPanelView(is_tag_chooser=True))
panel = TagSearchPanel(library, view=SearchPanelView("", is_chooser=True))
qtbot.addWidget(panel)
@@ -22,10 +21,9 @@ def test_update_tags(qtbot: QtBot, library: Library):
panel.update_items()
def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, qt_driver: QtDriver, library: Library):
panel = TagSearchPanel(library, view=TagSearchPanelView(is_tag_chooser=True))
def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, library: Library):
panel = TagSearchPanel(library, view=SearchPanelView(""))
qtbot.addWidget(panel)
panel.set_driver(qt_driver)
# Set the widget
tags = library.tags