diff --git a/src/tagstudio/core/enums.py b/src/tagstudio/core/enums.py
index 2a11a12fd..31e383b6a 100644
--- a/src/tagstudio/core/enums.py
+++ b/src/tagstudio/core/enums.py
@@ -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"
diff --git a/src/tagstudio/qt/modals/settings_panel.py b/src/tagstudio/qt/modals/settings_panel.py
index c400c4a08..378397892 100644
--- a/src/tagstudio/qt/modals/settings_panel.py
+++ b/src/tagstudio/qt/modals/settings_panel.py
@@ -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
@@ -63,6 +63,25 @@ 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)
@@ -70,3 +89,19 @@ def __init__(self, driver):
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
+ )
+ )
diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py
index 33ba48e16..034fb3f03 100644
--- a/src/tagstudio/qt/ts_qt.py
+++ b/src/tagstudio/qt/ts_qt.py
@@ -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,
@@ -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)
@@ -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)
@@ -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()
@@ -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)
diff --git a/src/tagstudio/qt/widgets/preview/file_attributes.py b/src/tagstudio/qt/widgets/preview/file_attributes.py
index c9e84c701..c6f3c0b2c 100644
--- a/src/tagstudio/qt/widgets/preview/file_attributes.py
+++ b/src/tagstudio/qt/widgets/preview/file_attributes.py
@@ -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
@@ -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."""
@@ -142,6 +144,18 @@ 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)
@@ -149,12 +163,14 @@ def update_stats(self, filepath: Path | None = None, ext: str = ".", stats: dict
file_str: str = ""
separator: str = f"{os.path.sep}" # 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}"
+ if i != len(display_path.parts) - 1:
+ file_str += f"{"\u200b".join(part_)}{separator}"
else:
- file_str += f"
{'\u200b'.join(part_)}"
+ if file_str != "":
+ file_str += "
"
+ file_str += f"{"\u200b".join(part_)}"
self.file_label.setText(file_str)
self.file_label.setCursor(Qt.CursorShape.PointingHandCursor)
self.opener = FileOpenerHelper(filepath)
diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json
index d1dc2caa2..4bec8a99a 100644
--- a/src/tagstudio/resources/translations/en.json
+++ b/src/tagstudio/resources/translations/en.json
@@ -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.",
diff --git a/tests/qt/test_file_path_options.py b/tests/qt/test_file_path_options.py
new file mode 100644
index 000000000..642bcaa84
--- /dev/null
+++ b/tests/qt/test_file_path_options.py
@@ -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"{os.path.sep}" # 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}"
+ else:
+ if file_str != "":
+ file_str += "
"
+ file_str += f"{"\u200b".join(part_)}"
+
+ # 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))