From c833eb6003a12a0d7922fe54e03757479c95edac Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 13:32:32 -0400 Subject: [PATCH 01/10] Non-destructively move the widget files --- .../{mixed/color_box.py => views/fields/color_box_widget_view.py} | 0 .../qt/{mixed/field_widget.py => views/fields/field_container.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/tagstudio/qt/{mixed/color_box.py => views/fields/color_box_widget_view.py} (100%) rename src/tagstudio/qt/{mixed/field_widget.py => views/fields/field_container.py} (100%) diff --git a/src/tagstudio/qt/mixed/color_box.py b/src/tagstudio/qt/views/fields/color_box_widget_view.py similarity index 100% rename from src/tagstudio/qt/mixed/color_box.py rename to src/tagstudio/qt/views/fields/color_box_widget_view.py diff --git a/src/tagstudio/qt/mixed/field_widget.py b/src/tagstudio/qt/views/fields/field_container.py similarity index 100% rename from src/tagstudio/qt/mixed/field_widget.py rename to src/tagstudio/qt/views/fields/field_container.py From 90ce9589352a9b766c0d9c9113073ecaeb8e7056 Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Thu, 30 Oct 2025 20:18:52 -0400 Subject: [PATCH 02/10] Refactor --- .../tag_box_widget_controller.py} | 10 +- src/tagstudio/qt/mixed/field_containers.py | 46 +-- src/tagstudio/qt/mixed/tag_color_manager.py | 4 +- .../qt/views/fields/color_box_widget_view.py | 2 +- .../qt/views/fields/field_container.py | 264 ++++++++++-------- src/tagstudio/qt/views/fields/field_widget.py | 9 + .../tag_box_widget_view.py} | 2 +- .../fields/text_field_widget.py} | 15 +- 8 files changed, 194 insertions(+), 158 deletions(-) rename src/tagstudio/qt/controllers/{tag_box_controller.py => fields/tag_box_widget_controller.py} (91%) create mode 100644 src/tagstudio/qt/views/fields/field_widget.py rename src/tagstudio/qt/views/{tag_box_view.py => fields/tag_box_widget_view.py} (97%) rename src/tagstudio/qt/{mixed/text_field.py => views/fields/text_field_widget.py} (84%) diff --git a/src/tagstudio/qt/controllers/tag_box_controller.py b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py similarity index 91% rename from src/tagstudio/qt/controllers/tag_box_controller.py rename to src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py index 2a5865d8b..43ac78010 100644 --- a/src/tagstudio/qt/controllers/tag_box_controller.py +++ b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py @@ -12,8 +12,8 @@ from tagstudio.core.library.alchemy.models import Tag from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.build_tag import BuildTagPanel +from tagstudio.qt.views.fields.tag_box_widget_view import TagBoxWidgetView from tagstudio.qt.views.panel_modal import PanelModal -from tagstudio.qt.views.tag_box_view import TagBoxWidgetView if TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver @@ -34,7 +34,7 @@ def set_entries(self, entries: list[int]) -> None: self.__entries = entries @override - def _on_click(self, tag: Tag) -> None: # type: ignore[misc] + def _on_click(self, tag: Tag) -> None: match self.__driver.settings.tag_click_action: case TagClickActionOption.OPEN_EDIT: self._on_edit(tag) @@ -58,7 +58,7 @@ def _on_click(self, tag: Tag) -> None: # type: ignore[misc] ) @override - def _on_remove(self, tag: Tag) -> None: # type: ignore[misc] + def _on_remove(self, tag: Tag) -> None: logger.info( "[TagBoxWidget] remove_tag", selected=self.__entries, @@ -70,7 +70,7 @@ def _on_remove(self, tag: Tag) -> None: # type: ignore[misc] self.on_update.emit() @override - def _on_edit(self, tag: Tag) -> None: # type: ignore[misc] + def _on_edit(self, tag: Tag) -> None: build_tag_panel = BuildTagPanel(self.__driver.lib, tag=tag) edit_modal = PanelModal( @@ -92,7 +92,7 @@ def _on_edit(self, tag: Tag) -> None: # type: ignore[misc] edit_modal.show() @override - def _on_search(self, tag: Tag) -> None: # type: ignore[misc] + def _on_search(self, tag: Tag) -> 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) diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/mixed/field_containers.py index 1128e494c..3c9e97554 100644 --- a/src/tagstudio/qt/mixed/field_containers.py +++ b/src/tagstudio/qt/mixed/field_containers.py @@ -33,13 +33,13 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag from tagstudio.core.utils.types import unwrap -from tagstudio.qt.controllers.tag_box_controller import TagBoxWidget +from tagstudio.qt.controllers.fields.tag_box_widget_controller import TagBoxWidget from tagstudio.qt.mixed.datetime_picker import DatetimePicker -from tagstudio.qt.mixed.field_widget import FieldContainer -from tagstudio.qt.mixed.text_field import TextWidget from tagstudio.qt.translations import Translations from tagstudio.qt.views.edit_text_box_modal import EditTextBox from tagstudio.qt.views.edit_text_line_modal import EditTextLine +from tagstudio.qt.views.fields.field_container import FieldContainer +from tagstudio.qt.views.fields.text_field_widget import TextFieldWidget from tagstudio.qt.views.panel_modal import PanelModal if typing.TYPE_CHECKING: @@ -272,8 +272,8 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): text = "Mixed Data" title = f"{field.type.name} ({field.type.type.value})" - inner_widget = TextWidget(title, text) - container.set_inner_widget(inner_widget) + inner_widget = TextFieldWidget(title, text) + container.set_field_widget(inner_widget) if not is_mixed: modal = PanelModal( EditTextLine(field.value), @@ -311,8 +311,8 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): else: text = "Mixed Data" title = f"{field.type.name} (Text Box)" - inner_widget = TextWidget(title, text) - container.set_inner_widget(inner_widget) + inner_widget = TextFieldWidget(title, text) + container.set_field_widget(inner_widget) if not is_mixed: modal = PanelModal( EditTextBox(field.value), @@ -352,8 +352,8 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): title += " (Unknown Format)" text = str(field.value) - inner_widget = TextWidget(title, text) - container.set_inner_widget(inner_widget) + inner_widget = TextFieldWidget(title, text) + container.set_field_widget(inner_widget) modal = PanelModal( DatetimePicker(self.driver, field.value or dt.now()), @@ -379,15 +379,15 @@ def write_container(self, index: int, field: BaseField, is_mixed: bool = False): else: text = "Mixed Data" title = f"{field.type.name} (Wacky Date)" - inner_widget = TextWidget(title, text) - container.set_inner_widget(inner_widget) + inner_widget = TextFieldWidget(title, text) + container.set_field_widget(inner_widget) else: logger.warning("[FieldContainers][write_container] Unknown Field", field=field) container.set_title(field.type.name) container.set_inline(False) title = f"{field.type.name} (Unknown Field Type)" - inner_widget = TextWidget(title, field.type.name) - container.set_inner_widget(inner_widget) + inner_widget = TextFieldWidget(title, field.type.name) + container.set_field_widget(inner_widget) container.set_remove_callback( lambda: self.remove_message_box( prompt=self.remove_field_prompt(field.type.name), @@ -425,28 +425,28 @@ def write_tag_container( container.set_inline(False) if not is_mixed: - inner_widget = container.get_inner_widget() + field_widget = container.get_field_widget() - if isinstance(inner_widget, TagBoxWidget): + if isinstance(field_widget, TagBoxWidget): with catch_warnings(record=True): - inner_widget.on_update.disconnect() + field_widget.on_update.disconnect() else: - inner_widget = TagBoxWidget( + field_widget = TagBoxWidget( "Tags", self.driver, ) - container.set_inner_widget(inner_widget) - inner_widget.set_entries([e.id for e in self.cached_entries]) - inner_widget.set_tags(tags) + container.set_field_widget(field_widget) + field_widget.set_entries([e.id for e in self.cached_entries]) + field_widget.set_tags(tags) - inner_widget.on_update.connect( + field_widget.on_update.connect( lambda: (self.update_from_entry(self.cached_entries[0].id, update_badges=True)) ) else: text = "Mixed Data" - inner_widget = TextWidget("Mixed Tags", text) - container.set_inner_widget(inner_widget) + field_widget = TextFieldWidget("Mixed Tags", text) + container.set_field_widget(field_widget) container.set_edit_callback() container.set_remove_callback() diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py index d381fcac0..721d42dc8 100644 --- a/src/tagstudio/qt/mixed/tag_color_manager.py +++ b/src/tagstudio/qt/mixed/tag_color_manager.py @@ -25,8 +25,8 @@ from tagstudio.core.enums import Theme from tagstudio.qt.mixed.build_namespace import BuildNamespacePanel from tagstudio.qt.mixed.color_box import ColorBoxWidget -from tagstudio.qt.mixed.field_widget import FieldContainer from tagstudio.qt.translations import Translations +from tagstudio.qt.views.fields.field_container import FieldContainer from tagstudio.qt.views.panel_modal import PanelModal logger = structlog.get_logger(__name__) @@ -130,7 +130,7 @@ def setup_color_groups(self): ) ) field_container = FieldContainer(self.driver.lib.get_namespace_name(group)) - field_container.set_inner_widget(color_box) + field_container.set_field_widget(color_box) if not group.startswith(RESERVED_NAMESPACE_PREFIX): field_container.set_remove_callback( lambda checked=False, g=group: self.delete_namespace_dialog( diff --git a/src/tagstudio/qt/views/fields/color_box_widget_view.py b/src/tagstudio/qt/views/fields/color_box_widget_view.py index 20866c438..7d7d03de5 100644 --- a/src/tagstudio/qt/views/fields/color_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/color_box_widget_view.py @@ -15,10 +15,10 @@ from tagstudio.core.library.alchemy.models import TagColorGroup from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.build_color import BuildColorPanel -from tagstudio.qt.mixed.field_widget import FieldWidget from tagstudio.qt.mixed.tag_color_label import TagColorLabel from tagstudio.qt.models.palette import ColorType, get_tag_color from tagstudio.qt.translations import Translations +from tagstudio.qt.views.fields.field_widget import FieldWidget from tagstudio.qt.views.layouts.flow_layout import FlowLayout from tagstudio.qt.views.panel_modal import PanelModal diff --git a/src/tagstudio/qt/views/fields/field_container.py b/src/tagstudio/qt/views/fields/field_container.py index d2678b556..4844ca151 100644 --- a/src/tagstudio/qt/views/fields/field_container.py +++ b/src/tagstudio/qt/views/fields/field_container.py @@ -1,207 +1,223 @@ -# Copyright (C) 2025 Travis Abendshien (CyanVoxel). -# Licensed under the GPL-3.0 License. -# Created for TagStudio: https://github.com/CyanVoxel/TagStudio - - import math from collections.abc import Callable from pathlib import Path from typing import override from warnings import catch_warnings -import structlog from PIL import Image, ImageQt -from PySide6.QtCore import QEvent, Qt -from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent +from PySide6.QtCore import QEvent +from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent, Qt from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget from tagstudio.core.enums import Theme - -logger = structlog.get_logger(__name__) +from tagstudio.qt.views.fields.field_widget import FieldWidget + +# TODO: reference a resources folder rather than path.parents[2]? +clipboard_icon: Image.Image = Image.open( + str(Path(__file__).parents[3] / "resources/qt/images/clipboard_icon_128.png") +).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) +clipboard_icon.load() + +edit_icon: Image.Image = Image.open( + str(Path(__file__).parents[3] / "resources/qt/images/edit_icon_128.png") +).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) +edit_icon.load() + +trash_icon: Image.Image = Image.open( + str(Path(__file__).parents[3] / "resources/qt/images/trash_icon_128.png") +).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) +trash_icon.load() + +# TODO: There should be a global button theme somewhere. +CONTAINER_STYLE = f""" + QWidget#fieldContainer{{ + border-radius: 4px; + }} + QWidget#fieldContainer::hover{{ + background-color: {Theme.COLOR_HOVER.value}; + }} + QWidget#fieldContainer::pressed{{ + background-color: {Theme.COLOR_PRESSED.value}; + }} +""" + +BUTTON_SIZE = 24 + +type Callback = Callable[[], None] | None class FieldContainer(QWidget): - # TODO: reference a resources folder rather than path.parents[2]? - clipboard_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/clipboard_icon_128.png") - ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) - clipboard_icon_128.load() - - edit_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/edit_icon_128.png") - ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) - edit_icon_128.load() - - trash_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/trash_icon_128.png") - ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) - trash_icon_128.load() - - # TODO: There should be a global button theme somewhere. - container_style = ( - f"QWidget#fieldContainer{{" - "border-radius:4px;" - f"}}" - f"QWidget#fieldContainer::hover{{" - f"background-color:{Theme.COLOR_HOVER.value};" - f"}}" - f"QWidget#fieldContainer::pressed{{" - f"background-color:{Theme.COLOR_PRESSED.value};" - f"}}" - ) + """A container that holds a field widget and provides some relevant information and controls.""" def __init__(self, title: str = "Field", inline: bool = True) -> None: super().__init__() + + self.__copy_callback: Callback = None + self.__edit_callback: Callback = None + self.__remove_callback: Callback = None + + # Container self.setObjectName("fieldContainer") self.title: str = title self.inline: bool = inline - self.copy_callback: Callable[[], None] | None = None - self.edit_callback: Callable[[], None] | None = None - self.remove_callback: Callable[[], None] | None = None - button_size = 24 - - self.root_layout = QVBoxLayout(self) - self.root_layout.setObjectName("baseLayout") - self.root_layout.setContentsMargins(0, 0, 0, 0) - - self.inner_layout = QVBoxLayout() - self.inner_layout.setObjectName("innerLayout") - self.inner_layout.setContentsMargins(6, 0, 6, 6) - self.inner_layout.setSpacing(0) + self.setStyleSheet(CONTAINER_STYLE) + + self.base_layout = QVBoxLayout(self) + self.base_layout.setObjectName("baseLayout") + self.base_layout.setContentsMargins(0, 0, 0, 0) + + # Field container + self.container_layout = QVBoxLayout() + self.container_layout.setObjectName("fieldContainerLayout") + self.container_layout.setContentsMargins(6, 0, 6, 6) + self.container_layout.setSpacing(0) + self.field_container = QWidget() self.field_container.setObjectName("fieldContainer") - self.field_container.setLayout(self.inner_layout) - self.root_layout.addWidget(self.field_container) + self.field_container.setLayout(self.container_layout) + + self.base_layout.addWidget(self.field_container) + # Title self.title_container = QWidget() self.title_layout = QHBoxLayout(self.title_container) self.title_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) - self.title_layout.setObjectName("fieldLayout") + self.title_layout.setObjectName("titleLayout") self.title_layout.setContentsMargins(0, 0, 0, 0) self.title_layout.setSpacing(0) - self.inner_layout.addWidget(self.title_container) - - self.title_widget = QLabel() - self.title_widget.setMinimumHeight(button_size) - self.title_widget.setObjectName("fieldTitle") - self.title_widget.setWordWrap(True) - self.title_widget.setText(title) - self.title_layout.addWidget(self.title_widget) + + self.container_layout.addWidget(self.title_container) + + self.title_label = QLabel() + self.title_label.setMinimumHeight(BUTTON_SIZE) + self.title_label.setObjectName("titleLabel") + self.title_label.setWordWrap(True) + self.title_label.setText(title) + + self.title_layout.addWidget(self.title_label) self.title_layout.addStretch(2) + # Copy button self.copy_button = QPushButton() self.copy_button.setObjectName("copyButton") - self.copy_button.setMinimumSize(button_size, button_size) - self.copy_button.setMaximumSize(button_size, button_size) + self.copy_button.setMinimumSize(BUTTON_SIZE, BUTTON_SIZE) + self.copy_button.setMaximumSize(BUTTON_SIZE, BUTTON_SIZE) self.copy_button.setFlat(True) - self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.clipboard_icon_128))) + self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(clipboard_icon))) self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.title_layout.addWidget(self.copy_button) self.copy_button.setHidden(True) + self.title_layout.addWidget(self.copy_button) + + # Edit button self.edit_button = QPushButton() self.edit_button.setObjectName("editButton") - self.edit_button.setMinimumSize(button_size, button_size) - self.edit_button.setMaximumSize(button_size, button_size) + self.edit_button.setMinimumSize(BUTTON_SIZE, BUTTON_SIZE) + self.edit_button.setMaximumSize(BUTTON_SIZE, BUTTON_SIZE) self.edit_button.setFlat(True) - self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128))) + self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(edit_icon))) self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.title_layout.addWidget(self.edit_button) self.edit_button.setHidden(True) + self.title_layout.addWidget(self.edit_button) + + # Remove button self.remove_button = QPushButton() self.remove_button.setObjectName("removeButton") - self.remove_button.setMinimumSize(button_size, button_size) - self.remove_button.setMaximumSize(button_size, button_size) + self.remove_button.setMinimumSize(BUTTON_SIZE, BUTTON_SIZE) + self.remove_button.setMaximumSize(BUTTON_SIZE, BUTTON_SIZE) self.remove_button.setFlat(True) - self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.trash_icon_128))) + self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(trash_icon))) self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.title_layout.addWidget(self.remove_button) self.remove_button.setHidden(True) + self.title_layout.addWidget(self.remove_button) + + # Field self.field = QWidget() self.field.setObjectName("field") self.field_layout = QHBoxLayout() self.field_layout.setObjectName("fieldLayout") self.field_layout.setContentsMargins(0, 0, 0, 0) self.field.setLayout(self.field_layout) - self.inner_layout.addWidget(self.field) + self.container_layout.addWidget(self.field) + + # Fill data self.set_title(title) - self.setStyleSheet(FieldContainer.container_style) - def set_copy_callback(self, callback: Callable[[], None] | None = None) -> None: + def set_title(self, title: str) -> None: + """Sets the title of the field container.""" + self.title = self.title = f"

