mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-29 06:10:51 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user