diff --git a/src/tagstudio/qt/controllers/field_suggest_box.py b/src/tagstudio/qt/controllers/field_suggest_box.py index 2a20ab76..d4bb618c 100644 --- a/src/tagstudio/qt/controllers/field_suggest_box.py +++ b/src/tagstudio/qt/controllers/field_suggest_box.py @@ -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: diff --git a/src/tagstudio/qt/controllers/field_template_search_panel_controller.py b/src/tagstudio/qt/controllers/field_template_search_panel_controller.py index 36d45e7b..dc57a68c 100644 --- a/src/tagstudio/qt/controllers/field_template_search_panel_controller.py +++ b/src/tagstudio/qt/controllers/field_template_search_panel_controller.py @@ -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 diff --git a/src/tagstudio/qt/controllers/search_panel_controller.py b/src/tagstudio/qt/controllers/search_panel_controller.py index f26f3e33..09f82e97 100644 --- a/src/tagstudio/qt/controllers/search_panel_controller.py +++ b/src/tagstudio/qt/controllers/search_panel_controller.py @@ -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() diff --git a/src/tagstudio/qt/controllers/suggest_box.py b/src/tagstudio/qt/controllers/suggest_box.py index 34346e45..7b3bd13f 100644 --- a/src/tagstudio/qt/controllers/suggest_box.py +++ b/src/tagstudio/qt/controllers/suggest_box.py @@ -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] = [] diff --git a/src/tagstudio/qt/controllers/tag_search_panel_controller.py b/src/tagstudio/qt/controllers/tag_search_panel_controller.py index 242e1241..585090eb 100644 --- a/src/tagstudio/qt/controllers/tag_search_panel_controller.py +++ b/src/tagstudio/qt/controllers/tag_search_panel_controller.py @@ -6,7 +6,9 @@ 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 typing_extensions import deprecated from tagstudio.core.constants import RESERVED_TAG_END, RESERVED_TAG_START from tagstudio.core.library.alchemy.enums import BrowsingState @@ -17,47 +19,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 +60,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 +79,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 +91,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 +100,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 +109,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 +118,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 +131,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 +145,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 +155,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 +167,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 +186,36 @@ 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: + @deprecated("Put this callback in the driver!") + def _search_for_tag_callback(self, tag_id: int) -> None: if self._driver is None: return + # TODO: This should be a callback, the driver does not need to be passed for this. 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) ) + + @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) + + tag_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget() + assert isinstance(tag_widget, TagWidget) + return tag_widget diff --git a/src/tagstudio/qt/controllers/tag_suggest_box.py b/src/tagstudio/qt/controllers/tag_suggest_box.py index 8ff0cd06..80e06c8b 100644 --- a/src/tagstudio/qt/controllers/tag_suggest_box.py +++ b/src/tagstudio/qt/controllers/tag_suggest_box.py @@ -2,36 +2,36 @@ # 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 typing_extensions import deprecated -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 +40,24 @@ 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) ) + @deprecated("Put this callback in the driver!") 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) - ) + # TODO: This should be a callback, the driver does not need to be passed for this. + # 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) + # ) + pass 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 +68,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 +142,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) diff --git a/src/tagstudio/qt/mixed/build_tag.py b/src/tagstudio/qt/mixed/build_tag.py index 1a73976f..c5492122 100644 --- a/src/tagstudio/qt/mixed/build_tag.py +++ b/src/tagstudio/qt/mixed/build_tag.py @@ -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)) diff --git a/src/tagstudio/qt/mixed/tag_widget.py b/src/tagstudio/qt/mixed/tag_widget.py index 2d702a4f..d0ae2db5 100644 --- a/src/tagstudio/qt/mixed/tag_widget.py +++ b/src/tagstudio/qt/mixed/tag_widget.py @@ -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") diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index 8a7d356d..6ee49361 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -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() diff --git a/src/tagstudio/qt/views/field_template_search_panel_view.py b/src/tagstudio/qt/views/field_template_search_panel_view.py deleted file mode 100644 index 4b6218a9..00000000 --- a/src/tagstudio/qt/views/field_template_search_panel_view.py +++ /dev/null @@ -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 diff --git a/src/tagstudio/qt/views/preview_panel_view.py b/src/tagstudio/qt/views/preview_panel_view.py index e1daee1f..5e1c905c 100644 --- a/src/tagstudio/qt/views/preview_panel_view.py +++ b/src/tagstudio/qt/views/preview_panel_view.py @@ -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) diff --git a/src/tagstudio/qt/views/search_panel_view.py b/src/tagstudio/qt/views/search_panel_view.py index 60ccd1e5..cabcbc92 100644 --- a/src/tagstudio/qt/views/search_panel_view.py +++ b/src/tagstudio/qt/views/search_panel_view.py @@ -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 diff --git a/src/tagstudio/qt/views/tag_search_panel_view.py b/src/tagstudio/qt/views/tag_search_panel_view.py deleted file mode 100644 index be67f63f..00000000 --- a/src/tagstudio/qt/views/tag_search_panel_view.py +++ /dev/null @@ -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 diff --git a/tests/qt/test_build_tag_panel.py b/tests/qt/test_build_tag_panel.py index 024d1270..c53072ef 100644 --- a/tests/qt/test_build_tag_panel.py +++ b/tests/qt/test_build_tag_panel.py @@ -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 diff --git a/tests/qt/test_tag_search_panel.py b/tests/qt/test_tag_search_panel.py index 888d0eff..86d3ebbb 100644 --- a/tests/qt/test_tag_search_panel.py +++ b/tests/qt/test_tag_search_panel.py @@ -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