Skip to content

Commit 8387676

Browse files
authored
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
1 parent 24fa76e commit 8387676

File tree

4 files changed

+134
-58
lines changed

4 files changed

+134
-58
lines changed

tagstudio/src/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class SettingItems(str, enum.Enum):
1010
LAST_LIBRARY = "last_library"
1111
LIBS_LIST = "libs_list"
1212
WINDOW_SHOW_LIBS = "window_show_libs"
13+
SHOW_FILENAMES = "show_filenames"
1314
AUTOPLAY = "autoplay_videos"
1415

1516

tagstudio/src/qt/ts_qt.py

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,7 @@
2525
import structlog
2626
from humanfriendly import format_timespan
2727
from PySide6 import QtCore
28-
from PySide6.QtCore import (
29-
QObject,
30-
QSettings,
31-
Qt,
32-
QThread,
33-
QThreadPool,
34-
QTimer,
35-
Signal,
36-
)
28+
from PySide6.QtCore import QObject, QSettings, Qt, QThread, QThreadPool, QTimer, Signal
3729
from PySide6.QtGui import (
3830
QAction,
3931
QColor,
@@ -264,15 +256,12 @@ def start(self) -> None:
264256

265257
file_menu = QMenu("&File", menu_bar)
266258
edit_menu = QMenu("&Edit", menu_bar)
259+
view_menu = QMenu("&View", menu_bar)
267260
tools_menu = QMenu("&Tools", menu_bar)
268261
macros_menu = QMenu("&Macros", menu_bar)
269-
window_menu = QMenu("&Window", menu_bar)
270262
help_menu = QMenu("&Help", menu_bar)
271263

272264
# File Menu ============================================================
273-
# file_menu.addAction(QAction('&New Library', menu_bar))
274-
# file_menu.addAction(QAction('&Open Library', menu_bar))
275-
276265
open_library_action = QAction("&Open/Create Library", menu_bar)
277266
open_library_action.triggered.connect(lambda: self.open_library_from_dialog())
278267
open_library_action.setShortcut(
@@ -302,8 +291,6 @@ def start(self) -> None:
302291

303292
file_menu.addSeparator()
304293

305-
# refresh_lib_action = QAction('&Refresh Directories', self.main_window)
306-
# refresh_lib_action.triggered.connect(lambda: self.lib.refresh_dir())
307294
add_new_files_action = QAction("&Refresh Directories", menu_bar)
308295
add_new_files_action.triggered.connect(
309296
lambda: self.callback_library_needed_check(self.add_new_files_callback)
@@ -315,13 +302,23 @@ def start(self) -> None:
315302
)
316303
)
317304
add_new_files_action.setStatusTip("Ctrl+R")
318-
# file_menu.addAction(refresh_lib_action)
319305
file_menu.addAction(add_new_files_action)
320306
file_menu.addSeparator()
321307

322308
close_library_action = QAction("&Close Library", menu_bar)
323309
close_library_action.triggered.connect(self.close_library)
324310
file_menu.addAction(close_library_action)
311+
file_menu.addSeparator()
312+
313+
open_on_start_action = QAction("Open Library on Start", self)
314+
open_on_start_action.setCheckable(True)
315+
open_on_start_action.setChecked(
316+
bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool))
317+
)
318+
open_on_start_action.triggered.connect(
319+
lambda checked: self.settings.setValue(SettingItems.START_LOAD_LAST, checked)
320+
)
321+
file_menu.addAction(open_on_start_action)
325322

326323
# Edit Menu ============================================================
327324
new_tag_action = QAction("New &Tag", menu_bar)
@@ -364,15 +361,32 @@ def start(self) -> None:
364361
tag_database_action.triggered.connect(lambda: self.show_tag_database())
365362
edit_menu.addAction(tag_database_action)
366363

