Skip to content

feat: add setting to not display full file path #841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/tagstudio/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,21 @@ class SettingItems(str, enum.Enum):
LIBS_LIST = "libs_list"
WINDOW_SHOW_LIBS = "window_show_libs"
SHOW_FILENAMES = "show_filenames"
SHOW_FILEPATH = "show_filepath"
AUTOPLAY = "autoplay_videos"
THUMB_CACHE_SIZE_LIMIT = "thumb_cache_size_limit"
LANGUAGE = "language"


class ShowFilepathOption(int, enum.Enum):
"""Values representing the options for the "show_filenames" setting."""

SHOW_FULL_PATHS = 0
SHOW_RELATIVE_PATHS = 1
SHOW_FILENAMES_ONLY = 2
DEFAULT = SHOW_RELATIVE_PATHS


class Theme(str, enum.Enum):
COLOR_BG_DARK = "#65000000"
COLOR_BG_LIGHT = "#22000000"
Expand Down
37 changes: 36 additions & 1 deletion src/tagstudio/qt/modals/settings_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QComboBox, QFormLayout, QLabel, QVBoxLayout, QWidget

from tagstudio.core.enums import SettingItems
from tagstudio.core.enums import SettingItems, ShowFilepathOption
from tagstudio.qt.translations import Translations
from tagstudio.qt.widgets.panel import PanelWidget

Expand Down Expand Up @@ -63,10 +63,45 @@ def __init__(self, driver):
)
self.form_layout.addRow(language_label, self.language_combobox)

filepath_option_map: dict[int, str] = {
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations[
"settings.filepath.option.relative"
],
ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"],
}
self.filepath_combobox = QComboBox()
self.filepath_combobox.addItems(list(filepath_option_map.values()))
filepath_option: int = int(
driver.settings.value(
SettingItems.SHOW_FILEPATH, defaultValue=ShowFilepathOption.DEFAULT.value, type=int
)
)
filepath_option = 0 if filepath_option not in filepath_option_map else filepath_option
self.filepath_combobox.setCurrentIndex(filepath_option)
self.filepath_combobox.currentIndexChanged.connect(self.apply_filepath_setting)
self.form_layout.addRow(Translations["settings.filepath.label"], self.filepath_combobox)

self.root_layout.addWidget(self.form_container)
self.root_layout.addStretch(1)
self.root_layout.addWidget(self.restart_label)

def get_language(self) -> str:
values: list[str] = list(self.languages.values())
return values[self.language_combobox.currentIndex()]