{title}

" + self.title_label.setText(self.title) + + def set_inline(self, inline: bool) -> None: + """Sets whether the field container is inline or not.""" + self.inline = inline + + def set_field_widget(self, widget: FieldWidget) -> None: + """Sets the field widget the container holds.""" + if self.field_layout.itemAt(0): + old: QWidget = self.field_layout.itemAt(0).widget() + self.field_layout.removeWidget(old) + old.deleteLater() + + self.field_layout.addWidget(widget) + + def get_field_widget(self) -> QWidget | None: + """Returns the field widget the container holds.""" + if self.field_layout.itemAt(0): + return self.field_layout.itemAt(0).widget() + + return None + + # Callbacks + def set_copy_callback(self, callback: Callback = None) -> None: + """Sets the callback to be called when the 'Copy' button is pressed.""" with catch_warnings(record=True): self.copy_button.clicked.disconnect() - self.copy_callback = callback + self.__copy_callback = callback if callback: self.copy_button.clicked.connect(callback) - def set_edit_callback(self, callback: Callable[[], None] | None = None) -> None: + def set_edit_callback(self, callback: Callback = None) -> None: + """Sets the callback to be called when the 'Edit' button is pressed.""" with catch_warnings(record=True): self.edit_button.clicked.disconnect() - self.edit_callback = callback + self.__edit_callback = callback if callback: self.edit_button.clicked.connect(callback) - def set_remove_callback(self, callback: Callable[[], None] | None = None) -> None: + def set_remove_callback(self, callback: Callback = None) -> None: + """Sets the callback to be called when the 'Remove' button is pressed.""" with catch_warnings(record=True): self.remove_button.clicked.disconnect() - self.remove_callback = callback + self.__remove_callback = callback if callback: self.remove_button.clicked.connect(callback) - def set_inner_widget(self, widget: "FieldWidget") -> None: - if self.field_layout.itemAt(0): - old: QWidget = self.field_layout.itemAt(0).widget() - self.field_layout.removeWidget(old) - old.deleteLater() - - self.field_layout.addWidget(widget) - - def get_inner_widget(self) -> QWidget | None: - if self.field_layout.itemAt(0): - return self.field_layout.itemAt(0).widget() - return None - - def set_title(self, title: str) -> None: - self.title = self.title = f"

{title}

" - self.title_widget.setText(self.title) - - def set_inline(self, inline: bool) -> None: - self.inline = inline + # Events + @override + def resizeEvent(self, event: QResizeEvent) -> None: + self.title_label.setFixedWidth(int(event.size().width() // 1.5)) + return super().resizeEvent(event) @override def enterEvent(self, event: QEnterEvent) -> None: # NOTE: You could pass the hover event to the FieldWidget if needed. - if self.copy_callback: - self.copy_button.setHidden(False) - if self.edit_callback: - self.edit_button.setHidden(False) - if self.remove_callback: - self.remove_button.setHidden(False) + self.copy_button.setHidden(self.__copy_callback is None) + self.edit_button.setHidden(self.__edit_callback is None) + self.remove_button.setHidden(self.__remove_callback is None) + return super().enterEvent(event) @override def leaveEvent(self, event: QEvent) -> None: - if self.copy_callback: - self.copy_button.setHidden(True) - if self.edit_callback: - self.edit_button.setHidden(True) - if self.remove_callback: - self.remove_button.setHidden(True) - return super().leaveEvent(event) - - @override - def resizeEvent(self, event: QResizeEvent) -> None: - self.title_widget.setFixedWidth(int(event.size().width() // 1.5)) - return super().resizeEvent(event) - + self.copy_button.setHidden(True) + self.edit_button.setHidden(True) + self.remove_button.setHidden(True) -class FieldWidget(QWidget): - def __init__(self, title: str) -> None: - super().__init__() - self.title: str = title + return super().leaveEvent(event) diff --git a/src/tagstudio/qt/views/fields/field_widget.py b/src/tagstudio/qt/views/fields/field_widget.py new file mode 100644 index 000000000..254771aa0 --- /dev/null +++ b/src/tagstudio/qt/views/fields/field_widget.py @@ -0,0 +1,9 @@ +from PySide6.QtWidgets import QWidget + + +class FieldWidget(QWidget): + """A widget representing a field of an entry.""" + + def __init__(self, title: str) -> None: + super().__init__() + self.title: str = title diff --git a/src/tagstudio/qt/views/tag_box_view.py b/src/tagstudio/qt/views/fields/tag_box_widget_view.py similarity index 97% rename from src/tagstudio/qt/views/tag_box_view.py rename to src/tagstudio/qt/views/fields/tag_box_widget_view.py index bf24a88cf..24fa1c645 100644 --- a/src/tagstudio/qt/views/tag_box_view.py +++ b/src/tagstudio/qt/views/fields/tag_box_widget_view.py @@ -9,8 +9,8 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Tag -from tagstudio.qt.mixed.field_widget import FieldWidget from tagstudio.qt.mixed.tag_widget import TagWidget +from tagstudio.qt.views.fields.field_widget import FieldWidget from tagstudio.qt.views.layouts.flow_layout import FlowLayout if TYPE_CHECKING: diff --git a/src/tagstudio/qt/mixed/text_field.py b/src/tagstudio/qt/views/fields/text_field_widget.py similarity index 84% rename from src/tagstudio/qt/mixed/text_field.py rename to src/tagstudio/qt/views/fields/text_field_widget.py index d8052ce96..ccc6c2f8c 100644 --- a/src/tagstudio/qt/mixed/text_field.py +++ b/src/tagstudio/qt/views/fields/text_field_widget.py @@ -8,26 +8,37 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import QHBoxLayout, QLabel -from tagstudio.qt.mixed.field_widget import FieldWidget +from tagstudio.qt.views.fields.field_widget import FieldWidget -class TextWidget(FieldWidget): +class TextFieldWidget(FieldWidget): + """A widget representing a text field of an entry.""" + def __init__(self, title, text: str) -> None: super().__init__(title) + + # Widget self.setObjectName("textBox") + self.base_layout = QHBoxLayout() self.base_layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.base_layout) + + # Label self.text_label = QLabel() self.text_label.setStyleSheet("font-size: 12px") self.text_label.setWordWrap(True) self.text_label.setTextFormat(Qt.TextFormat.MarkdownText) self.text_label.setOpenExternalLinks(True) self.text_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) + self.base_layout.addWidget(self.text_label) + + # Fill data self.set_text(text) def set_text(self, text: str): + """Sets the text of the field.""" text = linkify(text) self.text_label.setText(text) From 1747798c64d0efc9b7195ce2d4cb2b5ca094b431 Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Thu, 30 Oct 2025 21:07:31 -0400 Subject: [PATCH 03/10] Refactor color box widget --- .../fields/color_box_widget_controller.py | 64 +++++++ src/tagstudio/qt/mixed/tag_color_manager.py | 2 +- .../qt/views/fields/color_box_widget_view.py | 161 ++++++++---------- 3 files changed, 133 insertions(+), 94 deletions(-) create mode 100644 src/tagstudio/qt/controllers/fields/color_box_widget_controller.py diff --git a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py b/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py new file mode 100644 index 000000000..978994a39 --- /dev/null +++ b/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py @@ -0,0 +1,64 @@ +from typing import TYPE_CHECKING + +import structlog +from PySide6.QtWidgets import QMessageBox + +from tagstudio.core.library.alchemy.models import TagColorGroup +from tagstudio.qt.mixed.build_color import BuildColorPanel +from tagstudio.qt.translations import Translations +from tagstudio.qt.views.fields.color_box_widget_view import ColorBoxWidgetView +from tagstudio.qt.views.panel_modal import PanelModal + +if TYPE_CHECKING: + from tagstudio.core.library.alchemy.library import Library + +logger = structlog.get_logger(__name__) + + +class ColorBoxWidget(ColorBoxWidgetView): + def __init__(self, group: str, colors: list[TagColorGroup], library: "Library") -> None: + super().__init__(group, colors, library) + self.__lib: Library = library + + def _on_edit_color(self, color_group: TagColorGroup) -> None: + build_color_panel = BuildColorPanel(self.__lib, color_group) + + edit_color_modal = PanelModal( + build_color_panel, + "Edit Color", + has_save=True, + ) + + edit_color_modal.saved.connect( + lambda: (self.__lib.update_color(*build_color_panel.build_color()), self.updated.emit()) + ) + + edit_color_modal.show() + + def _on_delete_color(self, color_group: TagColorGroup) -> None: + # Dialogue box + message_box = QMessageBox( + QMessageBox.Icon.Warning, + Translations["color.delete"], + Translations.format("color.confirm_delete", color_name=color_group.name), + ) + + # Buttons + cancel_button = message_box.addButton( + Translations["generic.cancel_alt"], QMessageBox.ButtonRole.RejectRole + ) + message_box.addButton( + Translations["generic.delete_alt"], QMessageBox.ButtonRole.DestructiveRole + ) + message_box.setEscapeButton(cancel_button) + + # Dialogue box result + result = message_box.exec_() + logger.info(QMessageBox.ButtonRole.DestructiveRole.value) + + if result != QMessageBox.ButtonRole.ActionRole.value: + return + + logger.info("[ColorBoxWidget] Removing color", color=color_group) + self.__lib.delete_color(color_group) + self.updated.emit() diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py index 721d42dc8..d739f84ac 100644 --- a/src/tagstudio/qt/mixed/tag_color_manager.py +++ b/src/tagstudio/qt/mixed/tag_color_manager.py @@ -23,8 +23,8 @@ from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.enums import Theme +from tagstudio.qt.controllers.fields.color_box_widget_controller import ColorBoxWidget from tagstudio.qt.mixed.build_namespace import BuildNamespacePanel -from tagstudio.qt.mixed.color_box import ColorBoxWidget from tagstudio.qt.translations import Translations from tagstudio.qt.views.fields.field_container import FieldContainer from tagstudio.qt.views.panel_modal import PanelModal diff --git a/src/tagstudio/qt/views/fields/color_box_widget_view.py b/src/tagstudio/qt/views/fields/color_box_widget_view.py index 7d7d03de5..563b3bfb1 100644 --- a/src/tagstudio/qt/views/fields/color_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/color_box_widget_view.py @@ -3,121 +3,125 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import typing from collections.abc import Iterable +from typing import TYPE_CHECKING import structlog from PySide6.QtCore import Signal -from PySide6.QtWidgets import QMessageBox, QPushButton +from PySide6.QtWidgets import QPushButton from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.library.alchemy.enums import TagColorEnum +from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import TagColorGroup from tagstudio.core.utils.types import unwrap -from tagstudio.qt.mixed.build_color import BuildColorPanel from tagstudio.qt.mixed.tag_color_label import TagColorLabel from tagstudio.qt.models.palette import ColorType, get_tag_color -from tagstudio.qt.translations import Translations from tagstudio.qt.views.fields.field_widget import FieldWidget from tagstudio.qt.views.layouts.flow_layout import FlowLayout -from tagstudio.qt.views.panel_modal import PanelModal -if typing.TYPE_CHECKING: +if TYPE_CHECKING: from tagstudio.core.library.alchemy.library import Library logger = structlog.get_logger(__name__) - -class ColorBoxWidget(FieldWidget): +BUTTON_STYLE = f""" + QPushButton{{ + background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; + color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)}; + font-weight: 600; + border-color: {get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)}; + border-radius: 6px; + border-style: solid; + border-width: 2px; + padding-right: 4px; + padding-bottom: 2px; + padding-left: 4px; + font-size: 15px; + }} + QPushButton::hover{{ + border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; + }} + QPushButton::pressed{{ + background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; + color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; + border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)}; + }} + QPushButton::focus{{ + border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)}; + outline:none; + }} +""" + + +class ColorBoxWidgetView(FieldWidget): + __lib: Library updated = Signal() - def __init__( - self, - group: str, - colors: list["TagColorGroup"], - library: "Library", - ) -> None: - self.namespace = group + def __init__(self, group: str, colors: list["TagColorGroup"], library: "Library") -> None: + self.namespace: str = group self.colors: list[TagColorGroup] = colors - self.lib: Library = library + self.__lib: Library = library - title = "" if not self.lib.engine else self.lib.get_namespace_name(group) + title: str = "" if not self.__lib.engine else self.__lib.get_namespace_name(group) super().__init__(title) - self.add_button_stylesheet = ( - f"QPushButton{{" - f"background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};" - f"color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)};" - f"font-weight: 600;" - f"border-color:{get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)};" - f"border-radius: 6px;" - f"border-style:solid;" - f"border-width: 2px;" - f"padding-right: 4px;" - f"padding-bottom: 2px;" - f"padding-left: 4px;" - f"font-size: 15px" - f"}}" - f"QPushButton::hover{{" - f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};" - f"}}" - f"QPushButton::pressed{{" - f"background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};" - f"color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};" - f"border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};" - f"}}" - f"QPushButton::focus{{" - f"border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};" - f"outline:none;" - f"}}" - ) - + # Color box self.setObjectName("colorBox") - self.base_layout = FlowLayout() - self.base_layout.enable_grid_optimizations(value=True) - self.base_layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(self.base_layout) + self.__base_layout = FlowLayout() + self.__base_layout.enable_grid_optimizations(value=True) + self.__base_layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(self.__base_layout) + + # Add button + self.add_button_stylesheet = BUTTON_STYLE + # Fill data self.set_colors(self.colors) - def set_colors(self, colors: Iterable[TagColorGroup]): + def set_colors(self, colors: Iterable[TagColorGroup]) -> None: colors_ = sorted( - list(colors), key=lambda color: self.lib.get_namespace_name(color.namespace) + list(colors), key=lambda color: self.__lib.get_namespace_name(color.namespace) ) is_mutable = not self.namespace.startswith(RESERVED_NAMESPACE_PREFIX) max_width = 60 - color_widgets: list[TagColorLabel] = [] - while self.base_layout.itemAt(0): - unwrap(self.base_layout.takeAt(0)).widget().deleteLater() + while self.__base_layout.itemAt(0): + unwrap(self.__base_layout.takeAt(0)).widget().deleteLater() + + color_widgets: list[TagColorLabel] = [] for color in colors_: color_widget = TagColorLabel( color=color, has_edit=is_mutable, has_remove=is_mutable, - library=self.lib, + library=self.__lib, ) + hint = color_widget.sizeHint().width() if hint > max_width: max_width = hint - color_widget.on_click.connect(lambda c=color: self.edit_color(c)) - color_widget.on_remove.connect(lambda c=color: self.delete_color(c)) + + color_widget.on_click.connect(lambda c=color: self._on_edit_color(c)) + color_widget.on_remove.connect(lambda c=color: self._on_delete_color(c)) color_widgets.append(color_widget) - self.base_layout.addWidget(color_widget) + self.__base_layout.addWidget(color_widget) for color_widget in color_widgets: color_widget.setFixedWidth(max_width) if is_mutable: + # Add button add_button = QPushButton() add_button.setText("+") add_button.setFlat(True) add_button.setFixedSize(22, 22) add_button.setStyleSheet(self.add_button_stylesheet) + add_button.clicked.connect( - lambda: self.edit_color( + lambda: self._on_edit_color( TagColorGroup( slug="slug", namespace=self.namespace, @@ -127,40 +131,11 @@ def set_colors(self, colors: Iterable[TagColorGroup]): ) ) ) - self.base_layout.addWidget(add_button) - def edit_color(self, color_group: TagColorGroup): - build_color_panel = BuildColorPanel(self.lib, color_group) - - self.edit_modal = PanelModal( - build_color_panel, - "Edit Color", - has_save=True, - ) + self.__base_layout.addWidget(add_button) - self.edit_modal.saved.connect( - lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit()) # type: ignore - ) - self.edit_modal.show() + def _on_edit_color(self, color_group: TagColorGroup) -> None: + raise NotImplementedError - def delete_color(self, color_group: TagColorGroup): - message_box = QMessageBox( - QMessageBox.Icon.Warning, - Translations["color.delete"], - Translations.format("color.confirm_delete", color_name=color_group.name), - ) - cancel_button = message_box.addButton( - Translations["generic.cancel_alt"], QMessageBox.ButtonRole.RejectRole - ) - message_box.addButton( - Translations["generic.delete_alt"], QMessageBox.ButtonRole.DestructiveRole - ) - message_box.setEscapeButton(cancel_button) - result = message_box.exec_() - logger.info(QMessageBox.ButtonRole.DestructiveRole.value) - if result != QMessageBox.ButtonRole.ActionRole.value: - return - - logger.info("[ColorBoxWidget] Removing color", color=color_group) - self.lib.delete_color(color_group) - self.updated.emit() + def _on_delete_color(self, color_group: TagColorGroup) -> None: + raise NotImplementedError From 5bc6858b9487498c8306f0b4054a8d5755ae022c Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 12:17:40 -0400 Subject: [PATCH 04/10] Add docstrings, return types, and other tweaks --- .../qt/controllers/fields/color_box_widget_controller.py | 9 +++++++-- .../qt/controllers/fields/tag_box_widget_controller.py | 4 +++- src/tagstudio/qt/mixed/tag_color_manager.py | 2 +- src/tagstudio/qt/views/fields/color_box_widget_view.py | 3 ++- src/tagstudio/qt/views/fields/tag_box_widget_view.py | 2 ++ src/tagstudio/qt/views/fields/text_field_widget.py | 5 +++-- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py b/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py index 978994a39..90b4d5b6e 100644 --- a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from PySide6.QtCore import Signal import structlog from PySide6.QtWidgets import QMessageBox @@ -16,6 +17,10 @@ class ColorBoxWidget(ColorBoxWidgetView): + """A widget holding a list of tag colors.""" + + on_update = Signal() + def __init__(self, group: str, colors: list[TagColorGroup], library: "Library") -> None: super().__init__(group, colors, library) self.__lib: Library = library @@ -30,7 +35,7 @@ def _on_edit_color(self, color_group: TagColorGroup) -> None: ) edit_color_modal.saved.connect( - lambda: (self.__lib.update_color(*build_color_panel.build_color()), self.updated.emit()) + lambda: (self.__lib.update_color(*build_color_panel.build_color()), self.on_update.emit()) ) edit_color_modal.show() @@ -61,4 +66,4 @@ def _on_delete_color(self, color_group: TagColorGroup) -> None: logger.info("[ColorBoxWidget] Removing color", color=color_group) self.__lib.delete_color(color_group) - self.updated.emit() + self.on_update.emit() diff --git a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py index 43ac78010..200da1671 100644 --- a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py @@ -22,11 +22,13 @@ class TagBoxWidget(TagBoxWidgetView): + """A widget that holds a list of tags.""" + on_update = Signal() __entries: list[int] = [] - def __init__(self, title: str, driver: "QtDriver"): + def __init__(self, title: str, driver: "QtDriver") -> None: super().__init__(title, driver) self.__driver = driver diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py index d739f84ac..a98966ea1 100644 --- a/src/tagstudio/qt/mixed/tag_color_manager.py +++ b/src/tagstudio/qt/mixed/tag_color_manager.py @@ -118,7 +118,7 @@ def setup_color_groups(self): if not group.startswith(RESERVED_NAMESPACE_PREFIX): all_default = False color_box = ColorBoxWidget(group, colors, self.driver.lib) - color_box.updated.connect( + color_box.on_update.connect( lambda: ( self.reset(), self.setup_color_groups(), diff --git a/src/tagstudio/qt/views/fields/color_box_widget_view.py b/src/tagstudio/qt/views/fields/color_box_widget_view.py index 563b3bfb1..33eaef7ea 100644 --- a/src/tagstudio/qt/views/fields/color_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/color_box_widget_view.py @@ -55,8 +55,8 @@ class ColorBoxWidgetView(FieldWidget): + """A widget holding a list of tag colors.""" __lib: Library - updated = Signal() def __init__(self, group: str, colors: list["TagColorGroup"], library: "Library") -> None: self.namespace: str = group @@ -80,6 +80,7 @@ def __init__(self, group: str, colors: list["TagColorGroup"], library: "Library" self.set_colors(self.colors) def set_colors(self, colors: Iterable[TagColorGroup]) -> None: + """Sets the colors the color box contains.""" colors_ = sorted( list(colors), key=lambda color: self.__lib.get_namespace_name(color.namespace) ) diff --git a/src/tagstudio/qt/views/fields/tag_box_widget_view.py b/src/tagstudio/qt/views/fields/tag_box_widget_view.py index 24fa1c645..77c2ea9fb 100644 --- a/src/tagstudio/qt/views/fields/tag_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/tag_box_widget_view.py @@ -20,6 +20,7 @@ class TagBoxWidgetView(FieldWidget): + """A widget that holds a list of tags.""" __lib: Library def __init__(self, title: str, driver: "QtDriver") -> None: @@ -32,6 +33,7 @@ def __init__(self, title: str, driver: "QtDriver") -> None: self.setLayout(self.__root_layout) def set_tags(self, tags: Iterable[Tag]) -> None: + """Sets the tags the tag box contains.""" tags_ = sorted(list(tags), key=lambda tag: self.__lib.tag_display_name(tag)) logger.info("[TagBoxWidget] Tags:", tags=tags) while self.__root_layout.itemAt(0): diff --git a/src/tagstudio/qt/views/fields/text_field_widget.py b/src/tagstudio/qt/views/fields/text_field_widget.py index ccc6c2f8c..51a99106a 100644 --- a/src/tagstudio/qt/views/fields/text_field_widget.py +++ b/src/tagstudio/qt/views/fields/text_field_widget.py @@ -37,14 +37,15 @@ def __init__(self, title, text: str) -> None: # Fill data self.set_text(text) - def set_text(self, text: str): + def set_text(self, text: str) -> None: """Sets the text of the field.""" text = linkify(text) self.text_label.setText(text) # Regex from https://stackoverflow.com/a/6041965 -def linkify(text: str): +def linkify(text: str) -> str: + """Replaces any found URLs in a string with an embedded link.""" url_pattern = ( r"(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-*]*[\w@?^=%&\/~+#-*])" ) From 3d39722a19fdc34c2e6494ebeb0713b0113d7db1 Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 12:37:39 -0400 Subject: [PATCH 05/10] Format --- .../qt/controllers/fields/color_box_widget_controller.py | 7 +++++-- src/tagstudio/qt/views/fields/color_box_widget_view.py | 2 +- src/tagstudio/qt/views/fields/tag_box_widget_view.py | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py b/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py index 90b4d5b6e..136e0b652 100644 --- a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING -from PySide6.QtCore import Signal import structlog +from PySide6.QtCore import Signal from PySide6.QtWidgets import QMessageBox from tagstudio.core.library.alchemy.models import TagColorGroup @@ -35,7 +35,10 @@ def _on_edit_color(self, color_group: TagColorGroup) -> None: ) edit_color_modal.saved.connect( - lambda: (self.__lib.update_color(*build_color_panel.build_color()), self.on_update.emit()) + lambda: ( + self.__lib.update_color(*build_color_panel.build_color()), + self.on_update.emit(), + ) ) edit_color_modal.show() diff --git a/src/tagstudio/qt/views/fields/color_box_widget_view.py b/src/tagstudio/qt/views/fields/color_box_widget_view.py index 33eaef7ea..4caef5b03 100644 --- a/src/tagstudio/qt/views/fields/color_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/color_box_widget_view.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING import structlog -from PySide6.QtCore import Signal from PySide6.QtWidgets import QPushButton from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX @@ -56,6 +55,7 @@ class ColorBoxWidgetView(FieldWidget): """A widget holding a list of tag colors.""" + __lib: Library def __init__(self, group: str, colors: list["TagColorGroup"], library: "Library") -> None: diff --git a/src/tagstudio/qt/views/fields/tag_box_widget_view.py b/src/tagstudio/qt/views/fields/tag_box_widget_view.py index 77c2ea9fb..d4d6f66ee 100644 --- a/src/tagstudio/qt/views/fields/tag_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/tag_box_widget_view.py @@ -21,6 +21,7 @@ class TagBoxWidgetView(FieldWidget): """A widget that holds a list of tags.""" + __lib: Library def __init__(self, title: str, driver: "QtDriver") -> None: From a5a195ad833ba209f0af5b50976d18c42f7b09d2 Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 13:07:13 -0400 Subject: [PATCH 06/10] So *apparently* those ignores weren't actually unused. Oops --- .../qt/controllers/fields/tag_box_widget_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py index 200da1671..bf2a4163d 100644 --- a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py @@ -36,7 +36,7 @@ def set_entries(self, entries: list[int]) -> None: self.__entries = entries @override - def _on_click(self, tag: Tag) -> None: + def _on_click(self, tag: Tag) -> None: # type: ignore[misc] match self.__driver.settings.tag_click_action: case TagClickActionOption.OPEN_EDIT: self._on_edit(tag) @@ -60,7 +60,7 @@ def _on_click(self, tag: Tag) -> None: ) @override - def _on_remove(self, tag: Tag) -> None: + def _on_remove(self, tag: Tag) -> None: # type: ignore[misc] logger.info( "[TagBoxWidget] remove_tag", selected=self.__entries, @@ -72,7 +72,7 @@ def _on_remove(self, tag: Tag) -> None: self.on_update.emit() @override - def _on_edit(self, tag: Tag) -> None: + def _on_edit(self, tag: Tag) -> None: # type: ignore[misc] build_tag_panel = BuildTagPanel(self.__driver.lib, tag=tag) edit_modal = PanelModal( @@ -94,7 +94,7 @@ def _on_edit(self, tag: Tag) -> None: edit_modal.show() @override - def _on_search(self, tag: Tag) -> None: + def _on_search(self, tag: Tag) -> None: # type: ignore[misc] 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) From 2c9915f4db58e5965b85db209e860f9227f13269 Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 13:08:30 -0400 Subject: [PATCH 07/10] Format --- .../qt/controllers/fields/tag_box_widget_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py index bf2a4163d..a8bece0bd 100644 --- a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py @@ -36,7 +36,7 @@ def set_entries(self, entries: list[int]) -> None: self.__entries = entries @override - def _on_click(self, tag: Tag) -> None: # type: ignore[misc] + def _on_click(self, tag: Tag) -> None: # type: ignore[misc] match self.__driver.settings.tag_click_action: case TagClickActionOption.OPEN_EDIT: self._on_edit(tag) @@ -60,7 +60,7 @@ def _on_click(self, tag: Tag) -> None: # type: ignore[misc] ) @override - def _on_remove(self, tag: Tag) -> None: # type: ignore[misc] + def _on_remove(self, tag: Tag) -> None: # type: ignore[misc] logger.info( "[TagBoxWidget] remove_tag", selected=self.__entries, @@ -72,7 +72,7 @@ def _on_remove(self, tag: Tag) -> None: # type: ignore[misc] self.on_update.emit() @override - def _on_edit(self, tag: Tag) -> None: # type: ignore[misc] + def _on_edit(self, tag: Tag) -> None: # type: ignore[misc] build_tag_panel = BuildTagPanel(self.__driver.lib, tag=tag) edit_modal = PanelModal( @@ -94,7 +94,7 @@ def _on_edit(self, tag: Tag) -> None: # type: ignore[misc] edit_modal.show() @override - def _on_search(self, tag: Tag) -> None: # type: ignore[misc] + def _on_search(self, tag: Tag) -> None: # type: ignore[misc] 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) From 02b76acca2a5aac52372860555c4298ec1c412f9 Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 15:10:44 -0400 Subject: [PATCH 08/10] Revert icon renames, that's unnecessary --- .../qt/views/fields/field_container.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tagstudio/qt/views/fields/field_container.py b/src/tagstudio/qt/views/fields/field_container.py index 4844ca151..36873b0e7 100644 --- a/src/tagstudio/qt/views/fields/field_container.py +++ b/src/tagstudio/qt/views/fields/field_container.py @@ -13,20 +13,20 @@ from tagstudio.qt.views.fields.field_widget import FieldWidget # TODO: reference a resources folder rather than path.parents[2]? -clipboard_icon: Image.Image = Image.open( +clipboard_icon_128: Image.Image = Image.open( str(Path(__file__).parents[3] / "resources/qt/images/clipboard_icon_128.png") ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) -clipboard_icon.load() +clipboard_icon_128.load() -edit_icon: Image.Image = Image.open( +edit_icon_128: Image.Image = Image.open( str(Path(__file__).parents[3] / "resources/qt/images/edit_icon_128.png") ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) -edit_icon.load() +edit_icon_128.load() -trash_icon: Image.Image = Image.open( +trash_icon_128: Image.Image = Image.open( str(Path(__file__).parents[3] / "resources/qt/images/trash_icon_128.png") ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) -trash_icon.load() +trash_icon_128.load() # TODO: There should be a global button theme somewhere. CONTAINER_STYLE = f""" @@ -103,7 +103,7 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.copy_button.setMinimumSize(BUTTON_SIZE, BUTTON_SIZE) self.copy_button.setMaximumSize(BUTTON_SIZE, BUTTON_SIZE) self.copy_button.setFlat(True) - self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(clipboard_icon))) + self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(clipboard_icon_128))) self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor) self.copy_button.setHidden(True) @@ -115,7 +115,7 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.edit_button.setMinimumSize(BUTTON_SIZE, BUTTON_SIZE) self.edit_button.setMaximumSize(BUTTON_SIZE, BUTTON_SIZE) self.edit_button.setFlat(True) - self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(edit_icon))) + self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(edit_icon_128))) self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor) self.edit_button.setHidden(True) @@ -127,7 +127,7 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.remove_button.setMinimumSize(BUTTON_SIZE, BUTTON_SIZE) self.remove_button.setMaximumSize(BUTTON_SIZE, BUTTON_SIZE) self.remove_button.setFlat(True) - self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(trash_icon))) + self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(trash_icon_128))) self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor) self.remove_button.setHidden(True) From 0d3b78497d346880ca7f6cfdbe81efe9167c0e7c Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Fri, 31 Oct 2025 15:15:38 -0400 Subject: [PATCH 09/10] Rename from `base_layout` to `__root_layout` --- .../qt/views/fields/color_box_widget_view.py | 16 ++++++++-------- src/tagstudio/qt/views/fields/field_container.py | 8 ++++---- .../qt/views/fields/text_field_widget.py | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/tagstudio/qt/views/fields/color_box_widget_view.py b/src/tagstudio/qt/views/fields/color_box_widget_view.py index 4caef5b03..3ae13bf34 100644 --- a/src/tagstudio/qt/views/fields/color_box_widget_view.py +++ b/src/tagstudio/qt/views/fields/color_box_widget_view.py @@ -68,10 +68,10 @@ def __init__(self, group: str, colors: list["TagColorGroup"], library: "Library" # Color box self.setObjectName("colorBox") - self.__base_layout = FlowLayout() - self.__base_layout.enable_grid_optimizations(value=True) - self.__base_layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(self.__base_layout) + self.__root_layout = FlowLayout() + self.__root_layout.enable_grid_optimizations(value=True) + self.__root_layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(self.__root_layout) # Add button self.add_button_stylesheet = BUTTON_STYLE @@ -87,8 +87,8 @@ def set_colors(self, colors: Iterable[TagColorGroup]) -> None: is_mutable = not self.namespace.startswith(RESERVED_NAMESPACE_PREFIX) max_width = 60 - while self.__base_layout.itemAt(0): - unwrap(self.__base_layout.takeAt(0)).widget().deleteLater() + while self.__root_layout.itemAt(0): + unwrap(self.__root_layout.takeAt(0)).widget().deleteLater() color_widgets: list[TagColorLabel] = [] @@ -108,7 +108,7 @@ def set_colors(self, colors: Iterable[TagColorGroup]) -> None: color_widget.on_remove.connect(lambda c=color: self._on_delete_color(c)) color_widgets.append(color_widget) - self.__base_layout.addWidget(color_widget) + self.__root_layout.addWidget(color_widget) for color_widget in color_widgets: color_widget.setFixedWidth(max_width) @@ -133,7 +133,7 @@ def set_colors(self, colors: Iterable[TagColorGroup]) -> None: ) ) - self.__base_layout.addWidget(add_button) + self.__root_layout.addWidget(add_button) def _on_edit_color(self, color_group: TagColorGroup) -> None: raise NotImplementedError diff --git a/src/tagstudio/qt/views/fields/field_container.py b/src/tagstudio/qt/views/fields/field_container.py index 36873b0e7..0db5b43a3 100644 --- a/src/tagstudio/qt/views/fields/field_container.py +++ b/src/tagstudio/qt/views/fields/field_container.py @@ -62,9 +62,9 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.inline: bool = inline self.setStyleSheet(CONTAINER_STYLE) - self.base_layout = QVBoxLayout(self) - self.base_layout.setObjectName("baseLayout") - self.base_layout.setContentsMargins(0, 0, 0, 0) + self.__root_layout = QVBoxLayout(self) + self.__root_layout.setObjectName("baseLayout") + self.__root_layout.setContentsMargins(0, 0, 0, 0) # Field container self.container_layout = QVBoxLayout() @@ -76,7 +76,7 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.field_container.setObjectName("fieldContainer") self.field_container.setLayout(self.container_layout) - self.base_layout.addWidget(self.field_container) + self.__root_layout.addWidget(self.field_container) # Title self.title_container = QWidget() diff --git a/src/tagstudio/qt/views/fields/text_field_widget.py b/src/tagstudio/qt/views/fields/text_field_widget.py index 51a99106a..933001800 100644 --- a/src/tagstudio/qt/views/fields/text_field_widget.py +++ b/src/tagstudio/qt/views/fields/text_field_widget.py @@ -20,9 +20,9 @@ def __init__(self, title, text: str) -> None: # Widget self.setObjectName("textBox") - self.base_layout = QHBoxLayout() - self.base_layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(self.base_layout) + self.__root_layout = QHBoxLayout() + self.__root_layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(self.__root_layout) # Label self.text_label = QLabel() @@ -32,7 +32,7 @@ def __init__(self, title, text: str) -> None: self.text_label.setOpenExternalLinks(True) self.text_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction) - self.base_layout.addWidget(self.text_label) + self.__root_layout.addWidget(self.text_label) # Fill data self.set_text(text) From 648adf5b54f1730cc89998be4e1b6dec7b8ad11a Mon Sep 17 00:00:00 2001 From: TrigamDev Date: Tue, 4 Nov 2025 11:30:40 -0500 Subject: [PATCH 10/10] Change fields/ to preview_panel/fields/ to better fit with the file attributes refactor --- .../fields/color_box_widget_controller.py | 2 +- .../fields/tag_box_widget_controller.py | 2 +- src/tagstudio/qt/mixed/field_containers.py | 6 +++--- src/tagstudio/qt/mixed/tag_color_manager.py | 4 ++-- .../{ => preview_panel}/fields/color_box_widget_view.py | 2 +- .../views/{ => preview_panel}/fields/field_container.py | 8 ++++---- .../qt/views/{ => preview_panel}/fields/field_widget.py | 0 .../{ => preview_panel}/fields/tag_box_widget_view.py | 2 +- .../views/{ => preview_panel}/fields/text_field_widget.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) rename src/tagstudio/qt/controllers/{ => preview_panel}/fields/color_box_widget_controller.py (96%) rename src/tagstudio/qt/controllers/{ => preview_panel}/fields/tag_box_widget_controller.py (97%) rename src/tagstudio/qt/views/{ => preview_panel}/fields/color_box_widget_view.py (98%) rename src/tagstudio/qt/views/{ => preview_panel}/fields/field_container.py (96%) rename src/tagstudio/qt/views/{ => preview_panel}/fields/field_widget.py (100%) rename src/tagstudio/qt/views/{ => preview_panel}/fields/tag_box_widget_view.py (96%) rename src/tagstudio/qt/views/{ => preview_panel}/fields/text_field_widget.py (95%) diff --git a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py b/src/tagstudio/qt/controllers/preview_panel/fields/color_box_widget_controller.py similarity index 96% rename from src/tagstudio/qt/controllers/fields/color_box_widget_controller.py rename to src/tagstudio/qt/controllers/preview_panel/fields/color_box_widget_controller.py index 136e0b652..57e4732de 100644 --- a/src/tagstudio/qt/controllers/fields/color_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/preview_panel/fields/color_box_widget_controller.py @@ -7,8 +7,8 @@ from tagstudio.core.library.alchemy.models import TagColorGroup from tagstudio.qt.mixed.build_color import BuildColorPanel from tagstudio.qt.translations import Translations -from tagstudio.qt.views.fields.color_box_widget_view import ColorBoxWidgetView from tagstudio.qt.views.panel_modal import PanelModal +from tagstudio.qt.views.preview_panel.fields.color_box_widget_view import ColorBoxWidgetView if TYPE_CHECKING: from tagstudio.core.library.alchemy.library import Library diff --git a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py b/src/tagstudio/qt/controllers/preview_panel/fields/tag_box_widget_controller.py similarity index 97% rename from src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py rename to src/tagstudio/qt/controllers/preview_panel/fields/tag_box_widget_controller.py index a8bece0bd..fce5c979a 100644 --- a/src/tagstudio/qt/controllers/fields/tag_box_widget_controller.py +++ b/src/tagstudio/qt/controllers/preview_panel/fields/tag_box_widget_controller.py @@ -12,8 +12,8 @@ from tagstudio.core.library.alchemy.models import Tag from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.build_tag import BuildTagPanel -from tagstudio.qt.views.fields.tag_box_widget_view import TagBoxWidgetView from tagstudio.qt.views.panel_modal import PanelModal +from tagstudio.qt.views.preview_panel.fields.tag_box_widget_view import TagBoxWidgetView if TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/mixed/field_containers.py index bb5462051..b262c4c52 100644 --- a/src/tagstudio/qt/mixed/field_containers.py +++ b/src/tagstudio/qt/mixed/field_containers.py @@ -33,14 +33,14 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag from tagstudio.core.utils.types import unwrap -from tagstudio.qt.controllers.fields.tag_box_widget_controller import TagBoxWidget +from tagstudio.qt.controllers.preview_panel.fields.tag_box_widget_controller import TagBoxWidget from tagstudio.qt.mixed.datetime_picker import DatetimePicker from tagstudio.qt.translations import Translations from tagstudio.qt.views.edit_text_box_modal import EditTextBox from tagstudio.qt.views.edit_text_line_modal import EditTextLine -from tagstudio.qt.views.fields.field_container import FieldContainer -from tagstudio.qt.views.fields.text_field_widget import TextFieldWidget from tagstudio.qt.views.panel_modal import PanelModal +from tagstudio.qt.views.preview_panel.fields.field_container import FieldContainer +from tagstudio.qt.views.preview_panel.fields.text_field_widget import TextFieldWidget if typing.TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py index a98966ea1..c23625cba 100644 --- a/src/tagstudio/qt/mixed/tag_color_manager.py +++ b/src/tagstudio/qt/mixed/tag_color_manager.py @@ -23,11 +23,11 @@ from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.enums import Theme -from tagstudio.qt.controllers.fields.color_box_widget_controller import ColorBoxWidget +from tagstudio.qt.controllers.preview_panel.fields.color_box_widget_controller import ColorBoxWidget from tagstudio.qt.mixed.build_namespace import BuildNamespacePanel from tagstudio.qt.translations import Translations -from tagstudio.qt.views.fields.field_container import FieldContainer from tagstudio.qt.views.panel_modal import PanelModal +from tagstudio.qt.views.preview_panel.fields.field_container import FieldContainer logger = structlog.get_logger(__name__) diff --git a/src/tagstudio/qt/views/fields/color_box_widget_view.py b/src/tagstudio/qt/views/preview_panel/fields/color_box_widget_view.py similarity index 98% rename from src/tagstudio/qt/views/fields/color_box_widget_view.py rename to src/tagstudio/qt/views/preview_panel/fields/color_box_widget_view.py index 3ae13bf34..abc078f12 100644 --- a/src/tagstudio/qt/views/fields/color_box_widget_view.py +++ b/src/tagstudio/qt/views/preview_panel/fields/color_box_widget_view.py @@ -16,8 +16,8 @@ from tagstudio.core.utils.types import unwrap from tagstudio.qt.mixed.tag_color_label import TagColorLabel from tagstudio.qt.models.palette import ColorType, get_tag_color -from tagstudio.qt.views.fields.field_widget import FieldWidget from tagstudio.qt.views.layouts.flow_layout import FlowLayout +from tagstudio.qt.views.preview_panel.fields.field_widget import FieldWidget if TYPE_CHECKING: from tagstudio.core.library.alchemy.library import Library diff --git a/src/tagstudio/qt/views/fields/field_container.py b/src/tagstudio/qt/views/preview_panel/fields/field_container.py similarity index 96% rename from src/tagstudio/qt/views/fields/field_container.py rename to src/tagstudio/qt/views/preview_panel/fields/field_container.py index 0db5b43a3..5a48b80c6 100644 --- a/src/tagstudio/qt/views/fields/field_container.py +++ b/src/tagstudio/qt/views/preview_panel/fields/field_container.py @@ -10,21 +10,21 @@ from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget from tagstudio.core.enums import Theme -from tagstudio.qt.views.fields.field_widget import FieldWidget +from tagstudio.qt.views.preview_panel.fields.field_widget import FieldWidget # TODO: reference a resources folder rather than path.parents[2]? clipboard_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[3] / "resources/qt/images/clipboard_icon_128.png") + str(Path(__file__).parents[4] / "resources/qt/images/clipboard_icon_128.png") ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) clipboard_icon_128.load() edit_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[3] / "resources/qt/images/edit_icon_128.png") + str(Path(__file__).parents[4] / "resources/qt/images/edit_icon_128.png") ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) edit_icon_128.load() trash_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[3] / "resources/qt/images/trash_icon_128.png") + str(Path(__file__).parents[4] / "resources/qt/images/trash_icon_128.png") ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) trash_icon_128.load() diff --git a/src/tagstudio/qt/views/fields/field_widget.py b/src/tagstudio/qt/views/preview_panel/fields/field_widget.py similarity index 100% rename from src/tagstudio/qt/views/fields/field_widget.py rename to src/tagstudio/qt/views/preview_panel/fields/field_widget.py diff --git a/src/tagstudio/qt/views/fields/tag_box_widget_view.py b/src/tagstudio/qt/views/preview_panel/fields/tag_box_widget_view.py similarity index 96% rename from src/tagstudio/qt/views/fields/tag_box_widget_view.py rename to src/tagstudio/qt/views/preview_panel/fields/tag_box_widget_view.py index d4d6f66ee..fd853cb84 100644 --- a/src/tagstudio/qt/views/fields/tag_box_widget_view.py +++ b/src/tagstudio/qt/views/preview_panel/fields/tag_box_widget_view.py @@ -10,8 +10,8 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Tag from tagstudio.qt.mixed.tag_widget import TagWidget -from tagstudio.qt.views.fields.field_widget import FieldWidget from tagstudio.qt.views.layouts.flow_layout import FlowLayout +from tagstudio.qt.views.preview_panel.fields.field_widget import FieldWidget if TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver diff --git a/src/tagstudio/qt/views/fields/text_field_widget.py b/src/tagstudio/qt/views/preview_panel/fields/text_field_widget.py similarity index 95% rename from src/tagstudio/qt/views/fields/text_field_widget.py rename to src/tagstudio/qt/views/preview_panel/fields/text_field_widget.py index 933001800..c6f4010d2 100644 --- a/src/tagstudio/qt/views/fields/text_field_widget.py +++ b/src/tagstudio/qt/views/preview_panel/fields/text_field_widget.py @@ -8,7 +8,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import QHBoxLayout, QLabel -from tagstudio.qt.views.fields.field_widget import FieldWidget +from tagstudio.qt.views.preview_panel.fields.field_widget import FieldWidget class TextFieldWidget(FieldWidget):