367-
check_action = QAction("Open library on start", self)
368-
check_action.setCheckable(True)
369-
check_action.setChecked(
370-
bool(self.settings.value(SettingItems.START_LOAD_LAST, defaultValue=True, type=bool))
364+
# View Menu ============================================================
365+
show_libs_list_action = QAction("Show Recent Libraries", menu_bar)
366+
show_libs_list_action.setCheckable(True)
367+
show_libs_list_action.setChecked(
368+
bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, defaultValue=True, type=bool))
371369
)
372-
check_action.triggered.connect(
373-
lambda checked: self.settings.setValue(SettingItems.START_LOAD_LAST, checked)
370+
show_libs_list_action.triggered.connect(
371+
lambda checked: (
372+
self.settings.setValue(SettingItems.WINDOW_SHOW_LIBS, checked),
373+
self.toggle_libs_list(checked),
374+
)
374375
)
375-
window_menu.addAction(check_action)
376+
view_menu.addAction(show_libs_list_action)
377+
378+
show_filenames_action = QAction("Show Filenames in Grid", menu_bar)
379+
show_filenames_action.setCheckable(True)
380+
show_filenames_action.setChecked(
381+
bool(self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool))
382+
)
383+
show_filenames_action.triggered.connect(
384+
lambda checked: (
385+
self.settings.setValue(SettingItems.SHOW_FILENAMES, checked),
386+
self.show_grid_filenames(checked),
387+
)
388+
)
389+
view_menu.addAction(show_filenames_action)
376390

377391
# Tools Menu ===========================================================
378392
def create_fix_unlinked_entries_modal():
@@ -407,19 +421,6 @@ def create_dupe_files_modal():
407421
)
408422
macros_menu.addAction(self.autofill_action)
409423

410-
show_libs_list_action = QAction("Show Recent Libraries", menu_bar)
411-
show_libs_list_action.setCheckable(True)
412-
show_libs_list_action.setChecked(
413-
bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, defaultValue=True, type=bool))
414-
)
415-
show_libs_list_action.triggered.connect(
416-
lambda checked: (
417-
self.settings.setValue(SettingItems.WINDOW_SHOW_LIBS, checked),
418-
self.toggle_libs_list(checked),
419-
)
420-
)
421-
window_menu.addAction(show_libs_list_action)
422-
423424
def create_folders_tags_modal():
424425
if not hasattr(self, "folders_modal"):
425426
self.folders_modal = FoldersToTagsModal(self.lib, self)
@@ -429,7 +430,7 @@ def create_folders_tags_modal():
429430
folders_to_tags_action.triggered.connect(create_folders_tags_modal)
430431
macros_menu.addAction(folders_to_tags_action)
431432

432-
# Help Menu ==========================================================
433+
# Help Menu ============================================================
433434
self.repo_action = QAction("Visit GitHub Repository", menu_bar)
434435
self.repo_action.triggered.connect(
435436
lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio")
@@ -439,9 +440,9 @@ def create_folders_tags_modal():
439440

440441
menu_bar.addMenu(file_menu)
441442
menu_bar.addMenu(edit_menu)
443+
menu_bar.addMenu(view_menu)
442444
menu_bar.addMenu(tools_menu)
443445
menu_bar.addMenu(macros_menu)
444-
menu_bar.addMenu(window_menu)
445446
menu_bar.addMenu(help_menu)
446447

447448
self.main_window.searchField.textChanged.connect(self.update_completions_list)
@@ -551,6 +552,10 @@ def toggle_libs_list(self, value: bool):
551552
self.preview_panel.libs_flow_container.hide()
552553
self.preview_panel.update()
553554

555+
def show_grid_filenames(self, value: bool):
556+
for thumb in self.item_thumbs:
557+
thumb.set_filename_visibility(value)
558+
554559
def callback_library_needed_check(self, func):
555560
"""Check if loaded library has valid path before executing the button function."""
556561
if self.lib.library_dir:
@@ -833,9 +838,9 @@ def thumb_size_callback(self, index: int):
833838
it.thumb_button.setIcon(blank_icon)
834839
it.resize(self.thumb_size, self.thumb_size)
835840
it.thumb_size = (self.thumb_size, self.thumb_size)
836-
it.setMinimumSize(self.thumb_size, self.thumb_size)
837-
it.setMaximumSize(self.thumb_size, self.thumb_size)
841+
it.setFixedSize(self.thumb_size, self.thumb_size)
838842
it.thumb_button.thumb_size = (self.thumb_size, self.thumb_size)
843+
it.set_filename_visibility(it.show_filename_label)
839844
self.flow_container.layout().setSpacing(
840845
min(self.thumb_size // spacing_divisor, min_spacing)
841846
)
@@ -883,7 +888,14 @@ def _init_thumb_grid(self):
883888
# TODO - init after library is loaded, it can have different page_size
884889
for grid_idx in range(self.filter.page_size):
885890
item_thumb = ItemThumb(
886-
None, self.lib, self, (self.thumb_size, self.thumb_size), grid_idx
891+
None,
892+
self.lib,
893+
self,
894+
(self.thumb_size, self.thumb_size),
895+
grid_idx,
896+
bool(
897+
self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool)
898+
),
887899
)
888900

889901
layout.addWidget(item_thumb)

tagstudio/src/qt/widgets/item_thumb.py

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,16 @@ class ItemThumb(FlowWidget):
110110
"padding-left: 1px;"
111111
)
112112

