mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 07:39:10 +00:00
ui: display empty selection; better multi-selection
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user