def apply_filepath_setting(self):
selected_value = self.filepath_combobox.currentIndex()
self.driver.settings.setValue(SettingItems.SHOW_FILEPATH, selected_value)
self.driver.update_recent_lib_menu()
self.driver.preview_panel.update_widgets()
library_directory = self.driver.lib.library_dir
if selected_value == ShowFilepathOption.SHOW_FULL_PATHS:
display_path = library_directory
else:
display_path = library_directory.name
self.driver.main_window.setWindowTitle(
Translations.format(
"app.title", base_title=self.driver.base_title, library_dir=display_path
)
)
35 changes: 31 additions & 4 deletions src/tagstudio/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
import tagstudio.qt.resources_rc # noqa: F401
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE, VERSION, VERSION_BRANCH
from tagstudio.core.driver import DriverMixin
from tagstudio.core.enums import LibraryPrefs, MacroID, SettingItems
from tagstudio.core.enums import LibraryPrefs, MacroID, SettingItems, ShowFilepathOption
from tagstudio.core.library.alchemy.enums import (
FieldTypeEnum,
FilterState,
Expand Down Expand Up @@ -1749,6 +1749,11 @@ def update_recent_lib_menu(self):
"""Updates the recent library menu from the latest values from the settings file."""
actions: list[QAction] = []
lib_items: dict[str, tuple[str, str]] = {}
filepath_option: int = int(
self.settings.value(
SettingItems.SHOW_FILEPATH, defaultValue=ShowFilepathOption.DEFAULT.value, type=int
)
)

settings = self.settings
settings.beginGroup(SettingItems.LIBS_LIST)
Expand All @@ -1767,7 +1772,10 @@ def update_recent_lib_menu(self):
for library_key in libs_sorted:
path = Path(library_key[1][0])
action = QAction(self.open_recent_library_menu)
action.setText(str(path))
if filepath_option == ShowFilepathOption.SHOW_FULL_PATHS:
action.setText(str(path))
else:
action.setText(str(Path(path).name))
action.triggered.connect(lambda checked=False, p=path: self.open_library(p))
actions.append(action)

Expand Down Expand Up @@ -1822,7 +1830,15 @@ def update_language_settings(self, language: str):

def open_library(self, path: Path) -> None:
"""Open a TagStudio library."""
message = Translations.format("splash.opening_library", library_path=str(path))
filepath_option: int = int(
self.settings.value(
SettingItems.SHOW_FILEPATH, defaultValue=ShowFilepathOption.DEFAULT.value, type=int
)
)
library_dir_display = (
path if filepath_option == ShowFilepathOption.SHOW_FULL_PATHS else path.name
)
message = Translations.format("splash.opening_library", library_path=library_dir_display)
self.main_window.landing_widget.set_status_label(message)
self.main_window.statusbar.showMessage(message, 3)
self.main_window.repaint()
Expand Down Expand Up @@ -1867,12 +1883,23 @@ def init_library(self, path: Path, open_status: LibraryStatus):
if self.lib.entries_count < 10000:
self.add_new_files_callback()

library_dir_display = self.lib.library_dir
filepath_option: int = int(
self.settings.value(
SettingItems.SHOW_FILEPATH, defaultValue=ShowFilepathOption.DEFAULT.value, type=int
)
)
if filepath_option == ShowFilepathOption.SHOW_FULL_PATHS:
library_dir_display = self.lib.library_dir
else:
library_dir_display = self.lib.library_dir.name

self.update_libs_list(path)
self.main_window.setWindowTitle(
Translations.format(
"app.title",
base_title=self.base_title,
library_dir=self.lib.library_dir,
library_dir=library_dir_display,
)
)
self.main_window.setAcceptDrops(True)
Expand Down
26 changes: 21 additions & 5 deletions src/tagstudio/qt/widgets/preview/file_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from PySide6.QtGui import QGuiApplication
from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget

from tagstudio.core.enums import Theme
from tagstudio.core.enums import SettingItems, ShowFilepathOption, Theme
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.media_types import MediaCategories
from tagstudio.qt.helpers.file_opener import FileOpenerHelper, FileOpenerLabel
Expand Down Expand Up @@ -96,6 +96,8 @@ def __init__(self, library: Library, driver: "QtDriver"):
root_layout.addWidget(self.file_label)
root_layout.addWidget(self.date_container)
root_layout.addWidget(self.dimensions_label)
self.library = library
self.driver = driver

def update_date_label(self, filepath: Path | None = None) -> None:
"""Update the "Date Created" and "Date Modified" file property labels."""
Expand Down Expand Up @@ -142,19 +144,33 @@ def update_stats(self, filepath: Path | None = None, ext: str = ".", stats: dict
self.dimensions_label.setText("")
self.dimensions_label.setHidden(True)
else:
filepath_option = self.driver.settings.value(
SettingItems.SHOW_FILEPATH, defaultValue=ShowFilepathOption.DEFAULT.value, type=int
)
self.library_path = self.library.library_dir
display_path = filepath
if filepath_option == ShowFilepathOption.SHOW_FULL_PATHS:
display_path = filepath
elif filepath_option == ShowFilepathOption.SHOW_RELATIVE_PATHS:
display_path = Path(filepath).relative_to(self.library_path)
elif filepath_option == ShowFilepathOption.SHOW_FILENAMES_ONLY:
display_path = Path(filepath.name)

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
for i, part in enumerate(filepath.parts):
for i, part in enumerate(display_path.parts):
part_ = part.strip(os.path.sep)
if i != len(filepath.parts) - 1:
file_str += f"{'\u200b'.join(part_)}{separator}</b>"
if i != len(display_path.parts) - 1:
file_str += f"{"\u200b".join(part_)}{separator}</b>"
else:
file_str += f"<br><b>{'\u200b'.join(part_)}</b>"
if file_str != "":
file_str += "<br>"
file_str += f"<b>{"\u200b".join(part_)}</b>"
self.file_label.setText(file_str)
self.file_label.setCursor(Qt.CursorShape.PointingHandCursor)
self.opener = FileOpenerHelper(filepath)
Expand Down
4 changes: 4 additions & 0 deletions src/tagstudio/resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@
"edit.copy_fields": "Copy Fields",
"edit.paste_fields": "Paste Fields",
"settings.clear_thumb_cache.title": "Clear Thumbnail Cache",
"settings.filepath.label": "Filepath Visibility",
"settings.filepath.option.full": "Show Full Paths",
"settings.filepath.option.name": "Show Filenames Only",
"settings.filepath.option.relative": "Show Relative Paths",
"settings.language": "Language",
"settings.open_library_on_start": "Open Library on Start",
"settings.restart_required": "Please restart TagStudio for changes to take effect.",
Expand Down
130 changes: 130 additions & 0 deletions tests/qt/test_file_path_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import os
import pathlib
from unittest.mock import patch

import pytest
from PySide6.QtGui import (
QAction,
)
from PySide6.QtWidgets import QMenu, QMenuBar

from tagstudio.core.enums import SettingItems, ShowFilepathOption
from tagstudio.core.library.alchemy.library import LibraryStatus
from tagstudio.qt.modals.settings_panel import SettingsPanel
from tagstudio.qt.widgets.preview_panel import PreviewPanel


# Tests to see if the file path setting is applied correctly
@pytest.mark.parametrize(
"filepath_option",
[
ShowFilepathOption.SHOW_FULL_PATHS.value,
ShowFilepathOption.SHOW_RELATIVE_PATHS.value,
ShowFilepathOption.SHOW_FILENAMES_ONLY.value,
],
)
def test_filepath_setting(qtbot, qt_driver, filepath_option):
settings_panel = SettingsPanel(qt_driver)
qtbot.addWidget(settings_panel)

# Mock the update_recent_lib_menu method
with patch.object(qt_driver, "update_recent_lib_menu", return_value=None):
# Set the file path option
settings_panel.filepath_combobox.setCurrentIndex(filepath_option)
settings_panel.apply_filepath_setting()

# Assert the setting is applied
assert qt_driver.settings.value(SettingItems.SHOW_FILEPATH) == filepath_option


# Tests to see if the file paths are being displayed correctly
@pytest.mark.parametrize(
"filepath_option, expected_path",
[
(
ShowFilepathOption.SHOW_FULL_PATHS,
lambda library: pathlib.Path(library.library_dir / "one/two/bar.md"),
),
(ShowFilepathOption.SHOW_RELATIVE_PATHS, lambda library: pathlib.Path("one/two/bar.md")),
(ShowFilepathOption.SHOW_FILENAMES_ONLY, lambda library: pathlib.Path("bar.md")),
],
)
def test_file_path_display(qt_driver, library, filepath_option, expected_path):
panel = PreviewPanel(library, qt_driver)

# Select 2
qt_driver.toggle_item_selection(2, append=False, bridge=False)
panel.update_widgets()

with patch.object(qt_driver.settings, "value", return_value=filepath_option):
# Apply the mock value
filename = library.get_entry(2).path
panel.file_attrs.update_stats(filepath=pathlib.Path(library.library_dir / filename))

# Generate the expected file string.
# This is copied directly from the file_attributes.py file
# can be imported as a function in the future
display_path = expected_path(library)
file_str: str = ""
separator: str = f"<a style='color: #777777'><b>{os.path.sep}</a>" # Gray
for i, part in enumerate(display_path.parts):
part_ = part.strip(os.path.sep)
if i != len(display_path.parts) - 1:
file_str += f"{"\u200b".join(part_)}{separator}</b>"
else:
if file_str != "":
file_str += "<br>"
file_str += f"<b>{"\u200b".join(part_)}</b>"

# Assert the file path is displayed correctly
assert panel.file_attrs.file_label.text() == file_str


@pytest.mark.parametrize(
"filepath_option, expected_title",
[
(
ShowFilepathOption.SHOW_FULL_PATHS.value,
lambda path, base_title: f"{base_title} - Library '{path}'",
),
(
ShowFilepathOption.SHOW_RELATIVE_PATHS.value,
lambda path, base_title: f"{base_title} - Library '{path.name}'",
),
(
ShowFilepathOption.SHOW_FILENAMES_ONLY.value,
lambda path, base_title: f"{base_title} - Library '{path.name}'",
),
],
)
def test_title_update(qtbot, qt_driver, filepath_option, expected_title):
base_title = qt_driver.base_title
test_path = pathlib.Path("/dev/null")
open_status = LibraryStatus(
success=True,
library_path=test_path,
message="",
msg_description="",
)
# Set the file path option
qt_driver.settings.setValue(SettingItems.SHOW_FILEPATH, filepath_option)
menu_bar = QMenuBar()

qt_driver.open_recent_library_menu = QMenu(menu_bar)
qt_driver.manage_file_ext_action = QAction(menu_bar)
qt_driver.save_library_backup_action = QAction(menu_bar)
qt_driver.close_library_action = QAction(menu_bar)
qt_driver.refresh_dir_action = QAction(menu_bar)
qt_driver.tag_manager_action = QAction(menu_bar)
qt_driver.color_manager_action = QAction(menu_bar)
qt_driver.new_tag_action = QAction(menu_bar)
qt_driver.fix_dupe_files_action = QAction(menu_bar)
qt_driver.fix_unlinked_entries_action = QAction(menu_bar)
qt_driver.clear_thumb_cache_action = QAction(menu_bar)
qt_driver.folders_to_tags_action = QAction(menu_bar)

# Trigger the update
qt_driver.init_library(pathlib.Path(test_path), open_status)

# Assert the title is updated correctly
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(test_path, base_title))