113+
filename_style = "font-size:10px;"
114+
113115
def __init__(
114116
self,
115117
mode: ItemType,
116118
library: Library,
117119
driver: "QtDriver",
118120
thumb_size: tuple[int, int],
119121
grid_idx: int,
122+
show_filename_label: bool = False,
120123
):
121124
super().__init__()
122125
self.grid_idx = grid_idx
@@ -125,17 +128,33 @@ def __init__(
125128
self.driver = driver
126129
self.item_id: int | None = None
127130
self.thumb_size: tuple[int, int] = thumb_size
131+
self.show_filename_label: bool = show_filename_label
132+
self.label_height = 12
133+
self.label_spacing = 4
128134
self.setMinimumSize(*thumb_size)
129135
self.setMaximumSize(*thumb_size)
130136
self.setMouseTracking(True)
131137
check_size = 24
138+
self.setFixedSize(
139+
thumb_size[0],
140+
thumb_size[1]
141+
+ ((self.label_height + self.label_spacing) if show_filename_label else 0),
142+
)
143+
144+
self.thumb_container = QWidget()
145+
self.base_layout = QVBoxLayout(self)
146+
self.base_layout.setContentsMargins(0, 0, 0, 0)
147+
self.base_layout.setSpacing(0)
148+
self.setLayout(self.base_layout)
132149

133150
# +----------+
134151
# | ARC FAV| Top Right: Favorite & Archived Badges
135152
# | |
136153
# | |
137154
# |EXT #| Lower Left: File Type, Tag Group Icon, or Collation Icon
138155
# +----------+ Lower Right: Collation Count, Video Length, or Word Count
156+
#
157+
# Filename Underneath: (Optional) Filename
139158

140159
# Thumbnail ============================================================
141160

@@ -145,9 +164,9 @@ def __init__(
145164
# || ||
146165
# |*--------*|
147166
# +----------+
148-
self.base_layout = QVBoxLayout(self)
149-
self.base_layout.setObjectName("baseLayout")
150-
self.base_layout.setContentsMargins(0, 0, 0, 0)
167+
self.thumb_layout = QVBoxLayout(self.thumb_container)
168+
self.thumb_layout.setObjectName("baseLayout")
169+
self.thumb_layout.setContentsMargins(0, 0, 0, 0)
151170

152171
# +----------+
153172
# |[~~~~~~~~]|
@@ -160,15 +179,15 @@ def __init__(
160179
self.top_layout.setContentsMargins(6, 6, 6, 6)
161180
self.top_container = QWidget()
162181
self.top_container.setLayout(self.top_layout)
163-
self.base_layout.addWidget(self.top_container)
182+
self.thumb_layout.addWidget(self.top_container)
164183

165184
# +----------+
166185
# |[~~~~~~~~]|
167186
# | ^ |
168187
# | | |
169188
# | v |
170189
# +----------+
171-
self.base_layout.addStretch(2)
190+
self.thumb_layout.addStretch(2)
172191

173192
# +----------+
174193
# |[~~~~~~~~]|
@@ -181,19 +200,20 @@ def __init__(
181200
self.bottom_layout.setContentsMargins(6, 6, 6, 6)
182201
self.bottom_container = QWidget()
183202
self.bottom_container.setLayout(self.bottom_layout)
184-
self.base_layout.addWidget(self.bottom_container)
203+
self.thumb_layout.addWidget(self.bottom_container)
185204

186-
self.thumb_button = ThumbButton(self, thumb_size)
205+
self.thumb_button = ThumbButton(self.thumb_container, thumb_size)
187206
self.renderer = ThumbRenderer()
188207
self.renderer.updated.connect(
189-
lambda ts, i, s, ext: (
208+
lambda ts, i, s, fn, ext: (
190209
self.update_thumb(ts, image=i),
191210
self.update_size(ts, size=s),
211+
self.set_filename_text(fn),
192212
self.set_extension(ext),
193213
)
194214
)
195215
self.thumb_button.setFlat(True)
196-
self.thumb_button.setLayout(self.base_layout)
216+
self.thumb_button.setLayout(self.thumb_layout)
197217
self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
198218

199219
self.opener = FileOpenerHelper("")
@@ -285,6 +305,16 @@ def __init__(
285305
self.badges[badge_type] = badge
286306
self.cb_layout.addWidget(badge)
287307

308+
# Filename Label =======================================================
309+
self.file_label = QLabel(text="Filename")
310+
self.file_label.setStyleSheet(ItemThumb.filename_style)
311+
self.file_label.setMaximumHeight(self.label_height)
312+
if not show_filename_label:
313+
self.file_label.setHidden(True)
314+
315+
self.base_layout.addWidget(self.thumb_container)
316+
self.base_layout.addWidget(self.file_label)
317+
288318
self.set_mode(mode)
289319

290320
@property
@@ -298,11 +328,11 @@ def is_archived(self):
298328
def set_mode(self, mode: ItemType | None) -> None:
299329
if mode is None:
300330
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=True)
301-
self.unsetCursor()
331+
self.thumb_button.unsetCursor()
302332
self.thumb_button.setHidden(True)
303333
elif mode == ItemType.ENTRY and self.mode != ItemType.ENTRY:
304334
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
305-
self.setCursor(Qt.CursorShape.PointingHandCursor)
335+
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
306336
self.thumb_button.setHidden(False)
307337
self.cb_container.setHidden(False)
308338
# Count Badge depends on file extension (video length, word count)
@@ -312,7 +342,7 @@ def set_mode(self, mode: ItemType | None) -> None:
312342
self.ext_badge.setHidden(True)
313343
elif mode == ItemType.COLLATION and self.mode != ItemType.COLLATION:
314344
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
315-
self.setCursor(Qt.CursorShape.PointingHandCursor)
345+
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
316346
self.thumb_button.setHidden(False)
317347
self.cb_container.setHidden(True)
318348
self.ext_badge.setHidden(True)
@@ -321,7 +351,7 @@ def set_mode(self, mode: ItemType | None) -> None:
321351
self.item_type_badge.setHidden(False)
322352
elif mode == ItemType.TAG_GROUP and self.mode != ItemType.TAG_GROUP:
323353
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
324-
self.setCursor(Qt.CursorShape.PointingHandCursor)
354+
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
325355
self.thumb_button.setHidden(False)
326356
self.ext_badge.setHidden(True)
327357
self.count_badge.setHidden(False)
@@ -366,14 +396,40 @@ def set_count(self, count: str) -> None:
366396
self.ext_badge.setHidden(True)
367397
self.count_badge.setHidden(True)
368398

399+
def set_filename_text(self, filename: Path | str | None):
400+
self.file_label.setText(str(filename))
401+
402+
def set_filename_visibility(self, set_visible: bool):
403+
"""Toggle the visibility of the filename label.
404+
405+
Args:
406+
set_visible (bool): Show the filename, true or false.
407+
"""
408+
if set_visible:
409+
if self.file_label.isHidden():
410+
self.file_label.setHidden(False)
411+
self.setFixedHeight(self.thumb_size[1] + self.label_height + self.label_spacing)
412+
else:
413+
self.file_label.setHidden(True)
414+
self.setFixedHeight(self.thumb_size[1])
415+
self.show_filename_label = set_visible
416+
369417
def update_thumb(self, timestamp: float, image: QPixmap | None = None):
370418
"""Update attributes of a thumbnail element."""
371419
if timestamp > ItemThumb.update_cutoff:
372420
self.thumb_button.setIcon(image if image else QPixmap())
373421

374422
def update_size(self, timestamp: float, size: QSize):
375-
"""Updates attributes of a thumbnail element."""
376-
if timestamp > ItemThumb.update_cutoff and self.thumb_button.iconSize != size:
423+
"""Updates attributes of a thumbnail element.
424+
425+
Args:
426+
timestamp (float | None): The UTC timestamp for when this call was
427+
originally dispatched. Used to skip outdated jobs.
428+
429+
size (QSize): The new thumbnail size to set.
430+
"""
431+
if timestamp > ItemThumb.update_cutoff:
432+
self.thumb_size = size.toTuple() # type: ignore
377433
self.thumb_button.setIconSize(size)
378434
self.thumb_button.setMinimumSize(size)
379435
self.thumb_button.setMaximumSize(size)

0 commit comments

Comments
 (0)