ui: display empty selection; better multi-selection

This commit is contained in:
Travis Abendshien
2025-01-03 04:25:50 -08:00
parent 2c91cf62ec
commit ff04802f12
6 changed files with 123 additions and 65 deletions

View File

@@ -20,10 +20,11 @@ class Theme(str, enum.Enum):
COLOR_DARK_LABEL = "#DD000000"
COLOR_BG = "#65000000"
COLOR_HOVER = "#65AAAAAA"
COLOR_PRESSED = "#65EEEEEE"
COLOR_DISABLED = "#65F39CAA"
COLOR_DISABLED_BG = "#65440D12"
COLOR_HOVER = "#65444444"
COLOR_PRESSED = "#65777777"
COLOR_DISABLED_BG = "#30000000"
COLOR_FORBIDDEN = "#65F39CAA"
COLOR_FORBIDDEN_BG = "#65440D12"
class OpenStatus(enum.IntEnum):

View File

@@ -60,7 +60,7 @@ class FieldContainers(QWidget):
self.is_open: bool = False
self.common_fields: list = []
self.mixed_fields: list = []
self.selected: list[Entry] = []
self.cached_entries: list[Entry] = []
self.containers: list[FieldContainer] = []
self.panel_bg_color = (
@@ -108,7 +108,7 @@ class FieldContainers(QWidget):
def update_from_entry(self, entry: Entry):
"""Update tags and fields from a single Entry source."""
self.selected = [self.lib.get_entry_full(entry.id)]
self.cached_entries = [self.lib.get_entry_full(entry.id)]
logger.info(
"[FieldContainers] Updating Selection",
path=entry.path,
@@ -116,7 +116,7 @@ class FieldContainers(QWidget):
tags=entry.tags,
)
entry_ = self.selected[0]
entry_ = self.cached_entries[0]
container_len: int = len(entry_.fields)
container_index = 0
@@ -139,6 +139,11 @@ class FieldContainers(QWidget):
if i > (container_len - 1):
c.setHidden(True)
def hide_containers(self):
"""Hide all field and tag containers."""
for c in self.containers:
c.setHidden(True)
def get_tag_categories(self, tags: set[Tag]) -> dict[Tag, set[Tag | None]]:
"""Get a dictionary of category tags mapped to their respective tags."""
cats: dict[Tag, set[Tag | None]] = {}
@@ -184,29 +189,35 @@ class FieldContainers(QWidget):
return Translations.translate_formatted("library.field.confirm_remove", name=name)
def add_field_to_selected(self, field_list: list):
"""Add list of entry fields to one or more selected items."""
"""Add list of entry fields to one or more selected items.
Uses the current driver selection, NOT the field containers cache.
"""
logger.info(
"[FieldContainers][add_field_to_selected]",
selected=[x.path for x in self.selected],
selected=self.driver.selected,
fields=field_list,
)
for entry in self.selected:
for entry_id in self.driver.selected:
for field_item in field_list:
self.lib.add_entry_field_type(
entry.id,
entry_id,
field_id=field_item.data(Qt.ItemDataRole.UserRole),
)
def add_tags_to_selected(self, tags: list[int]):
"""Add list of tags to one or more selected items."""
"""Add list of tags to one or more selected items.
Uses the current driver selection, NOT the field containers cache.
"""
logger.info(
"[FieldContainers][add_tags_to_selected]",
selected=[x.path for x in self.selected],
selected=self.driver.selected,
tags=tags,
)
for entry in self.selected:
for entry_id in self.driver.selected:
self.lib.add_tags_to_entry(
entry.id,
entry_id,
tag_ids=tags,
)
self.tags_updated.emit()
@@ -251,7 +262,7 @@ class FieldContainers(QWidget):
save_callback=(
lambda content: (
self.update_field(field, content),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
)
),
)
@@ -265,7 +276,7 @@ class FieldContainers(QWidget):
prompt=self.remove_field_prompt(field.type.type.value),
callback=lambda: (
self.remove_field(field),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
),
)
)
@@ -291,7 +302,7 @@ class FieldContainers(QWidget):
save_callback=(
lambda content: (
self.update_field(field, content),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
)
),
)
@@ -301,7 +312,7 @@ class FieldContainers(QWidget):
prompt=self.remove_field_prompt(field.type.name),
callback=lambda: (
self.remove_field(field),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
),
)
)
@@ -330,7 +341,7 @@ class FieldContainers(QWidget):
prompt=self.remove_field_prompt(field.type.name),
callback=lambda: (
self.remove_field(field),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
),
)
)
@@ -351,7 +362,7 @@ class FieldContainers(QWidget):
prompt=self.remove_field_prompt(field.type.name),
callback=lambda: (
self.remove_field(field),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
),
)
)
@@ -404,7 +415,7 @@ class FieldContainers(QWidget):
inner_widget.updated.connect(
lambda: (
self.write_tag_container(index, tags, category_tag),
self.update_from_entry(self.selected[0]),
self.update_from_entry(self.cached_entries[0]),
)
)
else:
@@ -421,9 +432,9 @@ class FieldContainers(QWidget):
logger.info(
"[FieldContainers] Removing Field",
field=field,
selected=[x.path for x in self.selected],
selected=[x.path for x in self.cached_entries],
)
entry_ids = [e.id for e in self.selected]
entry_ids = [e.id for e in self.cached_entries]
self.lib.remove_entry_field(field, entry_ids)
# # if the field is meta tags, update the badges
@@ -437,7 +448,7 @@ class FieldContainers(QWidget):
(TextField, DatetimeField), # , TagBoxField)
), f"instance: {type(field)}"
entry_ids = [e.id for e in self.selected]
entry_ids = [e.id for e in self.cached_entries]
assert entry_ids, "No entries selected"
self.lib.update_entry_field(

View File

@@ -46,7 +46,7 @@ class FileAttributes(QWidget):
root_layout = QVBoxLayout(self)
root_layout.setContentsMargins(0, 0, 0, 0)
root_layout.setSpacing(6)
root_layout.setSpacing(0)
label_bg_color = (
Theme.COLOR_BG_DARK.value
@@ -87,17 +87,20 @@ class FileAttributes(QWidget):
self.date_created_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.date_created_label.setTextFormat(Qt.TextFormat.RichText)
self.date_created_label.setStyleSheet(self.date_style)
self.date_created_label.setHidden(True)
self.date_modified_label = QLabel()
self.date_modified_label.setObjectName("dateModifiedLabel")
self.date_modified_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.date_modified_label.setTextFormat(Qt.TextFormat.RichText)
self.date_modified_label.setStyleSheet(self.date_style)
self.date_modified_label.setHidden(True)
self.dimensions_label = QLabel()
self.dimensions_label.setObjectName("dimensionsLabel")
self.dimensions_label.setWordWrap(True)
self.dimensions_label.setStyleSheet(self.properties_style)
self.dimensions_label.setHidden(True)
self.date_container = QWidget()
date_layout = QVBoxLayout(self.date_container)
@@ -142,12 +145,18 @@ class FileAttributes(QWidget):
stats = {}
if not filepath:
self.layout().setSpacing(0)
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label.setText("<i>No Items Selected</i>")
self.file_label.set_file_path("")
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
self.dimensions_label.setText("")
self.dimensions_label.setHidden(True)
else:
self.layout().setSpacing(6)
self.file_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
self.file_label.set_file_path(filepath)
self.dimensions_label.setHidden(False)
file_str: str = ""
separator: str = f"<a style='color: #777777'><b>{os.path.sep}</a>" # Gray
@@ -229,7 +238,10 @@ class FileAttributes(QWidget):
def update_multi_selection(self, count: int):
# Multiple Selected Items
self.layout().setSpacing(0)
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label.setText(f"<b>{count}</b> Items Selected")
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
self.file_label.set_file_path("")
self.dimensions_label.setText("")
self.dimensions_label.setHidden(True)

View File

@@ -368,6 +368,10 @@ class PreviewThumb(QWidget):
return stats
def hide_preview(self):
"""Completely hide the file preview."""
self.switch_preview("")
def resizeEvent(self, event: QResizeEvent) -> None: # noqa: N802
self.update_image_size((self.size().width(), self.size().height()))
return super().resizeEvent(event)

View File

@@ -99,7 +99,7 @@ class RecentLibraries(QWidget):
# "}"
# f"QPushButton::hover{{background-color:{Theme.COLOR_HOVER.value};}}"
# f"QPushButton::pressed{{background-color:{Theme.COLOR_PRESSED.value};}}"
# f"QPushButton::disabled{{background-color:{Theme.COLOR_DISABLED_BG.value};}}"
# f"QPushButton::disabled{{background-color:{Theme.COLOR_FORBIDDEN_BG.value};}}"
# )
# btn.setCursor(Qt.CursorShape.PointingHandCursor)

View File

@@ -2,22 +2,26 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import traceback
import typing
from pathlib import Path
from warnings import catch_warnings
import structlog
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QHBoxLayout, QSplitter, QVBoxLayout, QWidget
from PySide6.QtWidgets import (
QHBoxLayout,
QPushButton,
QSplitter,
QVBoxLayout,
QWidget,
)
from src.core.enums import Theme
from src.core.library.alchemy.library import Library
from src.core.library.alchemy.models import Entry
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
from src.core.palette import ColorType, UiColor, get_ui_color
from src.qt.modals.add_field import AddFieldModal
from src.qt.modals.tag_search import TagSearchPanel
from src.qt.widgets.panel import PanelModal
# from src.qt.modals.add_tag import AddTagModal
from src.qt.widgets.preview.field_containers import FieldContainers
from src.qt.widgets.preview.file_attributes import FileAttributes
from src.qt.widgets.preview.preview_thumb import PreviewThumb
@@ -34,6 +38,30 @@ class PreviewPanel(QWidget):
tags_updated = Signal()
# TODO: There should be a global button theme somewhere.
button_style = (
f"QPushButton{{"
f"background-color:{Theme.COLOR_BG.value};"
"border-radius:6px;"
"text-align: center;"
f"}}"
f"QPushButton::hover{{"
f"background-color:{Theme.COLOR_HOVER.value};"
f"border-color:{get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)};"
f"border-style:solid;"
f"border-width: 2px;"
f"}}"
f"QPushButton::pressed{{"
f"background-color:{Theme.COLOR_PRESSED.value};"
f"border-color:{get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)};"
f"border-style:solid;"
f"border-width: 2px;"
f"}}"
f"QPushButton::disabled{{"
f"background-color:{Theme.COLOR_DISABLED_BG.value};"
f"}}"
)
def __init__(self, library: Library, driver: "QtDriver"):
super().__init__()
self.lib = library
@@ -46,10 +74,8 @@ class PreviewPanel(QWidget):
self.fields = FieldContainers(library, driver)
tsp = TagSearchPanel(self.driver.lib)
# tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
self.add_tag_modal = PanelModal(tsp, "Add Tags", "Add Tags")
# self.add_tag_modal = AddTagModal(self.lib)
self.add_field_modal = AddFieldModal(self.lib)
preview_section = QWidget()
@@ -65,25 +91,24 @@ class PreviewPanel(QWidget):
splitter = QSplitter()
splitter.setOrientation(Qt.Orientation.Vertical)
splitter.setHandleWidth(12)
# splitter.splitterMoved.connect(
# lambda: self.thumb.update_image_size(
# (
# self.thumb.image_container.size().width(),
# self.thumb.image_container.size().height(),
# )
# )
# )
add_buttons_container = QWidget()
add_buttons_layout = QHBoxLayout(add_buttons_container)
add_buttons_layout.setContentsMargins(0, 0, 0, 0)
add_buttons_layout.setSpacing(6)
self.add_tag_button = QPushButtonWrapper()
self.add_tag_button = QPushButton()
self.add_tag_button.setEnabled(False)
self.add_tag_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.add_tag_button.setMinimumHeight(28)
self.add_tag_button.setStyleSheet(PreviewPanel.button_style)
self.add_tag_button.setText("Add Tag")
self.add_field_button = QPushButtonWrapper()
self.add_field_button = QPushButton()
self.add_field_button.setEnabled(False)
self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.add_field_button.setMinimumHeight(28)
self.add_field_button.setStyleSheet(PreviewPanel.button_style)
self.add_field_button.setText("Add Field")
add_buttons_layout.addWidget(self.add_tag_button)
@@ -107,12 +132,14 @@ class PreviewPanel(QWidget):
def update_widgets(self) -> bool:
"""Render the panel widgets with the newest data from the Library."""
# No Items Selected
# items: list[Entry] = [self.driver.frame_content[x] for x in self.driver.selected]
if len(self.driver.selected) == 0:
# TODO: Clear everything to default
# self.file_attrs.update_blank()
self.thumb.hide_preview()
self.file_attrs.update_stats()
self.file_attrs.update_date_label()
self.fields.hide_containers()
self.add_tag_button.setEnabled(False)
self.add_field_button.setEnabled(False)
# One Item Selected
elif len(self.driver.selected) == 1:
@@ -121,48 +148,54 @@ class PreviewPanel(QWidget):
ext: str = filepath.suffix.lower()
stats: dict = self.thumb.update_preview(filepath, ext)
try:
self.file_attrs.update_stats(filepath, ext, stats)
self.file_attrs.update_date_label(filepath)
self.fields.update_from_entry(entry)
self.update_add_tag_button(entry)
self.update_add_field_button(entry)
except Exception as e:
logger.error("[Preview Panel] Error updating selection", error=e)
traceback.print_exc()
self.file_attrs.update_stats(filepath, ext, stats)
self.file_attrs.update_date_label(filepath)
self.fields.update_from_entry(entry)
self.update_add_tag_button(entry)
self.update_add_field_button(entry)
self.add_tag_button.setEnabled(True)
self.add_field_button.setEnabled(True)
# Multiple Selected Items
elif len(self.driver.selected) > 1:
# Render mixed selection
# items: list[Entry] = [self.lib.get_entry_full(x) for x in self.driver.selected]
# TODO: Render mixed selection
self.thumb.hide_preview() # TODO: Allow for mixed editing
self.file_attrs.update_multi_selection(len(self.driver.selected))
self.file_attrs.update_date_label()
self.fields.hide_containers() # TODO: Allow for mixed editing
self.update_add_tag_button()
self.update_add_field_button()
# self.fields.update_from_entries(items)
# self.file_attrs.update_selection_count()
self.add_tag_button.setEnabled(True)
self.add_field_button.setEnabled(True)
# self.thumb.update_widgets()
# # self.file_attrs.update_widgets()
# self.fields.update_widgets()
return True
def update_add_field_button(self, entry: Entry):
def update_add_field_button(self, entry: Entry | None = None):
with catch_warnings(record=True):
self.add_field_modal.done.disconnect()
self.add_field_button.clicked.disconnect()
# TODO: Remove all "is_connected" instances across the codebase
self.add_field_modal.is_connected = False
self.add_field_button.is_connected = False
self.add_field_modal.done.connect(
lambda f: (
self.fields.add_field_to_selected(f),
self.fields.update_from_entry(entry),
(self.fields.update_from_entry(entry) if entry else ()),
)
)
self.add_field_modal.is_connected = True
self.add_field_button.clicked.connect(self.add_field_modal.show)
def update_add_tag_button(self, entry: Entry):
def update_add_tag_button(self, entry: Entry = None):
with catch_warnings(record=True):
self.add_tag_modal.widget.tag_chosen.disconnect()
self.add_tag_button.clicked.disconnect()
@@ -170,7 +203,7 @@ class PreviewPanel(QWidget):
self.add_tag_modal.widget.tag_chosen.connect(
lambda t: (
self.fields.add_tags_to_selected(t),
self.fields.update_from_entry(entry),
(self.fields.update_from_entry(entry) if entry else ()),
)
)
@@ -180,6 +213,3 @@ class PreviewPanel(QWidget):
self.add_tag_modal.show(),
)
)
self.add_tag_button.is_connected = True
# self.add_field_button.clicked.connect(self.add_tag_modal.show)