diff --git a/tagstudio/src/core/enums.py b/tagstudio/src/core/enums.py index 781bba0a..749428ca 100644 --- a/tagstudio/src/core/enums.py +++ b/tagstudio/src/core/enums.py @@ -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): diff --git a/tagstudio/src/qt/widgets/preview/field_containers.py b/tagstudio/src/qt/widgets/preview/field_containers.py index ec07281b..ae00a6c0 100644 --- a/tagstudio/src/qt/widgets/preview/field_containers.py +++ b/tagstudio/src/qt/widgets/preview/field_containers.py @@ -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( diff --git a/tagstudio/src/qt/widgets/preview/file_attributes.py b/tagstudio/src/qt/widgets/preview/file_attributes.py index 19820115..6a99b945 100644 --- a/tagstudio/src/qt/widgets/preview/file_attributes.py +++ b/tagstudio/src/qt/widgets/preview/file_attributes.py @@ -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("No Items Selected") 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"{os.path.sep}" # 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"{count} Items Selected") self.file_label.setCursor(Qt.CursorShape.ArrowCursor) self.file_label.set_file_path("") self.dimensions_label.setText("") + self.dimensions_label.setHidden(True) diff --git a/tagstudio/src/qt/widgets/preview/preview_thumb.py b/tagstudio/src/qt/widgets/preview/preview_thumb.py index 057551d9..7ac502df 100644 --- a/tagstudio/src/qt/widgets/preview/preview_thumb.py +++ b/tagstudio/src/qt/widgets/preview/preview_thumb.py @@ -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) diff --git a/tagstudio/src/qt/widgets/preview/recent_libraries.py b/tagstudio/src/qt/widgets/preview/recent_libraries.py index fd2fb2b2..e43d43fa 100644 --- a/tagstudio/src/qt/widgets/preview/recent_libraries.py +++ b/tagstudio/src/qt/widgets/preview/recent_libraries.py @@ -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) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 009ce524..89c73311 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -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)