feat(ui): show filenames in thumbnail grid (Closes #85) (#633)

* feat(ui): add filename label under thumbnails

* refactor(ui): organize menu items

* feat: make thumbnail filenames toggleable

* refactor variables, tweak spacing

* fix(ui): only change cursor on thumb_button

* revert changes to ../search_library/../ts_library.sqlite

* fix: don't ever call .setHidden(False) on visible widgets

* refactor: rename filename toggles to setters

* fix: remove duplicate open_on_start_action
This commit is contained in:
Travis Abendshien
2024-12-20 14:34:05 -08:00
committed by GitHub
parent 24fa76ee30
commit 8387676d79
4 changed files with 134 additions and 58 deletions

View File

@@ -10,6 +10,7 @@ class SettingItems(str, enum.Enum):
LAST_LIBRARY = "last_library"
LIBS_LIST = "libs_list"
WINDOW_SHOW_LIBS = "window_show_libs"
SHOW_FILENAMES = "show_filenames"
AUTOPLAY = "autoplay_videos"

View File

@@ -25,15 +25,7 @@ import src.qt.resources_rc # noqa: F401
import structlog
from humanfriendly import format_timespan
from PySide6 import QtCore
from PySide6.QtCore import (
QObject,
QSettings,
Qt,
QThread,
QThreadPool,
QTimer,
Signal,
)
from PySide6.QtCore import QObject, QSettings, Qt, QThread, QThreadPool, QTimer, Signal
from PySide6.QtGui import (
QAction,
QColor,
@@ -264,15 +256,12 @@ class QtDriver(DriverMixin, QObject):
file_menu = QMenu("&File", menu_bar)
edit_menu = QMenu("&Edit", menu_bar)
view_menu = QMenu("&View", menu_bar)
tools_menu = QMenu("&Tools", menu_bar)
macros_menu = QMenu("&Macros", menu_bar)
window_menu = QMenu("&Window", menu_bar)
help_menu = QMenu("&Help", menu_bar)
# File Menu ============================================================
# file_menu.addAction(QAction('&New Library', menu_bar))
# file_menu.addAction(QAction('&Open Library', menu_bar))
open_library_action = QAction("&Open/Create Library", menu_bar)
open_library_action.triggered.connect(lambda: self.open_library_from_dialog())
open_library_action.setShortcut(
@@ -302,8 +291,6 @@ class QtDriver(DriverMixin, QObject):
file_menu.addSeparator()
# refresh_lib_action = QAction('&Refresh Directories', self.main_window)
# refresh_lib_action.triggered.connect(lambda: self.lib.refresh_dir())
add_new_files_action = QAction("&Refresh Directories", menu_bar)
add_new_files_action.triggered.connect(
lambda: self.callback_library_needed_check(self.add_new_files_callback)
@@ -315,13 +302,23 @@ class QtDriver(DriverMixin, QObject):
)
)
add_new_files_action.setStatusTip("Ctrl+R")
# file_menu.addAction(refresh_lib_action)
file_menu.addAction(add_new_files_action)
file_menu.addSeparator()
close_library_action = QAction("&Close Library", menu_bar)
close_library_action.triggered.connect(self.close_library)
file_menu.addAction(close_library_action)
file_menu.addSeparator()
open_on_start_action = QAction("Open Library on Start", self)
open_on_start_action.setCheckable(True)
open_on_start_action.setChecked(
bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool))
)
open_on_start_action.triggered.connect(
lambda checked: self.settings.setValue(SettingItems.START_LOAD_LAST, checked)
)
file_menu.addAction(open_on_start_action)
# Edit Menu ============================================================
new_tag_action = QAction("New &Tag", menu_bar)
@@ -364,15 +361,32 @@ class QtDriver(DriverMixin, QObject):
tag_database_action.triggered.connect(lambda: self.show_tag_database())
edit_menu.addAction(tag_database_action)
check_action = QAction("Open library on start", self)
check_action.setCheckable(True)
check_action.setChecked(
bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool))
# View Menu ============================================================
show_libs_list_action = QAction("Show Recent Libraries", menu_bar)
show_libs_list_action.setCheckable(True)
show_libs_list_action.setChecked(
bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, defaultValue=True, type=bool))
)
check_action.triggered.connect(
lambda checked: self.settings.setValue(SettingItems.START_LOAD_LAST, checked)
show_libs_list_action.triggered.connect(
lambda checked: (
self.settings.setValue(SettingItems.WINDOW_SHOW_LIBS, checked),
self.toggle_libs_list(checked),
)
)
window_menu.addAction(check_action)
view_menu.addAction(show_libs_list_action)
show_filenames_action = QAction("Show Filenames in Grid", menu_bar)
show_filenames_action.setCheckable(True)
show_filenames_action.setChecked(
bool(self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool))
)
show_filenames_action.triggered.connect(
lambda checked: (
self.settings.setValue(SettingItems.SHOW_FILENAMES, checked),
self.show_grid_filenames(checked),
)
)
view_menu.addAction(show_filenames_action)
# Tools Menu ===========================================================
def create_fix_unlinked_entries_modal():
@@ -407,19 +421,6 @@ class QtDriver(DriverMixin, QObject):
)
macros_menu.addAction(self.autofill_action)
show_libs_list_action = QAction("Show Recent Libraries", menu_bar)
show_libs_list_action.setCheckable(True)
show_libs_list_action.setChecked(
bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, defaultValue=True, type=bool))
)
show_libs_list_action.triggered.connect(
lambda checked: (
self.settings.setValue(SettingItems.WINDOW_SHOW_LIBS, checked),
self.toggle_libs_list(checked),
)
)
window_menu.addAction(show_libs_list_action)
def create_folders_tags_modal():
if not hasattr(self, "folders_modal"):
self.folders_modal = FoldersToTagsModal(self.lib, self)
@@ -429,7 +430,7 @@ class QtDriver(DriverMixin, QObject):
folders_to_tags_action.triggered.connect(create_folders_tags_modal)
macros_menu.addAction(folders_to_tags_action)
# Help Menu ==========================================================
# Help Menu ============================================================
self.repo_action = QAction("Visit GitHub Repository", menu_bar)
self.repo_action.triggered.connect(
lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio")
@@ -439,9 +440,9 @@ class QtDriver(DriverMixin, QObject):
menu_bar.addMenu(file_menu)
menu_bar.addMenu(edit_menu)
menu_bar.addMenu(view_menu)
menu_bar.addMenu(tools_menu)
menu_bar.addMenu(macros_menu)
menu_bar.addMenu(window_menu)
menu_bar.addMenu(help_menu)
self.main_window.searchField.textChanged.connect(self.update_completions_list)
@@ -551,6 +552,10 @@ class QtDriver(DriverMixin, QObject):
self.preview_panel.libs_flow_container.hide()
self.preview_panel.update()
def show_grid_filenames(self, value: bool):
for thumb in self.item_thumbs:
thumb.set_filename_visibility(value)
def callback_library_needed_check(self, func):
"""Check if loaded library has valid path before executing the button function."""
if self.lib.library_dir:
@@ -833,9 +838,9 @@ class QtDriver(DriverMixin, QObject):
it.thumb_button.setIcon(blank_icon)
it.resize(self.thumb_size, self.thumb_size)
it.thumb_size = (self.thumb_size, self.thumb_size)
it.setMinimumSize(self.thumb_size, self.thumb_size)
it.setMaximumSize(self.thumb_size, self.thumb_size)
it.setFixedSize(self.thumb_size, self.thumb_size)
it.thumb_button.thumb_size = (self.thumb_size, self.thumb_size)
it.set_filename_visibility(it.show_filename_label)
self.flow_container.layout().setSpacing(
min(self.thumb_size // spacing_divisor, min_spacing)
)
@@ -883,7 +888,14 @@ class QtDriver(DriverMixin, QObject):
# TODO - init after library is loaded, it can have different page_size
for grid_idx in range(self.filter.page_size):
item_thumb = ItemThumb(
None, self.lib, self, (self.thumb_size, self.thumb_size), grid_idx
None,
self.lib,
self,
(self.thumb_size, self.thumb_size),
grid_idx,
bool(
self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool)
),
)
layout.addWidget(item_thumb)

View File

@@ -110,6 +110,8 @@ class ItemThumb(FlowWidget):
"padding-left: 1px;"
)
filename_style = "font-size:10px;"
def __init__(
self,
mode: ItemType,
@@ -117,6 +119,7 @@ class ItemThumb(FlowWidget):
driver: "QtDriver",
thumb_size: tuple[int, int],
grid_idx: int,
show_filename_label: bool = False,
):
super().__init__()
self.grid_idx = grid_idx
@@ -125,10 +128,24 @@ class ItemThumb(FlowWidget):
self.driver = driver
self.item_id: int | None = None
self.thumb_size: tuple[int, int] = thumb_size
self.show_filename_label: bool = show_filename_label
self.label_height = 12
self.label_spacing = 4
self.setMinimumSize(*thumb_size)
self.setMaximumSize(*thumb_size)
self.setMouseTracking(True)
check_size = 24
self.setFixedSize(
thumb_size[0],
thumb_size[1]
+ ((self.label_height + self.label_spacing) if show_filename_label else 0),
)
self.thumb_container = QWidget()
self.base_layout = QVBoxLayout(self)
self.base_layout.setContentsMargins(0, 0, 0, 0)
self.base_layout.setSpacing(0)
self.setLayout(self.base_layout)
# +----------+
# | ARC FAV| Top Right: Favorite & Archived Badges
@@ -136,6 +153,8 @@ class ItemThumb(FlowWidget):
# | |
# |EXT #| Lower Left: File Type, Tag Group Icon, or Collation Icon
# +----------+ Lower Right: Collation Count, Video Length, or Word Count
#
# Filename Underneath: (Optional) Filename
# Thumbnail ============================================================
@@ -145,9 +164,9 @@ class ItemThumb(FlowWidget):
# || ||
# |*--------*|
# +----------+
self.base_layout = QVBoxLayout(self)
self.base_layout.setObjectName("baseLayout")
self.base_layout.setContentsMargins(0, 0, 0, 0)
self.thumb_layout = QVBoxLayout(self.thumb_container)
self.thumb_layout.setObjectName("baseLayout")
self.thumb_layout.setContentsMargins(0, 0, 0, 0)
# +----------+
# |[~~~~~~~~]|
@@ -160,7 +179,7 @@ class ItemThumb(FlowWidget):
self.top_layout.setContentsMargins(6, 6, 6, 6)
self.top_container = QWidget()
self.top_container.setLayout(self.top_layout)
self.base_layout.addWidget(self.top_container)
self.thumb_layout.addWidget(self.top_container)
# +----------+
# |[~~~~~~~~]|
@@ -168,7 +187,7 @@ class ItemThumb(FlowWidget):
# | | |
# | v |
# +----------+
self.base_layout.addStretch(2)
self.thumb_layout.addStretch(2)
# +----------+
# |[~~~~~~~~]|
@@ -181,19 +200,20 @@ class ItemThumb(FlowWidget):
self.bottom_layout.setContentsMargins(6, 6, 6, 6)
self.bottom_container = QWidget()
self.bottom_container.setLayout(self.bottom_layout)
self.base_layout.addWidget(self.bottom_container)
self.thumb_layout.addWidget(self.bottom_container)
self.thumb_button = ThumbButton(self, thumb_size)
self.thumb_button = ThumbButton(self.thumb_container, thumb_size)
self.renderer = ThumbRenderer()
self.renderer.updated.connect(
lambda ts, i, s, ext: (
lambda ts, i, s, fn, ext: (
self.update_thumb(ts, image=i),
self.update_size(ts, size=s),
self.set_filename_text(fn),
self.set_extension(ext),
)
)
self.thumb_button.setFlat(True)
self.thumb_button.setLayout(self.base_layout)
self.thumb_button.setLayout(self.thumb_layout)
self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
self.opener = FileOpenerHelper("")
@@ -285,6 +305,16 @@ class ItemThumb(FlowWidget):
self.badges[badge_type] = badge
self.cb_layout.addWidget(badge)
# Filename Label =======================================================
self.file_label = QLabel(text="Filename")
self.file_label.setStyleSheet(ItemThumb.filename_style)
self.file_label.setMaximumHeight(self.label_height)
if not show_filename_label:
self.file_label.setHidden(True)
self.base_layout.addWidget(self.thumb_container)
self.base_layout.addWidget(self.file_label)
self.set_mode(mode)
@property
@@ -298,11 +328,11 @@ class ItemThumb(FlowWidget):
def set_mode(self, mode: ItemType | None) -> None:
if mode is None:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=True)
self.unsetCursor()
self.thumb_button.unsetCursor()
self.thumb_button.setHidden(True)
elif mode == ItemType.ENTRY and self.mode != ItemType.ENTRY:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setHidden(False)
self.cb_container.setHidden(False)
# Count Badge depends on file extension (video length, word count)
@@ -312,7 +342,7 @@ class ItemThumb(FlowWidget):
self.ext_badge.setHidden(True)
elif mode == ItemType.COLLATION and self.mode != ItemType.COLLATION:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setHidden(False)
self.cb_container.setHidden(True)
self.ext_badge.setHidden(True)
@@ -321,7 +351,7 @@ class ItemThumb(FlowWidget):
self.item_type_badge.setHidden(False)
elif mode == ItemType.TAG_GROUP and self.mode != ItemType.TAG_GROUP:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setHidden(False)
self.ext_badge.setHidden(True)
self.count_badge.setHidden(False)
@@ -366,14 +396,40 @@ class ItemThumb(FlowWidget):
self.ext_badge.setHidden(True)
self.count_badge.setHidden(True)
def set_filename_text(self, filename: Path | str | None):
self.file_label.setText(str(filename))
def set_filename_visibility(self, set_visible: bool):
"""Toggle the visibility of the filename label.
Args:
set_visible (bool): Show the filename, true or false.
"""
if set_visible:
if self.file_label.isHidden():
self.file_label.setHidden(False)
self.setFixedHeight(self.thumb_size[1] + self.label_height + self.label_spacing)
else:
self.file_label.setHidden(True)
self.setFixedHeight(self.thumb_size[1])
self.show_filename_label = set_visible
def update_thumb(self, timestamp: float, image: QPixmap | None = None):
"""Update attributes of a thumbnail element."""
if timestamp > ItemThumb.update_cutoff:
self.thumb_button.setIcon(image if image else QPixmap())
def update_size(self, timestamp: float, size: QSize):
"""Updates attributes of a thumbnail element."""
if timestamp > ItemThumb.update_cutoff and self.thumb_button.iconSize != size:
"""Updates attributes of a thumbnail element.
Args:
timestamp (float | None): The UTC timestamp for when this call was
originally dispatched. Used to skip outdated jobs.
size (QSize): The new thumbnail size to set.
"""
if timestamp > ItemThumb.update_cutoff:
self.thumb_size = size.toTuple() # type: ignore
self.thumb_button.setIconSize(size)
self.thumb_button.setMinimumSize(size)
self.thumb_button.setMaximumSize(size)

View File

@@ -72,7 +72,7 @@ class ThumbRenderer(QObject):
"""A class for rendering image and file thumbnails."""
rm: ResourceManager = ResourceManager()
updated = Signal(float, QPixmap, QSize, str)
updated = Signal(float, QPixmap, QSize, str, str)
updated_ratio = Signal(float)
def __init__(self) -> None:
@@ -1208,8 +1208,15 @@ class ThumbRenderer(QObject):
math.ceil(adj_size / pixel_ratio),
math.ceil(final.size[1] / pixel_ratio),
),
str(_filepath.name),
_filepath.suffix.lower(),
)
else:
self.updated.emit(timestamp, QPixmap(), QSize(*base_size), _filepath.suffix.lower())
self.updated.emit(
timestamp,
QPixmap(),
QSize(*base_size),
str(_filepath.name),
_filepath.suffix.lower(),
)