diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index d567e899..60f2a1d6 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -3,72 +3,32 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio import structlog -from PySide6.QtCore import QSize, Qt, Signal -from PySide6.QtGui import QShowEvent from PySide6.QtWidgets import ( - QFrame, - QHBoxLayout, - QLineEdit, QMessageBox, QPushButton, - QScrollArea, - QVBoxLayout, - QWidget, ) from src.core.constants import RESERVED_TAG_END, RESERVED_TAG_START from src.core.library import Library, Tag from src.qt.modals.build_tag import BuildTagPanel +from src.qt.modals.tag_search import TagSearchPanel from src.qt.translations import Translations -from src.qt.widgets.panel import PanelModal, PanelWidget -from src.qt.widgets.tag import TagWidget +from src.qt.widgets.panel import PanelModal logger = structlog.get_logger(__name__) -# TODO: This class shares the majority of its code with tag_search.py. -# It should either be made DRY, or be replaced with the intended and more robust -# Tag Management tab/pane outlined on the Feature Roadmap. +# TODO: Once this class is removed, the `is_tag_chooser` option of `TagSearchPanel` +# will most likely be enabled in every case +# and the possibilty of disabling it can therefore be removed -class TagDatabasePanel(PanelWidget): - tag_chosen = Signal(int) - +class TagDatabasePanel(TagSearchPanel): def __init__(self, library: Library): - super().__init__() - self.lib: Library = library - self.is_initialized: bool = False - self.first_tag_id = -1 - - self.setMinimumSize(300, 400) - self.root_layout = QVBoxLayout(self) - self.root_layout.setContentsMargins(6, 0, 6, 0) - - self.search_field = QLineEdit() - self.search_field.setObjectName("searchField") - self.search_field.setMinimumSize(QSize(0, 32)) - Translations.translate_with_setter(self.search_field.setPlaceholderText, "home.search_tags") - self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text())) - self.search_field.returnPressed.connect( - lambda checked=False: self.on_return(self.search_field.text()) - ) - - 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.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_area.setWidget(self.scroll_contents) + super().__init__(library, is_tag_chooser=False) self.create_tag_button = QPushButton() Translations.translate_qobject(self.create_tag_button, "tag.create") self.create_tag_button.clicked.connect(lambda: self.build_tag(self.search_field.text())) - self.root_layout.addWidget(self.search_field) - self.root_layout.addWidget(self.scroll_area) self.root_layout.addWidget(self.create_tag_button) self.update_tags() @@ -97,41 +57,6 @@ class TagDatabasePanel(PanelWidget): ) self.modal.show() - def on_return(self, text: str): - if text and self.first_tag_id >= 0: - # callback(self.first_tag_id) - self.search_field.setText("") - self.update_tags() - else: - self.search_field.setFocus() - self.parentWidget().hide() - - def update_tags(self, query: str | None = None): - # TODO: Look at recycling rather than deleting and re-initializing - logger.info("[Tag Manager Modal] Updating Tags") - while self.scroll_layout.itemAt(0): - self.scroll_layout.takeAt(0).widget().deleteLater() - - tags_results = self.lib.search_tags(name=query) - - for tag in tags_results: - container = QWidget() - row = QHBoxLayout(container) - row.setContentsMargins(0, 0, 0, 0) - row.setSpacing(3) - - if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END): - tag_widget = TagWidget(tag, has_edit=True, has_remove=False) - else: - tag_widget = TagWidget(tag, has_edit=True, has_remove=True) - - tag_widget.on_edit.connect(lambda checked=False, t=tag: self.edit_tag(t)) - tag_widget.on_remove.connect(lambda t=tag: self.remove_tag(t)) - row.addWidget(tag_widget) - self.scroll_layout.addWidget(container) - - self.search_field.setFocus() - def remove_tag(self, tag: Tag): if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END): return @@ -149,29 +74,3 @@ class TagDatabasePanel(PanelWidget): self.lib.remove_tag(tag) self.update_tags() - - def edit_tag(self, tag: Tag): - build_tag_panel = BuildTagPanel(self.lib, tag=tag) - - self.edit_modal = PanelModal( - build_tag_panel, - tag.name, - done_callback=(self.update_tags(self.search_field.text())), - has_save=True, - ) - Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit") - # TODO Check Warning: Expected type 'BuildTagPanel', got 'PanelWidget' instead - self.edit_modal.saved.connect(lambda: self.edit_tag_callback(build_tag_panel)) - self.edit_modal.show() - - def edit_tag_callback(self, btp: BuildTagPanel): - self.lib.update_tag( - btp.build_tag(), set(btp.parent_ids), set(btp.alias_names), set(btp.alias_ids) - ) - self.update_tags(self.search_field.text()) - - def showEvent(self, event: QShowEvent) -> None: # noqa N802 - if not self.is_initialized: - self.update_tags() - self.is_initialized = True - return super().showEvent(event) diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index b95326ac..44af8d22 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -17,10 +17,11 @@ from PySide6.QtWidgets import ( QVBoxLayout, QWidget, ) -from src.core.library import Library +from src.core.constants import RESERVED_TAG_END, RESERVED_TAG_START +from src.core.library import Library, Tag from src.core.palette import ColorType, get_tag_color from src.qt.translations import Translations -from src.qt.widgets.panel import PanelWidget +from src.qt.widgets.panel import PanelModal, PanelWidget from src.qt.widgets.tag import TagWidget logger = structlog.get_logger(__name__) @@ -28,13 +29,19 @@ logger = structlog.get_logger(__name__) class TagSearchPanel(PanelWidget): tag_chosen = Signal(int) + lib: Library + is_initialized: bool = False + first_tag_id: int = None + is_tag_chooser: bool - def __init__(self, library: Library, exclude: list[int] | None = None): + def __init__( + self, library: Library, exclude: list[int] | None = None, is_tag_chooser: bool = True + ): super().__init__() self.lib = library self.exclude = exclude - self.is_initialized: bool = False - self.first_tag_id: int = None + self.is_tag_chooser = is_tag_chooser + self.setMinimumSize(300, 400) self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 0, 6, 0) @@ -44,9 +51,7 @@ class TagSearchPanel(PanelWidget): self.search_field.setMinimumSize(QSize(0, 32)) Translations.translate_with_setter(self.search_field.setPlaceholderText, "home.search_tags") self.search_field.textEdited.connect(lambda: self.update_tags(self.search_field.text())) - self.search_field.returnPressed.connect( - lambda checked=False: self.on_return(self.search_field.text()) - ) + self.search_field.returnPressed.connect(lambda: self.on_return(self.search_field.text())) self.scroll_contents = QWidget() self.scroll_layout = QVBoxLayout(self.scroll_contents) @@ -63,17 +68,10 @@ class TagSearchPanel(PanelWidget): self.root_layout.addWidget(self.search_field) self.root_layout.addWidget(self.scroll_area) - def on_return(self, text: str): - if text and self.first_tag_id is not None: - self.tag_chosen.emit(self.first_tag_id) - self.search_field.setText("") - self.update_tags() - else: - self.search_field.setFocus() - self.parentWidget().hide() - def update_tags(self, query: str | None = None): - logger.info("[Tag Search Modal] Updating Tags") + logger.info("[Tag Search Super Class] Updating Tags") + + # TODO: Look at recycling rather than deleting and re-initializing while self.scroll_layout.count(): self.scroll_layout.takeAt(0).widget().deleteLater() @@ -87,45 +85,90 @@ class TagSearchPanel(PanelWidget): if self.exclude is not None and tag.id in self.exclude: continue - c = QWidget() - layout = QHBoxLayout(c) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(3) - tw = TagWidget(tag, has_edit=False, has_remove=False) - ab = QPushButton() - ab.setMinimumSize(23, 23) - ab.setMaximumSize(23, 23) - ab.setText("+") - ab.setStyleSheet( - f"QPushButton{{" - f"background: {get_tag_color(ColorType.PRIMARY, tag.color)};" - f"color: {get_tag_color(ColorType.TEXT, tag.color)};" - f"font-weight: 600;" - f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: {math.ceil(self.devicePixelRatio())}px;" - f"padding-bottom: 5px;" - f"font-size: 20px;" - f"}}" - f"QPushButton::hover" - f"{{" - f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};" - f"color: {get_tag_color(ColorType.DARK_ACCENT, tag.color)};" - f"background: {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};" - f"}}" + container = QWidget() + row = QHBoxLayout(container) + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(3) + + tag_widget = TagWidget( + tag, + has_edit=True, + has_remove=(not self.is_tag_chooser) + and (tag.id not in range(RESERVED_TAG_START, RESERVED_TAG_END)), ) + tag_widget.on_edit.connect(lambda t=tag: self.edit_tag(t)) + tag_widget.on_remove.connect(lambda t=tag: self.remove_tag(t)) + row.addWidget(tag_widget) - ab.clicked.connect(lambda checked=False, x=tag.id: self.tag_chosen.emit(x)) + if self.is_tag_chooser: + add_button = QPushButton() + add_button.setMinimumSize(23, 23) + add_button.setMaximumSize(23, 23) + add_button.setText("+") + add_button.setStyleSheet( + f"QPushButton{{" + f"background: {get_tag_color(ColorType.PRIMARY, tag.color)};" + f"color: {get_tag_color(ColorType.TEXT, tag.color)};" + f"font-weight: 600;" + f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};" + f"border-radius: 6px;" + f"border-style:solid;" + f"border-width: {math.ceil(self.devicePixelRatio())}px;" + f"padding-bottom: 5px;" + f"font-size: 20px;" + f"}}" + f"QPushButton::hover" + f"{{" + f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};" + f"color: {get_tag_color(ColorType.DARK_ACCENT, tag.color)};" + f"background: {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};" + f"}}" + ) + add_button.clicked.connect(lambda x=tag.id: self.tag_chosen.emit(x)) + row.addWidget(add_button) - layout.addWidget(tw) - layout.addWidget(ab) - self.scroll_layout.addWidget(c) + self.scroll_layout.addWidget(container) self.search_field.setFocus() + def on_return(self, text: str): + if text and self.first_tag_id is not None: + if self.is_tag_chooser: + self.tag_chosen.emit(self.first_tag_id) + self.search_field.setText("") + self.update_tags() + else: + self.search_field.setFocus() + self.parentWidget().hide() + def showEvent(self, event: QShowEvent) -> None: # noqa N802 if not self.is_initialized: self.update_tags() self.is_initialized = True return super().showEvent(event) + + def remove_tag(self, tag: Tag): + raise NotImplementedError + + def edit_tag(self, tag: Tag): + # only import here because of circular imports + from src.qt.modals.build_tag import BuildTagPanel + + def edit_tag_callback(btp: BuildTagPanel): + self.lib.update_tag( + btp.build_tag(), set(btp.parent_ids), set(btp.alias_names), set(btp.alias_ids) + ) + self.update_tags(self.search_field.text()) + + build_tag_panel = BuildTagPanel(self.lib, tag=tag) + + self.edit_modal = PanelModal( + build_tag_panel, + tag.name, + done_callback=(self.update_tags(self.search_field.text())), + has_save=True, + ) + Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit") + + self.edit_modal.saved.connect(lambda: edit_tag_callback(build_tag_panel)) + self.edit_modal.show()