refactor: TagDatabasePanel now inherits from TagSearchPanel for code deduplication (#699)

* refactor: TagDatabasePanel now inherits from TagSearchPanel for code deduplication

* refactor: rename callback method

* extract creation of row items to separate method
This commit is contained in:
Jann Stute
2025-01-13 22:46:57 +01:00
committed by GitHub
parent a272b18637
commit 5bab00aa6d
2 changed files with 97 additions and 151 deletions

View File

@@ -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)

View File

@@ -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
exclude: list[int]
def __init__(self, library: Library, exclude: list[int] | None = None):
def __init__(self, library: Library, exclude: list[int] = 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.exclude = exclude or []
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,40 +68,32 @@ 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 __build_row_item_widget(self, tag: Tag):
container = QWidget()
row = QHBoxLayout(container)
row.setContentsMargins(0, 0, 0, 0)
row.setSpacing(3)
def update_tags(self, query: str | None = None):
logger.info("[Tag Search Modal] Updating Tags")
while self.scroll_layout.count():
self.scroll_layout.takeAt(0).widget().deleteLater()
has_remove_button = False
if not self.is_tag_chooser:
has_remove_button = tag.id not in range(RESERVED_TAG_START, RESERVED_TAG_END)
tag_results = self.lib.search_tags(name=query)
if len(tag_results) > 0:
self.first_tag_id = tag_results[0].id
else:
self.first_tag_id = None
tag_widget = TagWidget(
tag,
has_edit=True,
has_remove=has_remove_button,
)
for tag in tag_results:
if self.exclude is not None and tag.id in self.exclude:
continue
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)
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(
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)};"
@@ -115,17 +112,67 @@ class TagSearchPanel(PanelWidget):
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)
return container
ab.clicked.connect(lambda checked=False, x=tag.id: self.tag_chosen.emit(x))
def update_tags(self, query: str | None = None):
logger.info("[Tag Search Super Class] Updating Tags")
layout.addWidget(tw)
layout.addWidget(ab)
self.scroll_layout.addWidget(c)
# TODO: Look at recycling rather than deleting and re-initializing
while self.scroll_layout.count():
self.scroll_layout.takeAt(0).widget().deleteLater()
tag_results = self.lib.search_tags(name=query)
if len(tag_results) > 0:
self.first_tag_id = tag_results[0].id
else:
self.first_tag_id = None
for tag in tag_results:
if tag.id not in self.exclude:
self.scroll_layout.addWidget(self.__build_row_item_widget(tag))
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):
pass
def edit_tag(self, tag: Tag):
# only import here because of circular imports
from src.qt.modals.build_tag import BuildTagPanel
def 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: callback(build_tag_panel))
self.edit_modal.show()