From 4eac495c46fac73cf30389ee1b220d0fdf2a4524 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Sat, 12 Sep 2026 08:11:12 -0700 Subject: [PATCH] refactor(ui): MVC split + rename FieldContainer(s) to EntryDataBox(List) --- docs/preview-support.md | 12 +- src/tagstudio/previews/file_renderer.py | 4 +- .../qt/controllers/entry_data_box.py | 98 +++++++++ .../entry_data_box_list.py} | 109 ++++------ src/tagstudio/qt/controllers/inspector.py | 10 +- src/tagstudio/qt/controllers/main_window.py | 12 +- src/tagstudio/qt/mixed/color_box.py | 9 +- src/tagstudio/qt/mixed/data_box.py | 14 ++ src/tagstudio/qt/mixed/field_widget.py | 189 ------------------ src/tagstudio/qt/mixed/folders_to_tags.py | 4 +- src/tagstudio/qt/mixed/item_thumb.py | 4 +- .../qt/mixed/mirror_entries_modal.py | 2 +- src/tagstudio/qt/mixed/settings_panel.py | 2 +- src/tagstudio/qt/mixed/tag_color_manager.py | 14 +- src/tagstudio/qt/mixed/text_field.py | 4 +- src/tagstudio/qt/qt_driver.py | 24 +-- .../qt/views/entry_data_box_list_view.py | 40 ++++ src/tagstudio/qt/views/entry_data_box_view.py | 83 ++++++++ src/tagstudio/qt/views/inspector_view.py | 4 +- src/tagstudio/qt/views/preview_thumb_view.py | 2 +- src/tagstudio/qt/views/styles/stylesheets.py | 6 +- src/tagstudio/qt/views/tag_box_view.py | 4 +- ...tainers.py => test_entry_data_box_list.py} | 18 +- 23 files changed, 335 insertions(+), 333 deletions(-) create mode 100644 src/tagstudio/qt/controllers/entry_data_box.py rename src/tagstudio/qt/{mixed/field_containers.py => controllers/entry_data_box_list.py} (81%) create mode 100644 src/tagstudio/qt/mixed/data_box.py delete mode 100644 src/tagstudio/qt/mixed/field_widget.py create mode 100644 src/tagstudio/qt/views/entry_data_box_list_view.py create mode 100644 src/tagstudio/qt/views/entry_data_box_view.py rename tests/qt/{test_field_containers.py => test_entry_data_box_list.py} (93%) diff --git a/docs/preview-support.md b/docs/preview-support.md index df64c56d..39b947ec 100644 --- a/docs/preview-support.md +++ b/docs/preview-support.md @@ -12,7 +12,7 @@ TagStudio offers built-in preview and thumbnail support for a wide variety of fi ### :material-image-outline: Images -Images will generate thumbnails the first time they are viewed or since the last time they were modified. Thumbnails are used in the grid view, but not in the Preview Panel. Animated images will play in the Preview Panel. +Images will generate thumbnails the first time they are viewed or since the last time they were modified. Thumbnails are used in the grid view, but not in the Inspector panel. Animated images will play in the Inspector panel. | Filetype | Extensions | Animation | | -------------------- | -------------------------------------------------- | :---------------------------------: | @@ -47,7 +47,7 @@ Images will generate thumbnails the first time they are viewed or since the last ### :material-movie-open: Videos -Video thumbnails will default to the closest viable frame from the middle of the video. Both thumbnail generation and video playback in the Preview Panel requires [FFmpeg](install.md#optional-dependencies) installed on your system. +Video thumbnails will default to the closest viable frame from the middle of the video. Both thumbnail generation and video playback in the Inspector panel requires [FFmpeg](install.md#optional-dependencies) installed on your system. | Filetype | Extensions | Dependencies | | --------------------- | ----------------------- | :----------: | @@ -65,7 +65,7 @@ Video thumbnails will default to the closest viable frame from the middle of the ### :material-sine-wave: Audio -Audio thumbnails will default to embedded cover art (if any) and fallback to generated waveform thumbnails. Audio file playback is supported in the Preview Panel if you have [FFmpeg](install.md#optional-dependencies) installed on your system. Audio waveforms are currently not cached. +Audio thumbnails will default to embedded cover art (if any) and fallback to generated waveform thumbnails. Audio file playback is supported in the Inspector panel if you have [FFmpeg](install.md#optional-dependencies) installed on your system. Audio waveforms are currently not cached. | Filetype | Extensions | Dependencies | | ------------------- | ------------------------ | :----------: | @@ -103,7 +103,7 @@ Preview support for office documents or well-known project file formats varies b ### :material-archive: Archives -Archive thumbnails will display the first image from the archive within the Preview Panel. +Archive thumbnails will display the first image from the archive within the Inspector panel. | Filetype | Extensions | | -------- | -------------- | @@ -129,7 +129,7 @@ Archive thumbnails will display the first image from the archive within the Prev ### :material-format-font: Fonts -Font thumbnails will use a "Aa" example preview of the font, with a full alphanumeric of the font available in the Preview Panel. +Font thumbnails will use a "Aa" example preview of the font, with a full alphanumeric of the font available in the Inspector panel. | Filetype | Extensions | | -------------------- | ----------------- | @@ -143,7 +143,7 @@ Font thumbnails will use a "Aa" example preview of the font, with a full alphanu !!! info "Plain Text Support" TagStudio supports the *vast* majority of files considered to be "[plain text](https://en.wikipedia.org/wiki/Plain_text)". If an extension or format is not listed here, odds are it's still supported anyway. -Text files render the first 256 bytes of text information to an image preview for thumbnails and the Preview Panel. Improved thumbnails, full scrollable text, and syntax highlighting are on our [roadmap](roadmap.md#uiux) for future features. +Text files render the first 256 bytes of text information to an image preview for thumbnails and the Inspector panel. Improved thumbnails, full scrollable text, and syntax highlighting are on our [roadmap](roadmap.md#uiux) for future features. | Filetype | Extensions | Syntax Highlighting | | ---------- | --------------------------------------------- | :--------------------------------: | diff --git a/src/tagstudio/previews/file_renderer.py b/src/tagstudio/previews/file_renderer.py index 73086bb9..d8a017be 100644 --- a/src/tagstudio/previews/file_renderer.py +++ b/src/tagstudio/previews/file_renderer.py @@ -139,7 +139,7 @@ class FileRenderer: Args: size (tuple[int, int]): The size of the graphic. pixel_ratio (float): The screen pixel ratio. - scale_radius (bool): Option to scale the radius up (Used for Preview Panel). + scale_radius (bool): Option to scale the radius up (Used by the Inspector). """ thumb_scale: int = 512 radius_scale: float = 1 @@ -223,7 +223,7 @@ class FileRenderer: Args: size (tuple[int,int]): The size of the graphic. pixel_ratio (float): The screen pixel ratio. - radius_scale (float): The scale factor of the border radius (Used by Preview Panel). + radius_scale (float): The scale factor of the border radius (Used by the Inspector). """ smooth_factor: int = 2 radius_factor: int = 8 diff --git a/src/tagstudio/qt/controllers/entry_data_box.py b/src/tagstudio/qt/controllers/entry_data_box.py new file mode 100644 index 00000000..9950bab0 --- /dev/null +++ b/src/tagstudio/qt/controllers/entry_data_box.py @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: GPL-3.0-only + + +from collections.abc import Callable +from typing import override +from warnings import catch_warnings + +from PySide6.QtCore import QEvent, Qt +from PySide6.QtGui import QEnterEvent, QResizeEvent +from PySide6.QtWidgets import QWidget + +from tagstudio.core.utils.types import unwrap +from tagstudio.qt.views.entry_data_box_view import EntryDataBoxView +from tagstudio.qt.views.styles.stylesheets import container_style, header + + +class EntryDataBox(QWidget): + def __init__(self, title: str = "DATA BOX") -> None: + super().__init__() + self.setObjectName("entry_data_box") + self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground) + self.title: str = title + self.copy_callback: Callable[[], None] | None = None + self.edit_callback: Callable[[], None] | None = None + self.remove_callback: Callable[[], None] | None = None + + self.view = EntryDataBoxView() + self.setLayout(self.view) + self.set_title(title) + self.setStyleSheet(container_style()) + + def set_copy_callback(self, callback: Callable[[], None] | None = None) -> None: + with catch_warnings(record=True): + self.view.copy_button.clicked.disconnect() + + self.copy_callback = callback + if callback: + self.view.copy_button.clicked.connect(callback) + + def set_edit_callback(self, callback: Callable[[], None] | None = None) -> None: + with catch_warnings(record=True): + self.view.edit_button.clicked.disconnect() + + self.edit_callback = callback + if callback: + self.view.edit_button.clicked.connect(callback) + + def set_remove_callback(self, callback: Callable[[], None] | None = None) -> None: + with catch_warnings(record=True): + self.view.remove_button.clicked.disconnect() + + self.remove_callback = callback + if callback: + self.view.remove_button.clicked.connect(callback) + + def set_inner_widget(self, widget: QWidget) -> None: + if item := self.view.data_layout.itemAt(0): + old: QWidget = unwrap(item.widget()) + self.view.data_layout.removeWidget(old) + old.deleteLater() + + self.view.data_layout.addWidget(widget) + + def get_inner_widget(self) -> QWidget | None: + if item := self.view.data_layout.itemAt(0): + return item.widget() + return None + + def set_title(self, title: str) -> None: + self.title = header(title, 4) + self.view.title_widget.setText(self.title) + + @override + def enterEvent(self, event: QEnterEvent) -> None: + # NOTE: You could pass the hover event to the EntryDataBox if needed. + if self.copy_callback: + self.view.copy_button.setHidden(False) + if self.edit_callback: + self.view.edit_button.setHidden(False) + if self.remove_callback: + self.view.remove_button.setHidden(False) + return super().enterEvent(event) + + @override + def leaveEvent(self, event: QEvent) -> None: + if self.copy_callback: + self.view.copy_button.setHidden(True) + if self.edit_callback: + self.view.edit_button.setHidden(True) + if self.remove_callback: + self.view.remove_button.setHidden(True) + return super().leaveEvent(event) + + @override + def resizeEvent(self, event: QResizeEvent) -> None: + self.view.title_widget.setFixedWidth(int(event.size().width() // 1.5)) + return super().resizeEvent(event) diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/controllers/entry_data_box_list.py similarity index 81% rename from src/tagstudio/qt/mixed/field_containers.py rename to src/tagstudio/qt/controllers/entry_data_box_list.py index daf57109..d521c896 100644 --- a/src/tagstudio/qt/mixed/field_containers.py +++ b/src/tagstudio/qt/controllers/entry_data_box_list.py @@ -9,16 +9,8 @@ from functools import partial from warnings import catch_warnings import structlog -from PySide6.QtCore import Qt, Signal -from PySide6.QtWidgets import ( - QFrame, - QHBoxLayout, - QMessageBox, - QScrollArea, - QSizePolicy, - QVBoxLayout, - QWidget, -) +from PySide6.QtCore import Signal +from PySide6.QtWidgets import QMessageBox, QWidget from tagstudio.core.library.alchemy.fields import ( BaseField, @@ -31,12 +23,12 @@ from tagstudio.core.library.alchemy.models import Entry, Tag from tagstudio.core.utils.types import unwrap from tagstudio.i18n.translations import FIELD_TYPE_KEYS, Translations from tagstudio.qt.controllers.edit_text import EditText +from tagstudio.qt.controllers.entry_data_box import EntryDataBox from tagstudio.qt.controllers.modal import Modal from tagstudio.qt.controllers.tag_box 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 TextContainerWidget -from tagstudio.qt.views.styles.stylesheets import inset_container_style +from tagstudio.qt.views.entry_data_box_list_view import EntryDataBoxListView if typing.TYPE_CHECKING: from tagstudio.qt.qt_driver import QtDriver @@ -44,9 +36,8 @@ if typing.TYPE_CHECKING: logger = structlog.get_logger(__name__) -# TODO: Split to use MVC guidelines. -class FieldContainers(QWidget): - """Widget for the tag and field containers displayed inside the Preview Panel.""" +class EntryDataBoxList(QWidget): + """Widget for the tag and field boxes displayed inside the Inspector.""" on_tags_update = Signal() @@ -57,43 +48,13 @@ class FieldContainers(QWidget): self.driver: QtDriver = driver self.initialized = False self.is_open: bool = False - self.common_fields: list = [] - self.mixed_fields: list = [] + self.common_fields: list = [] # TODO: Reimplement + self.mixed_fields: list = [] # TODO: Reimplement self.cached_entries: list[Entry] = [] - self._containers: list[FieldContainer] = [] + self._data_boxes: list[EntryDataBox] = [] - self.scroll_layout = QVBoxLayout() - self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop) - self.scroll_layout.setContentsMargins(3, 3, 3, 3) - self.scroll_layout.setSpacing(6) - - scroll_container: QWidget = QWidget() - scroll_container.setObjectName("entryScrollContainer") - scroll_container.setLayout(self.scroll_layout) - - info_section = QWidget() - info_layout = QVBoxLayout(info_section) - info_layout.setContentsMargins(0, 0, 0, 0) - info_layout.setSpacing(0) - - self.scroll_area = QScrollArea() - self.scroll_area.setObjectName("entryScrollArea") - self.scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) - self.scroll_area.setWidgetResizable(True) - self.scroll_area.setFrameShadow(QFrame.Shadow.Plain) - self.scroll_area.setFrameShape(QFrame.Shape.NoFrame) - - # NOTE: I would rather have this style applied to the scroll_area - # background and NOT the scroll container background, so that the - # rounded corners are maintained when scrolling. I was unable to - # find the right trick to only select that particular element. - self.scroll_area.setStyleSheet(inset_container_style("entryScrollContainer")) - self.scroll_area.setWidget(scroll_container) - - root_layout = QHBoxLayout(self) - root_layout.setContentsMargins(0, 0, 0, 0) - root_layout.addWidget(self.scroll_area) + self.view = EntryDataBoxListView() + self.setLayout(self.view) @property def top_entry_id(self) -> int: @@ -102,7 +63,7 @@ class FieldContainers(QWidget): def update_from_entry(self, entry_id: int, update_badges: bool = True) -> None: """Update tags and fields from a single Entry source.""" - logger.warning("[FieldContainers] Updating Selection", entry_id=entry_id) + logger.warning("[EntryDataBoxList] Updating Selection", entry_id=entry_id) entry = unwrap(self.lib.get_entry_full(entry_id)) self.cached_entries = [entry] @@ -131,8 +92,8 @@ class FieldContainers(QWidget): self.write_field_container(index, field, is_mixed=False) # Hide leftover container(s) - if len(self._containers) > container_len: - for i, c in enumerate(self._containers): + if len(self._data_boxes) > container_len: + for i, c in enumerate(self._data_boxes): if i > (container_len - 1): c.setHidden(True) @@ -151,7 +112,7 @@ class FieldContainers(QWidget): def hide_containers(self) -> None: """Hide all field and tag containers.""" - for c in self._containers: + for c in self._data_boxes: c.setHidden(True) def get_tag_categories(self, tags: set[Tag]) -> dict[Tag | None, set[Tag]]: @@ -215,7 +176,7 @@ class FieldContainers(QWidget): assert isinstance(field_templates, list) logger.info( - "[FieldContainers][add_field_to_selected]", + "[EntryDataBoxList][add_field_to_selected]", selected=self.driver.selected, fields=[ (field_template.class_name, field_template.id) for field_template in field_templates @@ -225,7 +186,7 @@ class FieldContainers(QWidget): for entry_id in self.driver.selected: for field_template in field_templates: logger.info( - "[FieldContainers][add_field_to_selected] Adding field", + "[EntryDataBoxList][add_field_to_selected] Adding field", name=field_template.name, type=field_template.class_name, ) @@ -239,7 +200,7 @@ class FieldContainers(QWidget): if isinstance(tag_ids, int): tag_ids = [tag_ids] logger.info( - "[FieldContainers][add_tags_to_selected]", + "[EntryDataBoxList][add_tags_to_selected]", selected=self.driver.selected, tag_ids=tag_ids, ) @@ -267,7 +228,7 @@ class FieldContainers(QWidget): self.update_from_entry(entry_id) def write_field_container(self, index: int, field: BaseField, is_mixed: bool = False) -> None: - """Update/Create data for a field FieldContainer. + """Update/Create data for a field EntryDataBox. Args: index(int): The container index. @@ -277,7 +238,7 @@ class FieldContainers(QWidget): """ def write_text_container( - container: FieldContainer, field: TextField, title: str, is_mixed: bool + container: EntryDataBox, field: TextField, title: str, is_mixed: bool ): container.set_title(field.name) @@ -311,7 +272,7 @@ class FieldContainers(QWidget): ) def write_datetime_container( - container: FieldContainer, field: DatetimeField, title: str, is_mixed: bool + container: EntryDataBox, field: DatetimeField, title: str, is_mixed: bool ): container.set_title(field.name) @@ -360,19 +321,19 @@ class FieldContainers(QWidget): ) logger.info( - "[FieldContainers][write_container]", + "[EntryDataBoxList][write_container]", index=index, name=field.name, type=field.class_name, ) # Create new containers if necessary - if len(self._containers) < (index + 1): - container = FieldContainer() - self._containers.append(container) - self.scroll_layout.addWidget(container) + if len(self._data_boxes) < (index + 1): + container = EntryDataBox() + self._data_boxes.append(container) + self.view.scroll_layout.addWidget(container) else: - container = self._containers[index] + container = self._data_boxes[index] # Set field title field_name_key: str = FIELD_TYPE_KEYS.get(field.class_name, "field_type.unknown") @@ -391,7 +352,7 @@ class FieldContainers(QWidget): def write_tag_container( self, index: int, tags: set[Tag], category_tag: Tag | None = None, is_mixed: bool = False ) -> None: - """Update/Create tag data for a tag FieldContainer. + """Update/Create tag data for a tag EntryDataBox. Args: index(int): The container index. @@ -400,13 +361,13 @@ class FieldContainers(QWidget): is_mixed(bool): Relevant when multiple items are selected. If True, field is not present in all selected items. """ - logger.info("[FieldContainers][write_tag_container]", index=index) - if len(self._containers) < (index + 1): - container = FieldContainer() - self._containers.append(container) - self.scroll_layout.addWidget(container) + logger.info("[EntryDataBoxList][write_tag_container]", index=index) + if len(self._data_boxes) < (index + 1): + container = EntryDataBox() + self._data_boxes.append(container) + self.view.scroll_layout.addWidget(container) else: - container = self._containers[index] + container = self._data_boxes[index] container.set_title(Translations["entries.tags"] if not category_tag else category_tag.name) @@ -441,7 +402,7 @@ class FieldContainers(QWidget): def _remove_field(self, field: BaseField) -> None: """Remove a field from all selected Entries.""" logger.info( - "[FieldContainers] Removing Field", + "[EntryDataBoxList] Removing Field", field=field, selected=[x.path for x in self.cached_entries], ) diff --git a/src/tagstudio/qt/controllers/inspector.py b/src/tagstudio/qt/controllers/inspector.py index ebeacc38..6f6ff029 100644 --- a/src/tagstudio/qt/controllers/inspector.py +++ b/src/tagstudio/qt/controllers/inspector.py @@ -27,9 +27,9 @@ from tagstudio.core.utils.ffmpeg_status import FfmpegStatus, FfprobeStatus from tagstudio.core.utils.types import unwrap from tagstudio.i18n.translations import FIELD_TYPE_KEYS, Translations from tagstudio.qt.controllers.edit_text import EditText +from tagstudio.qt.controllers.entry_data_box_list import EntryDataBoxList from tagstudio.qt.controllers.modal import Modal from tagstudio.qt.mixed.datetime_picker import DatetimePicker -from tagstudio.qt.mixed.field_containers import FieldContainers from tagstudio.qt.mixed.file_attributes import FileAttributeData from tagstudio.qt.views.inspector_view import InspectorView @@ -186,8 +186,8 @@ class Inspector(QWidget): self.layout().containers.update_from_entry(self._selected[0]) def _edit_field(self, entry_id: int, field: BaseField) -> None: - # TODO: A lot of this code is similar to or straight up shared with FieldContainers. - # It's possible to reuse it later, after a FieldContainers refactor. + # TODO: A lot of this code is similar to or straight up shared with EntryDataBoxList. + # It's possible to reuse it later, after an EntryDataBoxList refactor. field_name_key: str = FIELD_TYPE_KEYS.get(field.class_name, "field_type.unknown") if type(field) is TextField: @@ -274,14 +274,14 @@ class Inspector(QWidget): self._set_selection_callback() except Exception as e: - logger.error("[Preview Panel] Error updating selection", error=e) + logger.error("[Inspector] Error updating selection", error=e) def stop_media_playback(self) -> None: """Stop any media playback in the inspector.""" self.layout().preview_thumb.media_player.stop() @property - def containers(self) -> FieldContainers: + def containers(self) -> EntryDataBoxList: return self.layout().containers @override diff --git a/src/tagstudio/qt/controllers/main_window.py b/src/tagstudio/qt/controllers/main_window.py index ccd58b07..18eb52fe 100644 --- a/src/tagstudio/qt/controllers/main_window.py +++ b/src/tagstudio/qt/controllers/main_window.py @@ -495,8 +495,8 @@ class MainWindow(QMainWindow): self.landing_widget: LandingWidget self.pagination: Pagination - # initialized in setup_preview_panel - self.preview_panel: Inspector + # initialized in setup_inspector + self.inspector: Inspector # endregion if not self.objectName(): @@ -676,7 +676,7 @@ class MainWindow(QMainWindow): self.setup_entry_list(driver) self.content_splitter.addWidget(self.central_content) - self.setup_preview_panel(driver) + self.setup_inspector(driver) self.content_splitter.setStretchFactor(0, 1) self.content_layout.addWidget(self.content_splitter) @@ -719,9 +719,9 @@ class MainWindow(QMainWindow): self.pagination = Pagination(self.entry_list_container) self.central_content_layout.addWidget(self.entry_list_container) - def setup_preview_panel(self, driver: QtDriver): - self.preview_panel = Inspector(driver) - self.content_splitter.addWidget(self.preview_panel) + def setup_inspector(self, driver: QtDriver): + self.inspector = Inspector(driver) + self.content_splitter.addWidget(self.inspector) # endregion diff --git a/src/tagstudio/qt/mixed/color_box.py b/src/tagstudio/qt/mixed/color_box.py index 03e0e64e..bdefa82c 100644 --- a/src/tagstudio/qt/mixed/color_box.py +++ b/src/tagstudio/qt/mixed/color_box.py @@ -11,11 +11,10 @@ from PySide6.QtWidgets import QMessageBox, QPushButton from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.library.alchemy.models import TagColorGroup -from tagstudio.core.utils.types import unwrap from tagstudio.i18n.translations import Translations from tagstudio.qt.controllers.modal import Modal from tagstudio.qt.mixed.build_color import BuildColorPanel -from tagstudio.qt.mixed.field_widget import FieldWidget +from tagstudio.qt.mixed.data_box import DataBox from tagstudio.qt.mixed.tag_color_label import TagColorLabel from tagstudio.qt.views.layouts.flow_layout import FlowLayout from tagstudio.qt.views.styles.stylesheets import add_button_style @@ -27,7 +26,7 @@ logger = structlog.get_logger(__name__) # TODO: Split to use MVC guidelines. -class ColorBoxWidget(FieldWidget): +class ColorBoxWidget(DataBox): updated = Signal() def __init__( @@ -59,8 +58,8 @@ class ColorBoxWidget(FieldWidget): max_width = 60 color_widgets: list[TagColorLabel] = [] - while self.base_layout.itemAt(0): - unwrap(self.base_layout.takeAt(0)).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess] + while (item := self.base_layout.itemAt(0)) and (widget := item.widget()): + widget.deleteLater() for color in colors_: color_widget = TagColorLabel( diff --git a/src/tagstudio/qt/mixed/data_box.py b/src/tagstudio/qt/mixed/data_box.py new file mode 100644 index 00000000..78f01e2c --- /dev/null +++ b/src/tagstudio/qt/mixed/data_box.py @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: GPL-3.0-only + + +import structlog +from PySide6.QtWidgets import QWidget + +logger = structlog.get_logger(__name__) + + +class DataBox(QWidget): + def __init__(self, title: str) -> None: + super().__init__() + self.title: str = title diff --git a/src/tagstudio/qt/mixed/field_widget.py b/src/tagstudio/qt/mixed/field_widget.py deleted file mode 100644 index 4b699678..00000000 --- a/src/tagstudio/qt/mixed/field_widget.py +++ /dev/null @@ -1,189 +0,0 @@ -# SPDX-FileCopyrightText: (c) TagStudio Contributors -# SPDX-License-Identifier: GPL-3.0-only - - -from collections.abc import Callable -from typing import override -from warnings import catch_warnings - -import structlog -from PIL import ImageQt -from PySide6.QtCore import QEvent, QSize, Qt -from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent -from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget - -from tagstudio.core.utils.types import unwrap -from tagstudio.qt.resource_manager import ResourceManager -from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay -from tagstudio.qt.views.styles.stylesheets import container_style, header - -logger = structlog.get_logger(__name__) - - -# TODO: Split to use MVC guidelines. -class FieldContainer(QWidget): - rm: ResourceManager = ResourceManager() - copy_icon = auto_theme_overlay(rm.copy, inverse=True) - edit_icon = auto_theme_overlay(rm.edit, inverse=True) - trash_icon = auto_theme_overlay(rm.trash, inverse=True) - - # TODO: There should be a global button theme somewhere. - - def __init__(self, title: str = "Field", inline: bool = True) -> None: - super().__init__() - self.setObjectName("fieldContainer") - self.title: str = title - self.copy_callback: Callable[[], None] | None = None - self.edit_callback: Callable[[], None] | None = None - self.remove_callback: Callable[[], None] | None = None - button_size = 22 - icon_margins = 4 - icon_size = button_size - icon_margins - - 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(3, 0, 0, 3) - self.inner_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.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.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.title_layout.addStretch(2) - - 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.setFlat(True) - self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.copy_icon))) - self.copy_button.setIconSize(QSize(20, 20)) - self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.title_layout.addWidget(self.copy_button) - self.copy_button.setHidden(True) - - 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.setFlat(True) - self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.edit_icon))) - self.edit_button.setIconSize(QSize(icon_size, icon_size)) - self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.title_layout.addWidget(self.edit_button) - self.edit_button.setHidden(True) - - 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.setFlat(True) - self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.trash_icon))) - self.remove_button.setIconSize(QSize(icon_size, icon_size)) - self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.title_layout.addWidget(self.remove_button) - self.remove_button.setHidden(True) - - 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.set_title(title) - self.setStyleSheet(container_style()) - - def set_copy_callback(self, callback: Callable[[], None] | None = None) -> None: - with catch_warnings(record=True): - self.copy_button.clicked.disconnect() - - self.copy_callback = callback - if callback: - self.copy_button.clicked.connect(callback) - - def set_edit_callback(self, callback: Callable[[], None] | None = None) -> None: - with catch_warnings(record=True): - self.edit_button.clicked.disconnect() - - self.edit_callback = callback - if callback: - self.edit_button.clicked.connect(callback) - - def set_remove_callback(self, callback: Callable[[], None] | None = None) -> None: - with catch_warnings(record=True): - self.remove_button.clicked.disconnect() - - self.remove_callback = callback - if callback: - self.remove_button.clicked.connect(callback) - - def set_inner_widget(self, widget: FieldWidget) -> None: - item = self.field_layout.itemAt(0) - if item: - old: QWidget = unwrap(item.widget()) - self.field_layout.removeWidget(old) - old.deleteLater() - - self.field_layout.addWidget(widget) - - def get_inner_widget(self) -> QWidget | None: - item = self.field_layout.itemAt(0) - if item: - return item.widget() - return None - - def set_title(self, title: str) -> None: - self.title = header(title, 4) - self.title_widget.setText(self.title) - - @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) - 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) - - -class FieldWidget(QWidget): - def __init__(self, title: str) -> None: - super().__init__() - self.title: str = title diff --git a/src/tagstudio/qt/mixed/folders_to_tags.py b/src/tagstudio/qt/mixed/folders_to_tags.py index 0b6e6b7b..1a05e50b 100644 --- a/src/tagstudio/qt/mixed/folders_to_tags.py +++ b/src/tagstudio/qt/mixed/folders_to_tags.py @@ -229,9 +229,7 @@ class FoldersToTagsModal(QWidget): def on_apply(self): folders_to_tags(self.library) self.close() - self.driver.main_window.preview_panel.set_selection( - self.driver.selected, update_preview=False - ) + self.driver.main_window.inspector.set_selection(self.driver.selected, update_preview=False) @override def showEvent(self, event: QtGui.QShowEvent): diff --git a/src/tagstudio/qt/mixed/item_thumb.py b/src/tagstudio/qt/mixed/item_thumb.py index 8ed74010..c5bfb062 100644 --- a/src/tagstudio/qt/mixed/item_thumb.py +++ b/src/tagstudio/qt/mixed/item_thumb.py @@ -492,9 +492,7 @@ class ItemThumb(FlowWidget): ): selected = self.driver._selected if len(selected) == 1 and entry_id in selected: - self.driver.main_window.preview_panel.containers.update_toggled_tag( - tag_id, toggle_value - ) + self.driver.main_window.inspector.containers.update_toggled_tag(tag_id, toggle_value) @override def mouseMoveEvent(self, event: QMouseEvent) -> None: diff --git a/src/tagstudio/qt/mixed/mirror_entries_modal.py b/src/tagstudio/qt/mixed/mirror_entries_modal.py index 3ec9ea61..75980138 100644 --- a/src/tagstudio/qt/mixed/mirror_entries_modal.py +++ b/src/tagstudio/qt/mixed/mirror_entries_modal.py @@ -87,7 +87,7 @@ class MirrorEntriesModal(QWidget): pw.from_iterable_function( self.mirror_entries_runnable, displayed_text, - lambda s=self.driver.selected: self.driver.main_window.preview_panel.set_selection(s), + lambda s=self.driver.selected: self.driver.main_window.inspector.set_selection(s), self.done.emit, ) diff --git a/src/tagstudio/qt/mixed/settings_panel.py b/src/tagstudio/qt/mixed/settings_panel.py index 393616b0..5f935db6 100644 --- a/src/tagstudio/qt/mixed/settings_panel.py +++ b/src/tagstudio/qt/mixed/settings_panel.py @@ -429,7 +429,7 @@ class SettingsPanel(ModalContent): # Apply changes # Show File Path driver.update_recent_lib_menu() - driver.main_window.preview_panel.set_selection(self.driver.selected) + driver.main_window.inspector.set_selection(self.driver.selected) library_directory = driver.lib.library_dir if settings["show_filepath"] == ShowFilepathOption.SHOW_FULL_PATHS: display_path = library_directory or "" diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py index 639859ba..88b8471e 100644 --- a/src/tagstudio/qt/mixed/tag_color_manager.py +++ b/src/tagstudio/qt/mixed/tag_color_manager.py @@ -25,10 +25,10 @@ from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX from tagstudio.core.enums import ThemePalette from tagstudio.core.utils.types import unwrap from tagstudio.i18n.translations import Translations +from tagstudio.qt.controllers.entry_data_box import EntryDataBox from tagstudio.qt.controllers.modal import Modal 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.views.styles.stylesheets import header logger = structlog.get_logger(__name__) @@ -126,15 +126,15 @@ class TagColorManager(QWidget): self.setup_color_groups(), () if len(self.driver.selected) < 1 - else self.driver.main_window.preview_panel.containers.update_from_entry( # noqa: E501 + else self.driver.main_window.inspector.containers.update_from_entry( # noqa: E501 self.driver.selected[0], update_badges=False ), ) ) - field_container = FieldContainer(self.driver.lib.get_namespace_name(group)) - field_container.set_inner_widget(color_box) + entry_data_box = EntryDataBox(self.driver.lib.get_namespace_name(group)) + entry_data_box.set_inner_widget(color_box) if not group.startswith(RESERVED_NAMESPACE_PREFIX): - field_container.set_remove_callback( + entry_data_box.set_remove_callback( lambda checked=False, g=group: self.delete_namespace_dialog( prompt=Translations["color.namespace.delete.prompt"], callback=lambda namespace=g: ( @@ -143,14 +143,14 @@ class TagColorManager(QWidget): self.setup_color_groups(), () if len(self.driver.selected) < 1 - else self.driver.main_window.preview_panel.containers.update_from_entry( # noqa: E501 + else self.driver.main_window.inspector.containers.update_from_entry( # noqa: E501 self.driver.selected[0], update_badges=False ), ), ) ) - self.scroll_layout.addWidget(field_container) + self.scroll_layout.addWidget(entry_data_box) if all_default: ns_container = QWidget() diff --git a/src/tagstudio/qt/mixed/text_field.py b/src/tagstudio/qt/mixed/text_field.py index 683bfadb..4ba36a3b 100644 --- a/src/tagstudio/qt/mixed/text_field.py +++ b/src/tagstudio/qt/mixed/text_field.py @@ -7,11 +7,11 @@ import re from PySide6.QtCore import Qt from PySide6.QtWidgets import QHBoxLayout, QLabel -from tagstudio.qt.mixed.field_widget import FieldWidget +from tagstudio.qt.mixed.data_box import DataBox # TODO: Split to use MVC guidelines. -class TextContainerWidget(FieldWidget): +class TextContainerWidget(DataBox): def __init__(self, title: str, text: str) -> None: super().__init__(title) self.setObjectName("textBox") diff --git a/src/tagstudio/qt/qt_driver.py b/src/tagstudio/qt/qt_driver.py index 178dacf4..e6ac3b21 100644 --- a/src/tagstudio/qt/qt_driver.py +++ b/src/tagstudio/qt/qt_driver.py @@ -387,7 +387,7 @@ class QtDriver(DriverMixin, QObject): tsp.search_for_tag.connect(self.search_for_tag_callback) self.tag_manager = Modal(tsp, Translations["tag_manager.title"]) self.tag_manager.done.connect( - lambda checked=False: self.main_window.preview_panel.set_selection( + lambda checked=False: self.main_window.inspector.set_selection( self.selected, update_preview=False ) ) @@ -406,7 +406,7 @@ class QtDriver(DriverMixin, QObject): is_savable=False, ) self.field_template_manager.done.connect( - lambda checked=False: self.main_window.preview_panel.set_selection( + lambda checked=False: self.main_window.inspector.set_selection( self.selected, update_preview=False ) ) @@ -422,7 +422,7 @@ class QtDriver(DriverMixin, QObject): add_tag_tsp.item_chosen.connect( lambda chosen_tag: ( self.add_tags_to_selected_callback([chosen_tag]), - self.main_window.preview_panel.set_selection(self.selected, update_preview=False), + self.main_window.inspector.set_selection(self.selected, update_preview=False), ) ) @@ -800,7 +800,7 @@ class QtDriver(DriverMixin, QObject): self.cached_values.sync() # Reset library state - self.main_window.preview_panel.set_selection(self.selected) + self.main_window.inspector.set_selection(self.selected) self.main_window.search_field.setText("") self.main_window.results_label.setText("") scrollbar: QScrollArea = self.main_window.entry_scroll_area @@ -830,7 +830,7 @@ class QtDriver(DriverMixin, QObject): self.library_info_window.close() self.main_window.thumb_layout.set_entries([]) - self.main_window.preview_panel.set_selection(self.selected) + self.main_window.inspector.set_selection(self.selected) self.main_window.toggle_landing_page(enabled=True) self.main_window.pagination.setHidden(True) try: @@ -908,7 +908,7 @@ class QtDriver(DriverMixin, QObject): self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.main_window.preview_panel.set_selection(self.selected, update_preview=False) + self.main_window.inspector.set_selection(self.selected, update_preview=False) def select_inverse_action_callback(self): """Invert the selection of all visible items.""" @@ -917,14 +917,14 @@ class QtDriver(DriverMixin, QObject): self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.main_window.preview_panel.set_selection(self.selected, update_preview=False) + self.main_window.inspector.set_selection(self.selected, update_preview=False) def clear_select_action_callback(self): self.clear_selected() self.set_select_actions_visibility() self.set_clipboard_menu_viability() - self.main_window.preview_panel.set_selection(self.selected) + self.main_window.inspector.set_selection(self.selected) def add_tags_to_selected_callback(self, tag_ids: list[int]): selected: list[int] = self.selected @@ -972,7 +972,7 @@ class QtDriver(DriverMixin, QObject): ): for e_id, f in pending: if (origin_path == f) or (not origin_path): - self.main_window.preview_panel.stop_media_playback() + self.main_window.inspector.stop_media_playback() if e_id is not None: self.lib.remove_entries([e_id]) @@ -1436,7 +1436,7 @@ class QtDriver(DriverMixin, QObject): if TAG_FAVORITE in self.copy_buffer["tags"]: self.update_badges({BadgeType.FAVORITE: True}, origin_id=0, add_tags=False) else: - self.main_window.preview_panel.set_selection(self.selected) + self.main_window.inspector.set_selection(self.selected) def toggle_item_selection(self, item_id: int, append: bool, bridge: bool): """Toggle the selection of an item in the Thumbnail Grid. @@ -1465,7 +1465,7 @@ class QtDriver(DriverMixin, QObject): self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.main_window.preview_panel.set_selection(self.selected) + self.main_window.inspector.set_selection(self.selected) def set_clipboard_menu_viability(self): if len(self.selected) == 1: @@ -1872,7 +1872,7 @@ class QtDriver(DriverMixin, QObject): self.main_window.menu_bar.folders_to_tags_action.setEnabled(True) self.main_window.menu_bar.library_info_action.setEnabled(True) - self.main_window.preview_panel.set_selection(self.selected) + self.main_window.inspector.set_selection(self.selected) # page (re)rendering, extract eventually initial_state = BrowsingState( diff --git a/src/tagstudio/qt/views/entry_data_box_list_view.py b/src/tagstudio/qt/views/entry_data_box_list_view.py new file mode 100644 index 00000000..42ecbcc7 --- /dev/null +++ b/src/tagstudio/qt/views/entry_data_box_list_view.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: GPL-3.0-only + + +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QFrame, QHBoxLayout, QScrollArea, QSizePolicy, QVBoxLayout, QWidget + +from tagstudio.qt.views.styles.stylesheets import inset_container_style + + +class EntryDataBoxListView(QHBoxLayout): + def __init__(self) -> None: + super().__init__() + self.setContentsMargins(0, 0, 0, 0) + + self.scroll_layout = QVBoxLayout() + self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + self.scroll_layout.setContentsMargins(3, 3, 3, 3) + self.scroll_layout.setSpacing(6) + + scroll_container = QWidget() + scroll_container.setObjectName("entryScrollContainer") + scroll_container.setLayout(self.scroll_layout) + + self.scroll_area = QScrollArea() + self.scroll_area.setObjectName("entryScrollArea") + self.scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setFrameShadow(QFrame.Shadow.Plain) + self.scroll_area.setFrameShape(QFrame.Shape.NoFrame) + + # NOTE: I would rather have this style applied to the scroll_area + # background and NOT the scroll container background, so that the + # rounded corners are maintained when scrolling. I was unable to + # find the right trick to only select that particular element. + self.scroll_area.setStyleSheet(inset_container_style("entryScrollContainer")) + self.scroll_area.setWidget(scroll_container) + + self.addWidget(self.scroll_area) diff --git a/src/tagstudio/qt/views/entry_data_box_view.py b/src/tagstudio/qt/views/entry_data_box_view.py new file mode 100644 index 00000000..2a511be1 --- /dev/null +++ b/src/tagstudio/qt/views/entry_data_box_view.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: GPL-3.0-only + + +from PIL import ImageQt +from PySide6.QtCore import QSize, Qt +from PySide6.QtGui import QPixmap +from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget + +from tagstudio.qt.resource_manager import ResourceManager +from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay + +# TODO: There should be a global button theme somewhere. +_BUTTON_SIZE = 22 +_ICON_MARGIN = 4 +_ICON_SIZE = _BUTTON_SIZE - _ICON_MARGIN + + +class EntryDataBoxView(QVBoxLayout): + _rm = ResourceManager() + copy_icon = auto_theme_overlay(_rm.copy, inverse=True) + edit_icon = auto_theme_overlay(_rm.edit, inverse=True) + trash_icon = auto_theme_overlay(_rm.trash, inverse=True) + + def __init__(self) -> None: + super().__init__() + self.setContentsMargins(0, 0, 0, 0) + + self.inner_layout = QVBoxLayout() + self.inner_layout.setContentsMargins(3, 0, 0, 3) + self.inner_layout.setSpacing(0) + self.field_container = QWidget() + self.field_container.setLayout(self.inner_layout) + self.addWidget(self.field_container) + + self.title_container = QWidget() + self.title_layout = QHBoxLayout(self.title_container) + self.title_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) + 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.setWordWrap(True) + self.title_layout.addWidget(self.title_widget) + self.title_layout.addStretch(2) + + self.copy_button = QPushButton() + 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.copy_icon))) + self.copy_button.setIconSize(QSize(20, 20)) + self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor) + self.title_layout.addWidget(self.copy_button) + self.copy_button.setHidden(True) + + self.edit_button = QPushButton() + 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))) + self.edit_button.setIconSize(QSize(_ICON_SIZE, _ICON_SIZE)) + self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor) + self.title_layout.addWidget(self.edit_button) + self.edit_button.setHidden(True) + + self.remove_button = QPushButton() + 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))) + self.remove_button.setIconSize(QSize(_ICON_SIZE, _ICON_SIZE)) + self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor) + self.title_layout.addWidget(self.remove_button) + self.remove_button.setHidden(True) + + self.data = QWidget() + self.data_layout = QHBoxLayout() + self.data_layout.setContentsMargins(0, 0, 0, 0) + self.data.setLayout(self.data_layout) + self.inner_layout.addWidget(self.data) diff --git a/src/tagstudio/qt/views/inspector_view.py b/src/tagstudio/qt/views/inspector_view.py index de30b381..8e8c5a96 100644 --- a/src/tagstudio/qt/views/inspector_view.py +++ b/src/tagstudio/qt/views/inspector_view.py @@ -12,11 +12,11 @@ from PySide6.QtWidgets import QHBoxLayout, QLabel, QSplitter, QVBoxLayout, QWidg from tagstudio.core.constants import FFMPEG_HELP_URL from tagstudio.i18n.translations import Translations +from tagstudio.qt.controllers.entry_data_box_list import EntryDataBoxList from tagstudio.qt.controllers.field_suggest_box import FieldSuggestBox from tagstudio.qt.controllers.preview_thumb import PreviewThumb from tagstudio.qt.controllers.return_button import ReturnButton from tagstudio.qt.controllers.tag_suggest_box import TagSuggestBox -from tagstudio.qt.mixed.field_containers import FieldContainers from tagstudio.qt.mixed.file_attributes import FileAttributes from tagstudio.qt.resource_manager import ResourceManager from tagstudio.qt.views.styles.stylesheets import button_style, preview_warning_style @@ -47,7 +47,7 @@ class InspectorView(QVBoxLayout): self.preview_thumb = PreviewThumb(driver.lib, driver) self.file_attrs = FileAttributes(driver.lib, driver) - self.containers = FieldContainers(driver.lib, driver) + self.containers = EntryDataBoxList(driver.lib, driver) # Visual Preview preview_section = QWidget() diff --git a/src/tagstudio/qt/views/preview_thumb_view.py b/src/tagstudio/qt/views/preview_thumb_view.py index 5fdb5f3b..e250d59f 100644 --- a/src/tagstudio/qt/views/preview_thumb_view.py +++ b/src/tagstudio/qt/views/preview_thumb_view.py @@ -42,7 +42,7 @@ class PreviewType(Enum): # TODO: Use newer MVC style guidelines class PreviewThumbView(QWidget): - """The Preview Panel Widget.""" + """The file preview thumbnail widget.""" check_ffmpeg = Signal(bool) stats_updated = Signal(Path, FileAttributeData) diff --git a/src/tagstudio/qt/views/styles/stylesheets.py b/src/tagstudio/qt/views/styles/stylesheets.py index 4cc7d12b..678dd0a0 100644 --- a/src/tagstudio/qt/views/styles/stylesheets.py +++ b/src/tagstudio/qt/views/styles/stylesheets.py @@ -258,13 +258,13 @@ def color_swatch_style( def container_style() -> str: """Style used for field containers.""" return f""" - QWidget#fieldContainer{{ + QWidget#entry_data_box{{ border-radius: 4px; }} - QWidget#fieldContainer::hover{{ + QWidget#entry_data_box::hover{{ background-color: {ThemePalette.COLOR_HOVER.value}; }} - QWidget#fieldContainer::pressed{{ + QWidget#entry_data_box::pressed{{ background-color: {ThemePalette.COLOR_PRESSED.value}; }} """ diff --git a/src/tagstudio/qt/views/tag_box_view.py b/src/tagstudio/qt/views/tag_box_view.py index f66829a2..78a4b03e 100644 --- a/src/tagstudio/qt/views/tag_box_view.py +++ b/src/tagstudio/qt/views/tag_box_view.py @@ -9,7 +9,7 @@ import structlog 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.data_box import DataBox from tagstudio.qt.mixed.tag_widget import TagWidget from tagstudio.qt.views.layouts.flow_layout import FlowLayout @@ -20,7 +20,7 @@ logger = structlog.get_logger(__name__) # TODO: Use newer MVC style guidelines -class TagBoxWidgetView(FieldWidget): +class TagBoxWidgetView(DataBox): __lib: Library def __init__(self, title: str, driver: QtDriver) -> None: diff --git a/tests/qt/test_field_containers.py b/tests/qt/test_entry_data_box_list.py similarity index 93% rename from tests/qt/test_field_containers.py rename to tests/qt/test_entry_data_box_list.py index d2518360..5eaef11b 100644 --- a/tests/qt/test_field_containers.py +++ b/tests/qt/test_entry_data_box_list.py @@ -21,7 +21,7 @@ def test_update_selection_empty(qt_driver: QtDriver): panel.set_selection(qt_driver.selected) # FieldContainer should hide all containers - for container in panel.containers._containers: + for container in panel.containers._data_boxes: assert container.isHidden() @@ -33,7 +33,7 @@ def test_update_selection_single(qt_driver: QtDriver, entry_full: Entry): panel.set_selection(qt_driver.selected) # FieldContainer should show all applicable tags and field containers - for container in panel.containers._containers: + for container in panel.containers._data_boxes: assert not container.isHidden() @@ -48,7 +48,7 @@ def test_update_selection_multiple(qt_driver: QtDriver): panel.set_selection(qt_driver.selected) # FieldContainer should show mixed field editing - for container in panel.containers._containers: + for container in panel.containers._data_boxes: assert container.isHidden() @@ -137,8 +137,8 @@ def test_meta_tag_category(qt_driver: QtDriver, entry_full: Entry): panel.set_selection(qt_driver.selected) # FieldContainer should hide all containers - assert len(panel.containers._containers) == 3 - for i, container in enumerate(panel.containers._containers): + assert len(panel.containers._data_boxes) == 3 + for i, container in enumerate(panel.containers._data_boxes): match i: case 0: # Check if the container is the Meta Tags category @@ -170,8 +170,8 @@ def test_custom_tag_category(qt_driver: QtDriver, entry_full: Entry): panel.set_selection(qt_driver.selected) # FieldContainer should hide all containers - assert len(panel.containers._containers) == 3 - for i, container in enumerate(panel.containers._containers): + assert len(panel.containers._data_boxes) == 3 + for i, container in enumerate(panel.containers._data_boxes): match i: case 0: # Check if the container is the Meta Tags category @@ -206,5 +206,5 @@ def test_exclude_tag_category( qt_driver.toggle_item_selection(entry.id, append=False, bridge=False) panel.set_selection(qt_driver.selected) - assert len(panel.containers._containers) == 1 - assert panel.containers._containers[0].title == "

Tags

" + assert len(panel.containers._data_boxes) == 1 + assert panel.containers._data_boxes[0].title == "

Tags

"