mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-28 22:01:24 +00:00
feat: new settings menu + settings backend (#859)
* feat: add tab widget * refactor: move languages dict to translations.py * refactor: move build of Settings Modal to SettingsPanel class * feat: hide title label * feat: global settings class * fix: initialise settings * fix: properly store grid files changes * fix: placeholder text for library settings * feat: add ui elements for remaining global settings * feat: add page size setting * fix: version mismatch between pydantic and typing_extensions * fix: update test_driver.py * fix(test_file_path_options): replace patch with change of settings * feat: setting for dark mode * fix: only show restart_label when necessary * fix: change modal from "done" type to "Save/Cancel" type * feat: add test for GlobalSettings * docs: mark roadmap item as completed * fix(test_filepath_setting): Mock the app field of QtDriver * Update src/tagstudio/main.py Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> * fix: address review suggestions * fix: page size setting * feat: change dark mode option to theme dropdown * fix: test was expecting wrong behaviour * fix: test was testing for correct behaviour, fix behaviour instead * fix: test fr fr * fix: tests fr fr fr * fix: tests fr fr fr fr * fix: update test * fix: tests fr fr fr fr fr * fix: select all was selecting hidden entries * fix: create more thumbitems as necessary
This commit is contained in:
@@ -106,9 +106,9 @@ These version milestones are rough estimations for when the previous core featur
|
||||
- [ ] 3D Model Previews [MEDIUM]
|
||||
- [ ] STL Previews [HIGH]
|
||||
- [ ] Word count/line count on text thumbnails [LOW]
|
||||
- [ ] Settings Menu [HIGH]
|
||||
- [ ] Application Settings [HIGH]
|
||||
- [ ] Stored in system user folder/designated folder [HIGH]
|
||||
- [x] Settings Menu [HIGH]
|
||||
- [x] Application Settings [HIGH]
|
||||
- [x] Stored in system user folder/designated folder [HIGH]
|
||||
- [ ] Library Settings [HIGH]
|
||||
- [ ] Stored in `.TagStudio` folder [HIGH]
|
||||
- [ ] Tagging Panel [HIGH]
|
||||
|
||||
@@ -27,6 +27,8 @@ dependencies = [
|
||||
"typing_extensions>=3.10.0.0,<4.11.0",
|
||||
"ujson>=5.8.0,<5.9.0",
|
||||
"vtf2img==0.1.0",
|
||||
"toml==0.10.2",
|
||||
"pydantic==2.9.2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -5,13 +5,15 @@ from PySide6.QtCore import QSettings
|
||||
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.enums import SettingItems
|
||||
from tagstudio.core.global_settings import GlobalSettings
|
||||
from tagstudio.core.library.alchemy.library import LibraryStatus
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class DriverMixin:
|
||||
settings: QSettings
|
||||
cached_values: QSettings
|
||||
settings: GlobalSettings
|
||||
|
||||
def evaluate_path(self, open_path: str | None) -> LibraryStatus:
|
||||
"""Check if the path of library is valid."""
|
||||
@@ -21,17 +23,17 @@ class DriverMixin:
|
||||
if not library_path.exists():
|
||||
logger.error("Path does not exist.", open_path=open_path)
|
||||
return LibraryStatus(success=False, message="Path does not exist.")
|
||||
elif self.settings.value(
|
||||
SettingItems.START_LOAD_LAST, defaultValue=True, type=bool
|
||||
) and self.settings.value(SettingItems.LAST_LIBRARY):
|
||||
library_path = Path(str(self.settings.value(SettingItems.LAST_LIBRARY)))
|
||||
elif self.settings.open_last_loaded_on_startup and self.cached_values.value(
|
||||
SettingItems.LAST_LIBRARY
|
||||
):
|
||||
library_path = Path(str(self.cached_values.value(SettingItems.LAST_LIBRARY)))
|
||||
if not (library_path / TS_FOLDER_NAME).exists():
|
||||
logger.error(
|
||||
"TagStudio folder does not exist.",
|
||||
library_path=library_path,
|
||||
ts_folder=TS_FOLDER_NAME,
|
||||
)
|
||||
self.settings.setValue(SettingItems.LAST_LIBRARY, "")
|
||||
self.cached_values.setValue(SettingItems.LAST_LIBRARY, "")
|
||||
# dont consider this a fatal error, just skip opening the library
|
||||
library_path = None
|
||||
|
||||
|
||||
@@ -10,15 +10,9 @@ from uuid import uuid4
|
||||
class SettingItems(str, enum.Enum):
|
||||
"""List of setting item names."""
|
||||
|
||||
START_LOAD_LAST = "start_load_last"
|
||||
LAST_LIBRARY = "last_library"
|
||||
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):
|
||||
@@ -81,5 +75,4 @@ class LibraryPrefs(DefaultEnum):
|
||||
|
||||
IS_EXCLUDE_LIST = True
|
||||
EXTENSION_LIST = [".json", ".xmp", ".aae"]
|
||||
PAGE_SIZE = 500
|
||||
DB_VERSION = 9
|
||||
|
||||
70
src/tagstudio/core/global_settings.py
Normal file
70
src/tagstudio/core/global_settings.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
import platform
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
import structlog
|
||||
import toml
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from tagstudio.core.enums import ShowFilepathOption
|
||||
|
||||
if platform.system() == "Windows":
|
||||
DEFAULT_GLOBAL_SETTINGS_PATH = (
|
||||
Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml"
|
||||
)
|
||||
else:
|
||||
DEFAULT_GLOBAL_SETTINGS_PATH = Path.home() / ".config" / "TagStudio" / "settings.toml"
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class TomlEnumEncoder(toml.TomlEncoder):
|
||||
@override
|
||||
def dump_value(self, v):
|
||||
if isinstance(v, Enum):
|
||||
return super().dump_value(v.value)
|
||||
return super().dump_value(v)
|
||||
|
||||
|
||||
class Theme(Enum):
|
||||
DARK = 0
|
||||
LIGHT = 1
|
||||
SYSTEM = 2
|
||||
DEFAULT = SYSTEM
|
||||
|
||||
|
||||
# NOTE: pydantic also has a BaseSettings class (from pydantic-settings) that allows any settings
|
||||
# properties to be overwritten with environment variables. as tagstudio is not currently using
|
||||
# environment variables, i did not base it on that, but that may be useful in the future.
|
||||
class GlobalSettings(BaseModel):
|
||||
language: str = Field(default="en")
|
||||
open_last_loaded_on_startup: bool = Field(default=False)
|
||||
autoplay: bool = Field(default=False)
|
||||
show_filenames_in_grid: bool = Field(default=False)
|
||||
page_size: int = Field(default=500)
|
||||
show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT)
|
||||
theme: Theme = Field(default=Theme.SYSTEM)
|
||||
|
||||
@staticmethod
|
||||
def read_settings(path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> "GlobalSettings":
|
||||
if path.exists():
|
||||
with open(path) as file:
|
||||
filecontents = file.read()
|
||||
if len(filecontents.strip()) != 0:
|
||||
logger.info("[Settings] Reading Global Settings File", path=path)
|
||||
settings_data = toml.loads(filecontents)
|
||||
settings = GlobalSettings(**settings_data)
|
||||
return settings
|
||||
|
||||
return GlobalSettings()
|
||||
|
||||
def save(self, path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> None:
|
||||
if not path.parent.exists():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(path, "w") as f:
|
||||
toml.dump(dict(self), f, encoder=TomlEnumEncoder())
|
||||
@@ -76,14 +76,14 @@ class FilterState:
|
||||
"""Represent a state of the Library grid view."""
|
||||
|
||||
# these should remain
|
||||
page_index: int | None = 0
|
||||
page_size: int | None = 500
|
||||
page_size: int
|
||||
page_index: int = 0
|
||||
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
|
||||
ascending: bool = True
|
||||
|
||||
# these should be erased on update
|
||||
# Abstract Syntax Tree Of the current Search Query
|
||||
ast: AST = None
|
||||
ast: AST | None = None
|
||||
|
||||
@property
|
||||
def limit(self):
|
||||
@@ -94,35 +94,32 @@ class FilterState:
|
||||
return self.page_size * self.page_index
|
||||
|
||||
@classmethod
|
||||
def show_all(cls) -> "FilterState":
|
||||
return FilterState()
|
||||
def show_all(cls, page_size: int) -> "FilterState":
|
||||
return FilterState(page_size=page_size)
|
||||
|
||||
@classmethod
|
||||
def from_search_query(cls, search_query: str) -> "FilterState":
|
||||
return cls(ast=Parser(search_query).parse())
|
||||
def from_search_query(cls, search_query: str, page_size: int) -> "FilterState":
|
||||
return cls(ast=Parser(search_query).parse(), page_size=page_size)
|
||||
|
||||
@classmethod
|
||||
def from_tag_id(cls, tag_id: int | str) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.TagID, str(tag_id), []))
|
||||
def from_tag_id(cls, tag_id: int | str, page_size: int) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.TagID, str(tag_id), []), page_size=page_size)
|
||||
|
||||
@classmethod
|
||||
def from_path(cls, path: Path | str) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.Path, str(path).strip(), []))
|
||||
def from_path(cls, path: Path | str, page_size: int) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.Path, str(path).strip(), []), page_size=page_size)
|
||||
|
||||
@classmethod
|
||||
def from_mediatype(cls, mediatype: str) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.MediaType, mediatype, []))
|
||||
def from_mediatype(cls, mediatype: str, page_size: int) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.MediaType, mediatype, []), page_size=page_size)
|
||||
|
||||
@classmethod
|
||||
def from_filetype(cls, filetype: str) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.FileType, filetype, []))
|
||||
def from_filetype(cls, filetype: str, page_size: int) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.FileType, filetype, []), page_size=page_size)
|
||||
|
||||
@classmethod
|
||||
def from_tag_name(cls, tag_name: str) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.Tag, tag_name, []))
|
||||
|
||||
def with_page_size(self, page_size: int) -> "FilterState":
|
||||
return replace(self, page_size=page_size)
|
||||
def from_tag_name(cls, tag_name: str, page_size: int) -> "FilterState":
|
||||
return cls(ast=Constraint(ConstraintType.Tag, tag_name, []), page_size=page_size)
|
||||
|
||||
def with_sorting_mode(self, mode: SortingModeEnum) -> "FilterState":
|
||||
return replace(self, sorting_mode=mode)
|
||||
|
||||
@@ -52,7 +52,7 @@ class DupeRegistry:
|
||||
continue
|
||||
|
||||
results = self.library.search_library(
|
||||
FilterState.from_path(path_relative),
|
||||
FilterState.from_path(path_relative, page_size=500),
|
||||
)
|
||||
|
||||
if not results:
|
||||
|
||||
@@ -33,11 +33,18 @@ def main():
|
||||
help="Path to a TagStudio Library folder to open on start.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config-file",
|
||||
dest="config_file",
|
||||
"-s",
|
||||
"--settings-file",
|
||||
dest="settings_file",
|
||||
type=str,
|
||||
help="Path to a TagStudio .ini or .plist config file to use.",
|
||||
help="Path to a TagStudio .toml global settings file to use.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--cache-file",
|
||||
dest="cache_file",
|
||||
type=str,
|
||||
help="Path to a TagStudio .ini or .plist cache file to use.",
|
||||
)
|
||||
|
||||
# parser.add_argument('--browse', dest='browse', action='store_true',
|
||||
@@ -50,12 +57,6 @@ def main():
|
||||
action="store_true",
|
||||
help="Reveals additional internal data useful for debugging.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ui",
|
||||
dest="ui",
|
||||
type=str,
|
||||
help="User interface option for TagStudio. Options: qt, cli (Default: qt)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
driver = QtDriver(args)
|
||||
|
||||
@@ -32,7 +32,7 @@ class CacheManager(metaclass=Singleton):
|
||||
self.last_lib_path: Path | None = None
|
||||
|
||||
@staticmethod
|
||||
def clear_cache(library_dir: Path) -> bool:
|
||||
def clear_cache(library_dir: Path | None) -> bool:
|
||||
"""Clear all files and folders within the cached folder.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -3,105 +3,208 @@
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QComboBox, QFormLayout, QLabel, QVBoxLayout, QWidget
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from tagstudio.core.enums import SettingItems, ShowFilepathOption
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelWidget
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QFormLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QTabWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.enums import ShowFilepathOption
|
||||
from tagstudio.core.global_settings import Theme
|
||||
from tagstudio.qt.translations import DEFAULT_TRANSLATION, LANGUAGES, Translations
|
||||
from tagstudio.qt.widgets.panel import PanelModal, PanelWidget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
FILEPATH_OPTION_MAP: dict[ShowFilepathOption, 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"],
|
||||
}
|
||||
|
||||
THEME_MAP: dict[Theme, str] = {
|
||||
Theme.DARK: Translations["settings.theme.dark"],
|
||||
Theme.LIGHT: Translations["settings.theme.light"],
|
||||
Theme.SYSTEM: Translations["settings.theme.system"],
|
||||
}
|
||||
|
||||
|
||||
class SettingsPanel(PanelWidget):
|
||||
def __init__(self, driver):
|
||||
driver: "QtDriver"
|
||||
|
||||
def __init__(self, driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.setMinimumSize(320, 200)
|
||||
self.setMinimumSize(400, 300)
|
||||
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(6, 0, 6, 0)
|
||||
self.root_layout.setContentsMargins(0, 6, 0, 0)
|
||||
|
||||
self.form_container = QWidget()
|
||||
self.form_layout = QFormLayout(self.form_container)
|
||||
self.form_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# Tabs
|
||||
self.tab_widget = QTabWidget()
|
||||
|
||||
self.__build_global_settings()
|
||||
self.tab_widget.addTab(self.global_settings_container, Translations["settings.global"])
|
||||
|
||||
# self.__build_library_settings()
|
||||
# self.tab_widget.addTab(self.library_settings_container, Translations["settings.library"])
|
||||
|
||||
self.root_layout.addWidget(self.tab_widget)
|
||||
|
||||
# Restart Label
|
||||
self.restart_label = QLabel(Translations["settings.restart_required"])
|
||||
self.restart_label.setHidden(True)
|
||||
self.restart_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
language_label = QLabel(Translations["settings.language"])
|
||||
self.languages = {
|
||||
# "Cantonese (Traditional)": "yue_Hant", # Empty
|
||||
"Chinese (Traditional)": "zh_Hant",
|
||||
# "Czech": "cs", # Minimal
|
||||
# "Danish": "da", # Minimal
|
||||
"Dutch": "nl",
|
||||
"English": "en",
|
||||
"Filipino": "fil",
|
||||
"French": "fr",
|
||||
"German": "de",
|
||||
"Hungarian": "hu",
|
||||
# "Italian": "it", # Minimal
|
||||
"Norwegian Bokmål": "nb_NO",
|
||||
"Polish": "pl",
|
||||
"Portuguese (Brazil)": "pt_BR",
|
||||
# "Portuguese (Portugal)": "pt", # Empty
|
||||
"Russian": "ru",
|
||||
"Spanish": "es",
|
||||
"Swedish": "sv",
|
||||
"Tamil": "ta",
|
||||
"Toki Pona": "tok",
|
||||
"Turkish": "tr",
|
||||
}
|
||||
self.language_combobox = QComboBox()
|
||||
self.language_combobox.addItems(list(self.languages.keys()))
|
||||
current_lang: str = str(
|
||||
driver.settings.value(SettingItems.LANGUAGE, defaultValue="en", type=str)
|
||||
)
|
||||
current_lang = "en" if current_lang not in self.languages.values() else current_lang
|
||||
self.language_combobox.setCurrentIndex(list(self.languages.values()).index(current_lang))
|
||||
self.language_combobox.currentIndexChanged.connect(
|
||||
lambda: self.restart_label.setHidden(False)
|
||||
)
|
||||
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()]
|
||||
self.__update_restart_label()
|
||||
|
||||
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
|
||||
)
|
||||
def __update_restart_label(self):
|
||||
show_label = (
|
||||
self.language_combobox.currentData() != Translations.current_language
|
||||
or self.theme_combobox.currentData() != self.driver.applied_theme
|
||||
)
|
||||
self.restart_label.setHidden(not show_label)
|
||||
|
||||
def __build_global_settings(self):
|
||||
self.global_settings_container = QWidget()
|
||||
form_layout = QFormLayout(self.global_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
# Language
|
||||
self.language_combobox = QComboBox()
|
||||
for k in LANGUAGES:
|
||||
self.language_combobox.addItem(k, LANGUAGES[k])
|
||||
current_lang: str = self.driver.settings.language
|
||||
if current_lang not in LANGUAGES.values():
|
||||
current_lang = DEFAULT_TRANSLATION
|
||||
self.language_combobox.setCurrentIndex(list(LANGUAGES.values()).index(current_lang))
|
||||
self.language_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.language"], self.language_combobox)
|
||||
|
||||
# Open Last Library on Start
|
||||
self.open_last_lib_checkbox = QCheckBox()
|
||||
self.open_last_lib_checkbox.setChecked(self.driver.settings.open_last_loaded_on_startup)
|
||||
form_layout.addRow(
|
||||
Translations["settings.open_library_on_start"], self.open_last_lib_checkbox
|
||||
)
|
||||
|
||||
# Autoplay
|
||||
self.autoplay_checkbox = QCheckBox()
|
||||
self.autoplay_checkbox.setChecked(self.driver.settings.autoplay)
|
||||
form_layout.addRow(Translations["media_player.autoplay"], self.autoplay_checkbox)
|
||||
|
||||
# Show Filenames in Grid
|
||||
self.show_filenames_checkbox = QCheckBox()
|
||||
self.show_filenames_checkbox.setChecked(self.driver.settings.show_filenames_in_grid)
|
||||
form_layout.addRow(
|
||||
Translations["settings.show_filenames_in_grid"], self.show_filenames_checkbox
|
||||
)
|
||||
|
||||
# Page Size
|
||||
self.page_size_line_edit = QLineEdit()
|
||||
self.page_size_line_edit.setText(str(self.driver.settings.page_size))
|
||||
|
||||
def on_page_size_changed():
|
||||
text = self.page_size_line_edit.text()
|
||||
if not text.isdigit() or int(text) < 1:
|
||||
self.page_size_line_edit.setText(str(self.driver.settings.page_size))
|
||||
|
||||
self.page_size_line_edit.editingFinished.connect(on_page_size_changed)
|
||||
form_layout.addRow(Translations["settings.page_size"], self.page_size_line_edit)
|
||||
|
||||
# Show Filepath
|
||||
self.filepath_combobox = QComboBox()
|
||||
for k in FILEPATH_OPTION_MAP:
|
||||
self.filepath_combobox.addItem(FILEPATH_OPTION_MAP[k], k)
|
||||
filepath_option: ShowFilepathOption = self.driver.settings.show_filepath
|
||||
if filepath_option not in FILEPATH_OPTION_MAP:
|
||||
filepath_option = ShowFilepathOption.DEFAULT
|
||||
self.filepath_combobox.setCurrentIndex(
|
||||
list(FILEPATH_OPTION_MAP.keys()).index(filepath_option)
|
||||
)
|
||||
form_layout.addRow(Translations["settings.filepath.label"], self.filepath_combobox)
|
||||
|
||||
# Dark Mode
|
||||
self.theme_combobox = QComboBox()
|
||||
for k in THEME_MAP:
|
||||
self.theme_combobox.addItem(THEME_MAP[k], k)
|
||||
theme: Theme = self.driver.settings.theme
|
||||
if theme not in THEME_MAP:
|
||||
theme = Theme.DEFAULT
|
||||
self.theme_combobox.setCurrentIndex(list(THEME_MAP.keys()).index(theme))
|
||||
self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label)
|
||||
form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox)
|
||||
|
||||
def __build_library_settings(self):
|
||||
self.library_settings_container = QWidget()
|
||||
form_layout = QFormLayout(self.library_settings_container)
|
||||
form_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
todo_label = QLabel("TODO")
|
||||
form_layout.addRow(todo_label)
|
||||
|
||||
def __get_language(self) -> str:
|
||||
return list(LANGUAGES.values())[self.language_combobox.currentIndex()]
|
||||
|
||||
def get_settings(self) -> dict:
|
||||
return {
|
||||
"language": self.__get_language(),
|
||||
"open_last_loaded_on_startup": self.open_last_lib_checkbox.isChecked(),
|
||||
"autoplay": self.autoplay_checkbox.isChecked(),
|
||||
"show_filenames_in_grid": self.show_filenames_checkbox.isChecked(),
|
||||
"page_size": int(self.page_size_line_edit.text()),
|
||||
"show_filepath": self.filepath_combobox.currentData(),
|
||||
"theme": self.theme_combobox.currentData(),
|
||||
}
|
||||
|
||||
def update_settings(self, driver: "QtDriver"):
|
||||
settings = self.get_settings()
|
||||
|
||||
driver.settings.language = settings["language"]
|
||||
driver.settings.open_last_loaded_on_startup = settings["open_last_loaded_on_startup"]
|
||||
driver.settings.autoplay = settings["autoplay"]
|
||||
driver.settings.show_filenames_in_grid = settings["show_filenames_in_grid"]
|
||||
driver.settings.page_size = settings["page_size"]
|
||||
driver.settings.show_filepath = settings["show_filepath"]
|
||||
driver.settings.theme = settings["theme"]
|
||||
|
||||
driver.settings.save()
|
||||
|
||||
# Apply changes
|
||||
# Show File Path
|
||||
driver.update_recent_lib_menu()
|
||||
driver.preview_panel.update_widgets()
|
||||
library_directory = driver.lib.library_dir
|
||||
if settings["show_filepath"] == ShowFilepathOption.SHOW_FULL_PATHS:
|
||||
display_path = library_directory or ""
|
||||
else:
|
||||
display_path = library_directory.name if library_directory else ""
|
||||
driver.main_window.setWindowTitle(
|
||||
Translations.format("app.title", base_title=driver.base_title, library_dir=display_path)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def build_modal(cls, driver: "QtDriver") -> PanelModal:
|
||||
settings_panel = cls(driver)
|
||||
|
||||
modal = PanelModal(
|
||||
widget=settings_panel,
|
||||
done_callback=lambda: settings_panel.update_settings(driver),
|
||||
has_save=True,
|
||||
)
|
||||
modal.title_widget.setVisible(False)
|
||||
modal.setWindowTitle(Translations["settings.title"])
|
||||
|
||||
return modal
|
||||
|
||||
@@ -38,11 +38,13 @@ logger = structlog.get_logger(__name__)
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.modals.build_tag import BuildTagPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class TagSearchPanel(PanelWidget):
|
||||
tag_chosen = Signal(int)
|
||||
lib: Library
|
||||
driver: "QtDriver"
|
||||
is_initialized: bool = False
|
||||
first_tag_id: int | None = None
|
||||
is_tag_chooser: bool
|
||||
@@ -290,7 +292,9 @@ class TagSearchPanel(PanelWidget):
|
||||
tag_widget.search_for_tag_action.triggered.connect(
|
||||
lambda checked=False, tag_id=tag.id: (
|
||||
self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"),
|
||||
self.driver.filter_items(FilterState.from_tag_id(tag_id)),
|
||||
self.driver.filter_items(
|
||||
FilterState.from_tag_id(tag_id, page_size=self.driver.settings.page_size)
|
||||
),
|
||||
)
|
||||
)
|
||||
tag_widget.search_for_tag_action.setEnabled(True)
|
||||
|
||||
@@ -10,11 +10,35 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
DEFAULT_TRANSLATION = "en"
|
||||
|
||||
LANGUAGES = {
|
||||
# "Cantonese (Traditional)": "yue_Hant", # Empty
|
||||
"Chinese (Traditional)": "zh_Hant",
|
||||
# "Czech": "cs", # Minimal
|
||||
# "Danish": "da", # Minimal
|
||||
"Dutch": "nl",
|
||||
"English": "en",
|
||||
"Filipino": "fil",
|
||||
"French": "fr",
|
||||
"German": "de",
|
||||
"Hungarian": "hu",
|
||||
# "Italian": "it", # Minimal
|
||||
"Norwegian Bokmål": "nb_NO",
|
||||
"Polish": "pl",
|
||||
"Portuguese (Brazil)": "pt_BR",
|
||||
# "Portuguese (Portugal)": "pt", # Empty
|
||||
"Russian": "ru",
|
||||
"Spanish": "es",
|
||||
"Swedish": "sv",
|
||||
"Tamil": "ta",
|
||||
"Toki Pona": "tok",
|
||||
"Turkish": "tr",
|
||||
}
|
||||
|
||||
|
||||
class Translator:
|
||||
_default_strings: dict[str, str]
|
||||
_strings: dict[str, str] = {}
|
||||
_lang: str = DEFAULT_TRANSLATION
|
||||
__lang: str = DEFAULT_TRANSLATION
|
||||
|
||||
def __init__(self):
|
||||
self._default_strings = self.__get_translation_dict(DEFAULT_TRANSLATION)
|
||||
@@ -27,7 +51,7 @@ class Translator:
|
||||
return ujson.loads(f.read())
|
||||
|
||||
def change_language(self, lang: str):
|
||||
self._lang = lang
|
||||
self.__lang = lang
|
||||
self._strings = self.__get_translation_dict(lang)
|
||||
if system() == "Darwin":
|
||||
for k, v in self._strings.items():
|
||||
@@ -43,7 +67,7 @@ class Translator:
|
||||
"[Translations] Error while formatting translation.",
|
||||
text=text,
|
||||
kwargs=kwargs,
|
||||
language=self._lang,
|
||||
language=self.__lang,
|
||||
)
|
||||
params: defaultdict[str, Any] = defaultdict(lambda: "{unknown_key}")
|
||||
params.update(kwargs)
|
||||
@@ -55,5 +79,9 @@ class Translator:
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return self._strings.get(key) or self._default_strings.get(key) or f"[{key}]"
|
||||
|
||||
@property
|
||||
def current_language(self) -> str:
|
||||
return self.__lang
|
||||
|
||||
|
||||
Translations = Translator()
|
||||
|
||||
@@ -17,6 +17,7 @@ import platform
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
from shutil import which
|
||||
@@ -56,7 +57,8 @@ from PySide6.QtWidgets import (
|
||||
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, ShowFilepathOption
|
||||
from tagstudio.core.enums import MacroID, SettingItems, ShowFilepathOption
|
||||
from tagstudio.core.global_settings import DEFAULT_GLOBAL_SETTINGS_PATH, GlobalSettings, Theme
|
||||
from tagstudio.core.library.alchemy.enums import (
|
||||
FieldTypeEnum,
|
||||
FilterState,
|
||||
@@ -142,25 +144,30 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
SIGTERM = Signal()
|
||||
|
||||
preview_panel: PreviewPanel | None = None
|
||||
preview_panel: PreviewPanel
|
||||
tag_manager_panel: PanelModal | None = None
|
||||
color_manager_panel: TagColorManager | None = None
|
||||
file_extension_panel: PanelModal | None = None
|
||||
tag_search_panel: TagSearchPanel | None = None
|
||||
add_tag_modal: PanelModal | None = None
|
||||
folders_modal: FoldersToTagsModal
|
||||
about_modal: AboutModal
|
||||
unlinked_modal: FixUnlinkedEntriesModal
|
||||
dupe_modal: FixDupeFilesModal
|
||||
applied_theme: Theme
|
||||
|
||||
lib: Library
|
||||
|
||||
def __init__(self, args):
|
||||
def __init__(self, args: Namespace):
|
||||
super().__init__()
|
||||
# prevent recursive badges update when multiple items selected
|
||||
self.badge_update_lock = False
|
||||
self.lib = Library()
|
||||
self.rm: ResourceManager = ResourceManager()
|
||||
self.args = args
|
||||
self.filter = FilterState.show_all()
|
||||
self.frame_content: list[int] = [] # List of Entry IDs on the current page
|
||||
self.pages_count = 0
|
||||
self.applied_theme = None
|
||||
|
||||
self.scrollbar_pos = 0
|
||||
self.thumb_size = 128
|
||||
@@ -177,35 +184,43 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
self.SIGTERM.connect(self.handle_sigterm)
|
||||
|
||||
self.config_path = ""
|
||||
if self.args.config_file:
|
||||
path = Path(self.args.config_file)
|
||||
if not path.exists():
|
||||
logger.warning("[Config] Config File does not exist creating", path=path)
|
||||
logger.info("[Config] Using Config File", path=path)
|
||||
self.settings = QSettings(str(path), QSettings.Format.IniFormat)
|
||||
self.config_path = str(path)
|
||||
self.global_settings_path = DEFAULT_GLOBAL_SETTINGS_PATH
|
||||
if self.args.settings_file:
|
||||
self.global_settings_path = Path(self.args.settings_file)
|
||||
else:
|
||||
self.settings = QSettings(
|
||||
logger.info("[Settings] Global Settings File Path not specified, using default")
|
||||
self.settings = GlobalSettings.read_settings(self.global_settings_path)
|
||||
if not self.global_settings_path.exists():
|
||||
logger.warning(
|
||||
"[Settings] Global Settings File does not exist creating",
|
||||
path=self.global_settings_path,
|
||||
)
|
||||
self.filter = FilterState.show_all(page_size=self.settings.page_size)
|
||||
|
||||
if self.args.cache_file:
|
||||
path = Path(self.args.cache_file)
|
||||
if not path.exists():
|
||||
logger.warning("[Cache] Cache File does not exist creating", path=path)
|
||||
logger.info("[Cache] Using Cache File", path=path)
|
||||
self.cached_values = QSettings(str(path), QSettings.Format.IniFormat)
|
||||
else:
|
||||
self.cached_values = QSettings(
|
||||
QSettings.Format.IniFormat,
|
||||
QSettings.Scope.UserScope,
|
||||
"TagStudio",
|
||||
"TagStudio",
|
||||
)
|
||||
logger.info(
|
||||
"[Config] Config File not specified, using default one",
|
||||
filename=self.settings.fileName(),
|
||||
"[Cache] Cache File not specified, using default one",
|
||||
filename=self.cached_values.fileName(),
|
||||
)
|
||||
self.config_path = self.settings.fileName()
|
||||
|
||||
Translations.change_language(
|
||||
str(self.settings.value(SettingItems.LANGUAGE, defaultValue="en", type=str))
|
||||
)
|
||||
Translations.change_language(self.settings.language)
|
||||
|
||||
# NOTE: This should be a per-library setting rather than an application setting.
|
||||
thumb_cache_size_limit: int = int(
|
||||
str(
|
||||
self.settings.value(
|
||||
self.cached_values.value(
|
||||
SettingItems.THUMB_CACHE_SIZE_LIMIT,
|
||||
defaultValue=CacheManager.size_limit,
|
||||
type=int,
|
||||
@@ -214,8 +229,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
|
||||
CacheManager.size_limit = thumb_cache_size_limit
|
||||
self.settings.setValue(SettingItems.THUMB_CACHE_SIZE_LIMIT, CacheManager.size_limit)
|
||||
self.settings.sync()
|
||||
self.cached_values.setValue(SettingItems.THUMB_CACHE_SIZE_LIMIT, CacheManager.size_limit)
|
||||
self.cached_values.sync()
|
||||
logger.info(
|
||||
f"[Config] Thumbnail cache size limit: {format_size(CacheManager.size_limit)}",
|
||||
)
|
||||
@@ -254,16 +269,24 @@ class QtDriver(DriverMixin, QObject):
|
||||
def start(self) -> None:
|
||||
"""Launch the main Qt window."""
|
||||
_ = QUiLoader()
|
||||
if os.name == "nt":
|
||||
sys.argv += ["-platform", "windows:darkmode=2"]
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setStyle("Fusion")
|
||||
if self.settings.theme == Theme.SYSTEM and platform.system() == "Windows":
|
||||
sys.argv += ["-platform", "windows:darkmode=2"]
|
||||
self.app = QApplication(sys.argv)
|
||||
self.app.setStyle("Fusion")
|
||||
if self.settings.theme == Theme.SYSTEM:
|
||||
# TODO: detect theme instead of always setting dark
|
||||
self.app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
|
||||
else:
|
||||
self.app.styleHints().setColorScheme(
|
||||
Qt.ColorScheme.Dark if self.settings.theme == Theme.DARK else Qt.ColorScheme.Light
|
||||
)
|
||||
self.applied_theme = self.settings.theme
|
||||
|
||||
if (
|
||||
platform.system() == "Darwin" or platform.system() == "Windows"
|
||||
) and QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark:
|
||||
pal: QPalette = app.palette()
|
||||
pal: QPalette = self.app.palette()
|
||||
pal.setColor(QPalette.ColorGroup.Normal, QPalette.ColorRole.Window, QColor("#1e1e1e"))
|
||||
pal.setColor(QPalette.ColorGroup.Normal, QPalette.ColorRole.Button, QColor("#1e1e1e"))
|
||||
pal.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Window, QColor("#232323"))
|
||||
@@ -272,7 +295,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
QPalette.ColorGroup.Inactive, QPalette.ColorRole.ButtonText, QColor("#666666")
|
||||
)
|
||||
|
||||
app.setPalette(pal)
|
||||
self.app.setPalette(pal)
|
||||
|
||||
# Handle OS signals
|
||||
self.setup_signals()
|
||||
@@ -301,15 +324,15 @@ class QtDriver(DriverMixin, QObject):
|
||||
appid = "cyanvoxel.tagstudio.9"
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) # type: ignore[attr-defined,unused-ignore]
|
||||
|
||||
app.setApplicationName("tagstudio")
|
||||
app.setApplicationDisplayName("TagStudio")
|
||||
self.app.setApplicationName("tagstudio")
|
||||
self.app.setApplicationDisplayName("TagStudio")
|
||||
if platform.system() != "Darwin":
|
||||
fallback_icon = QIcon()
|
||||
fallback_icon.addFile(str(self.rm.get_path("icon")))
|
||||
app.setWindowIcon(QIcon.fromTheme("tagstudio", fallback_icon))
|
||||
self.app.setWindowIcon(QIcon.fromTheme("tagstudio", fallback_icon))
|
||||
|
||||
if platform.system() != "Windows":
|
||||
app.setDesktopFileName("tagstudio")
|
||||
self.app.setDesktopFileName("tagstudio")
|
||||
|
||||
# Initialize the Tag Manager panel
|
||||
self.tag_manager_panel = PanelModal(
|
||||
@@ -391,12 +414,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
open_on_start_action = QAction(Translations["settings.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)
|
||||
)
|
||||
open_on_start_action.setChecked(self.settings.open_last_loaded_on_startup)
|
||||
|
||||
def set_open_last_loaded_on_startup(checked: bool):
|
||||
self.settings.open_last_loaded_on_startup = checked
|
||||
self.settings.save()
|
||||
|
||||
open_on_start_action.triggered.connect(set_open_last_loaded_on_startup)
|
||||
file_menu.addAction(open_on_start_action)
|
||||
|
||||
file_menu.addSeparator()
|
||||
@@ -536,23 +560,19 @@ class QtDriver(DriverMixin, QObject):
|
||||
edit_menu.addAction(self.color_manager_action)
|
||||
|
||||
# View Menu ============================================================
|
||||
show_libs_list_action = QAction(Translations["settings.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 = QAction(Translations["settings.show_recent_libraries"], menu_bar)
|
||||
# show_libs_list_action.setCheckable(True)
|
||||
# show_libs_list_action.setChecked(self.settings.show_library_list)
|
||||
|
||||
def on_show_filenames_action(checked: bool):
|
||||
self.settings.show_filenames_in_grid = checked
|
||||
self.settings.save()
|
||||
self.show_grid_filenames(checked)
|
||||
|
||||
show_filenames_action = QAction(Translations["settings.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),
|
||||
)
|
||||
)
|
||||
show_filenames_action.setChecked(self.settings.show_filenames_in_grid)
|
||||
show_filenames_action.triggered.connect(on_show_filenames_action)
|
||||
view_menu.addAction(show_filenames_action)
|
||||
|
||||
# Tools Menu ===========================================================
|
||||
@@ -619,7 +639,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
# Help Menu ============================================================
|
||||
def create_about_modal():
|
||||
if not hasattr(self, "about_modal"):
|
||||
self.about_modal = AboutModal(self.config_path)
|
||||
self.about_modal = AboutModal(self.global_settings_path)
|
||||
self.about_modal.show()
|
||||
|
||||
self.about_action = QAction(Translations["menu.help.about"], menu_bar)
|
||||
@@ -665,14 +685,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
]
|
||||
self.item_thumbs: list[ItemThumb] = []
|
||||
self.thumb_renderers: list[ThumbRenderer] = []
|
||||
self.filter = FilterState.show_all()
|
||||
self.filter = FilterState.show_all(page_size=self.settings.page_size)
|
||||
self.init_library_window()
|
||||
self.migration_modal: JsonMigrationModal = None
|
||||
|
||||
path_result = self.evaluate_path(str(self.args.open).lstrip().rstrip())
|
||||
if path_result.success and path_result.library_path:
|
||||
self.open_library(path_result.library_path)
|
||||
elif self.settings.value(SettingItems.START_LOAD_LAST):
|
||||
elif self.settings.open_last_loaded_on_startup:
|
||||
# evaluate_path() with argument 'None' returns a LibraryStatus for the last library
|
||||
path_result = self.evaluate_path(None)
|
||||
if path_result.success and path_result.library_path:
|
||||
@@ -682,7 +702,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
if not which(FFMPEG_CMD) or not which(FFPROBE_CMD):
|
||||
FfmpegChecker().show()
|
||||
|
||||
app.exec()
|
||||
self.app.exec()
|
||||
self.shutdown()
|
||||
|
||||
def show_error_message(self, error_name: str, error_desc: str | None = None):
|
||||
@@ -712,7 +732,9 @@ class QtDriver(DriverMixin, QObject):
|
||||
def _filter_items():
|
||||
try:
|
||||
self.filter_items(
|
||||
FilterState.from_search_query(self.main_window.searchField.text())
|
||||
FilterState.from_search_query(
|
||||
self.main_window.searchField.text(), page_size=self.settings.page_size
|
||||
)
|
||||
.with_sorting_mode(self.sorting_mode)
|
||||
.with_sorting_direction(self.sorting_direction)
|
||||
)
|
||||
@@ -826,15 +848,15 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.statusbar.showMessage(Translations["status.library_closing"])
|
||||
start_time = time.time()
|
||||
|
||||
self.settings.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir))
|
||||
self.settings.sync()
|
||||
self.cached_values.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir))
|
||||
self.cached_values.sync()
|
||||
|
||||
# Reset library state
|
||||
self.preview_panel.update_widgets()
|
||||
self.main_window.searchField.setText("")
|
||||
scrollbar: QScrollArea = self.main_window.scrollArea
|
||||
scrollbar.verticalScrollBar().setValue(0)
|
||||
self.filter = FilterState.show_all()
|
||||
self.filter = FilterState.show_all(page_size=self.settings.page_size)
|
||||
|
||||
self.lib.close()
|
||||
|
||||
@@ -925,7 +947,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
"""Set the selection to all visible items."""
|
||||
self.selected.clear()
|
||||
for item in self.item_thumbs:
|
||||
if item.mode and item.item_id not in self.selected:
|
||||
if item.mode and item.item_id not in self.selected and not item.isHidden():
|
||||
self.selected.append(item.item_id)
|
||||
item.thumb_button.set_selected(True)
|
||||
|
||||
@@ -1300,30 +1322,33 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.frame_content[grid_idx] = None
|
||||
self.item_thumbs[grid_idx].hide()
|
||||
|
||||
def _update_thumb_count(self):
|
||||
missing_count = max(0, self.filter.page_size - len(self.item_thumbs))
|
||||
layout = self.flow_container.layout()
|
||||
for _ in range(missing_count):
|
||||
item_thumb = ItemThumb(
|
||||
None,
|
||||
self.lib,
|
||||
self,
|
||||
(self.thumb_size, self.thumb_size),
|
||||
self.settings.show_filenames_in_grid,
|
||||
)
|
||||
|
||||
layout.addWidget(item_thumb)
|
||||
self.item_thumbs.append(item_thumb)
|
||||
|
||||
def _init_thumb_grid(self):
|
||||
layout = FlowLayout()
|
||||
layout.enable_grid_optimizations(value=True)
|
||||
layout.setSpacing(min(self.thumb_size // 10, 12))
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# TODO - init after library is loaded, it can have different page_size
|
||||
for _ in range(self.filter.page_size):
|
||||
item_thumb = ItemThumb(
|
||||
None,
|
||||
self.lib,
|
||||
self,
|
||||
(self.thumb_size, self.thumb_size),
|
||||
bool(
|
||||
self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool)
|
||||
),
|
||||
)
|
||||
|
||||
layout.addWidget(item_thumb)
|
||||
self.item_thumbs.append(item_thumb)
|
||||
|
||||
self.flow_container: QWidget = QWidget()
|
||||
self.flow_container.setObjectName("flowContainer")
|
||||
self.flow_container.setLayout(layout)
|
||||
|
||||
self._update_thumb_count()
|
||||
|
||||
sa: QScrollArea = self.main_window.scrollArea
|
||||
sa.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
sa.setWidgetResizable(True)
|
||||
@@ -1538,6 +1563,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
def update_thumbs(self):
|
||||
"""Update search thumbnails."""
|
||||
self._update_thumb_count()
|
||||
# start_time = time.time()
|
||||
# logger.info(f'Current Page: {self.cur_page_idx}, Stack Length:{len(self.nav_stack)}')
|
||||
with self.thumb_job_queue.mutex:
|
||||
@@ -1721,22 +1747,22 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
|
||||
def remove_recent_library(self, item_key: str):
|
||||
self.settings.beginGroup(SettingItems.LIBS_LIST)
|
||||
self.settings.remove(item_key)
|
||||
self.settings.endGroup()
|
||||
self.settings.sync()
|
||||
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
|
||||
self.cached_values.remove(item_key)
|
||||
self.cached_values.endGroup()
|
||||
self.cached_values.sync()
|
||||
|
||||
def update_libs_list(self, path: Path | str):
|
||||
"""Add library to list in SettingItems.LIBS_LIST."""
|
||||
item_limit: int = 5
|
||||
path = Path(path)
|
||||
|
||||
self.settings.beginGroup(SettingItems.LIBS_LIST)
|
||||
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
|
||||
|
||||
all_libs = {str(time.time()): str(path)}
|
||||
|
||||
for item_key in self.settings.allKeys():
|
||||
item_path = str(self.settings.value(item_key, type=str))
|
||||
for item_key in self.cached_values.allKeys():
|
||||
item_path = str(self.cached_values.value(item_key, type=str))
|
||||
if Path(item_path) != path:
|
||||
all_libs[item_key] = item_path
|
||||
|
||||
@@ -1744,26 +1770,21 @@ class QtDriver(DriverMixin, QObject):
|
||||
all_libs_list = sorted(all_libs.items(), key=lambda item: item[0], reverse=True)
|
||||
|
||||
# remove previously saved items
|
||||
self.settings.remove("")
|
||||
self.cached_values.remove("")
|
||||
|
||||
for item_key, item_value in all_libs_list[:item_limit]:
|
||||
self.settings.setValue(item_key, item_value)
|
||||
self.cached_values.setValue(item_key, item_value)
|
||||
|
||||
self.settings.endGroup()
|
||||
self.settings.sync()
|
||||
self.cached_values.endGroup()
|
||||
self.cached_values.sync()
|
||||
self.update_recent_lib_menu()
|
||||
|
||||
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 = self.cached_values
|
||||
settings.beginGroup(SettingItems.LIBS_LIST)
|
||||
for item_tstamp in settings.allKeys():
|
||||
val = str(settings.value(item_tstamp, type=str))
|
||||
@@ -1780,10 +1801,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
for library_key in libs_sorted:
|
||||
path = Path(library_key[1][0])
|
||||
action = QAction(self.open_recent_library_menu)
|
||||
if filepath_option == ShowFilepathOption.SHOW_FULL_PATHS:
|
||||
if self.settings.show_filepath == ShowFilepathOption.SHOW_FULL_PATHS:
|
||||
action.setText(str(path))
|
||||
else:
|
||||
action.setText(str(Path(path).name))
|
||||
action.setText(str(path.name))
|
||||
action.triggered.connect(lambda checked=False, p=path: self.open_library(p))
|
||||
actions.append(action)
|
||||
|
||||
@@ -1811,40 +1832,20 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
def clear_recent_libs(self):
|
||||
"""Clear the list of recent libraries from the settings file."""
|
||||
settings = self.settings
|
||||
settings = self.cached_values
|
||||
settings.beginGroup(SettingItems.LIBS_LIST)
|
||||
self.settings.remove("")
|
||||
self.settings.endGroup()
|
||||
self.settings.sync()
|
||||
self.cached_values.remove("")
|
||||
self.cached_values.endGroup()
|
||||
self.cached_values.sync()
|
||||
self.update_recent_lib_menu()
|
||||
|
||||
def open_settings_modal(self):
|
||||
# TODO: Implement a proper settings panel, and don't re-create it each time it's opened.
|
||||
settings_panel = SettingsPanel(self)
|
||||
modal = PanelModal(
|
||||
widget=settings_panel,
|
||||
done_callback=lambda: self.update_language_settings(settings_panel.get_language()),
|
||||
has_save=False,
|
||||
)
|
||||
modal.setTitle(Translations["settings.title"])
|
||||
modal.setWindowTitle(Translations["settings.title"])
|
||||
modal.show()
|
||||
|
||||
def update_language_settings(self, language: str):
|
||||
Translations.change_language(language)
|
||||
|
||||
self.settings.setValue(SettingItems.LANGUAGE, language)
|
||||
self.settings.sync()
|
||||
SettingsPanel.build_modal(self).show()
|
||||
|
||||
def open_library(self, path: Path) -> None:
|
||||
"""Open a TagStudio library."""
|
||||
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
|
||||
path if self.settings.show_filepath == 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)
|
||||
@@ -1885,19 +1886,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
self.init_workers()
|
||||
|
||||
self.filter.page_size = self.lib.prefs(LibraryPrefs.PAGE_SIZE)
|
||||
self.filter.page_size = self.settings.page_size
|
||||
|
||||
# TODO - make this call optional
|
||||
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:
|
||||
if self.settings.show_filepath == ShowFilepathOption.SHOW_FULL_PATHS:
|
||||
library_dir_display = self.lib.library_dir
|
||||
else:
|
||||
library_dir_display = self.lib.library_dir.name
|
||||
|
||||
@@ -116,7 +116,7 @@ class ItemThumb(FlowWidget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: ItemType,
|
||||
mode: ItemType | None,
|
||||
library: Library,
|
||||
driver: "QtDriver",
|
||||
thumb_size: tuple[int, int],
|
||||
@@ -124,7 +124,7 @@ class ItemThumb(FlowWidget):
|
||||
):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.mode: ItemType = mode
|
||||
self.mode: ItemType | None = mode
|
||||
self.driver = driver
|
||||
self.item_id: int | None = None
|
||||
self.thumb_size: tuple[int, int] = thumb_size
|
||||
|
||||
@@ -17,7 +17,7 @@ from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.enums import SettingItems, ShowFilepathOption, Theme
|
||||
from tagstudio.core.enums import 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
|
||||
@@ -144,16 +144,13 @@ class FileAttributes(QWidget):
|
||||
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:
|
||||
if self.driver.settings.show_filepath == ShowFilepathOption.SHOW_FULL_PATHS:
|
||||
display_path = filepath
|
||||
elif filepath_option == ShowFilepathOption.SHOW_RELATIVE_PATHS:
|
||||
elif self.driver.settings.show_filepath == ShowFilepathOption.SHOW_RELATIVE_PATHS:
|
||||
display_path = Path(filepath).relative_to(self.library_path)
|
||||
elif filepath_option == ShowFilepathOption.SHOW_FILENAMES_ONLY:
|
||||
elif self.driver.settings.show_filepath == ShowFilepathOption.SHOW_FILENAMES_ONLY:
|
||||
display_path = Path(filepath.name)
|
||||
|
||||
self.layout().setSpacing(6)
|
||||
|
||||
@@ -67,7 +67,9 @@ class TagBoxWidget(FieldWidget):
|
||||
tag_widget.search_for_tag_action.triggered.connect(
|
||||
lambda checked=False, tag_id=tag.id: (
|
||||
self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"),
|
||||
self.driver.filter_items(FilterState.from_tag_id(tag_id)),
|
||||
self.driver.filter_items(
|
||||
FilterState.from_tag_id(tag_id, page_size=self.driver.settings.page_size)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ from PySide6.QtMultimediaWidgets import QGraphicsVideoItem
|
||||
from PySide6.QtSvgWidgets import QSvgWidget
|
||||
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView
|
||||
|
||||
from tagstudio.core.enums import SettingItems
|
||||
from tagstudio.qt.helpers.file_opener import FileOpenerHelper
|
||||
from tagstudio.qt.platform_strings import open_file_str
|
||||
from tagstudio.qt.translations import Translations
|
||||
@@ -112,9 +111,7 @@ class VideoPlayer(QGraphicsView):
|
||||
autoplay_action = QAction(Translations["media_player.autoplay"], self)
|
||||
autoplay_action.setCheckable(True)
|
||||
self.addAction(autoplay_action)
|
||||
autoplay_action.setChecked(
|
||||
self.driver.settings.value(SettingItems.AUTOPLAY, defaultValue=True, type=bool)
|
||||
)
|
||||
autoplay_action.setChecked(self.driver.settings.autoplay)
|
||||
autoplay_action.triggered.connect(lambda: self.toggle_autoplay())
|
||||
self.autoplay = autoplay_action
|
||||
|
||||
@@ -133,8 +130,8 @@ class VideoPlayer(QGraphicsView):
|
||||
|
||||
def toggle_autoplay(self) -> None:
|
||||
"""Toggle the autoplay state of the video."""
|
||||
self.driver.settings.setValue(SettingItems.AUTOPLAY, self.autoplay.isChecked())
|
||||
self.driver.settings.sync()
|
||||
self.driver.settings.autoplay = self.autoplay.isChecked()
|
||||
self.driver.settings.save()
|
||||
|
||||
def check_media_status(self, media_status: QMediaPlayer.MediaStatus) -> None:
|
||||
if media_status == QMediaPlayer.MediaStatus.EndOfMedia:
|
||||
|
||||
@@ -221,11 +221,18 @@
|
||||
"select.all": "Alle auswählen",
|
||||
"select.clear": "Auswahl leeren",
|
||||
"settings.clear_thumb_cache.title": "Vorschaubild-Zwischenspeicher leeren",
|
||||
"settings.global": "Globale Einstellungen",
|
||||
"settings.language": "Sprache",
|
||||
"settings.library": "Bibliothekseinstellungen",
|
||||
"settings.open_library_on_start": "Bibliothek zum Start öffnen",
|
||||
"settings.page_size": "Elemente pro Seite",
|
||||
"settings.restart_required": "Bitte TagStudio neustarten, um Änderungen anzuwenden.",
|
||||
"settings.show_filenames_in_grid": "Dateinamen in Raster darstellen",
|
||||
"settings.show_recent_libraries": "Zuletzt verwendete Bibliotheken anzeigen",
|
||||
"settings.theme.dark": "Dunkel",
|
||||
"settings.theme.label": "Design:",
|
||||
"settings.theme.light": "Hell",
|
||||
"settings.theme.system": "System",
|
||||
"settings.title": "Einstellungen",
|
||||
"sorting.direction.ascending": "Aufsteigend",
|
||||
"sorting.direction.descending": "Absteigend",
|
||||
|
||||
@@ -231,11 +231,18 @@
|
||||
"settings.filepath.option.full": "Show Full Paths",
|
||||
"settings.filepath.option.name": "Show Filenames Only",
|
||||
"settings.filepath.option.relative": "Show Relative Paths",
|
||||
"settings.global": "Global Settings",
|
||||
"settings.language": "Language",
|
||||
"settings.library": "Library Settings",
|
||||
"settings.open_library_on_start": "Open Library on Start",
|
||||
"settings.page_size": "Page Size",
|
||||
"settings.restart_required": "Please restart TagStudio for changes to take effect.",
|
||||
"settings.show_filenames_in_grid": "Show Filenames in Grid",
|
||||
"settings.show_recent_libraries": "Show Recent Libraries",
|
||||
"settings.theme.dark": "Dark",
|
||||
"settings.theme.label": "Theme:",
|
||||
"settings.theme.light": "Light",
|
||||
"settings.theme.system": "System",
|
||||
"settings.title": "Settings",
|
||||
"sorting.direction.ascending": "Ascending",
|
||||
"sorting.direction.descending": "Descending",
|
||||
|
||||
@@ -134,13 +134,15 @@ def qt_driver(qtbot, library):
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
|
||||
class Args:
|
||||
config_file = Path(tmp_dir) / "tagstudio.ini"
|
||||
settings_file = Path(tmp_dir) / "settings.toml"
|
||||
cache_file = Path(tmp_dir) / "tagstudio.ini"
|
||||
open = Path(tmp_dir)
|
||||
ci = True
|
||||
|
||||
with patch("tagstudio.qt.ts_qt.Consumer"), patch("tagstudio.qt.ts_qt.CustomRunnable"):
|
||||
driver = QtDriver(Args())
|
||||
|
||||
driver.app = Mock()
|
||||
driver.main_window = Mock()
|
||||
driver.preview_panel = Mock()
|
||||
driver.flow_container = Mock()
|
||||
|
||||
@@ -28,5 +28,5 @@ def test_refresh_missing_files(library: Library):
|
||||
assert list(registry.fix_unlinked_entries()) == [0, 1]
|
||||
|
||||
# `bar.md` should be relinked to new correct path
|
||||
results = library.search_library(FilterState.from_path("bar.md"))
|
||||
results = library.search_library(FilterState.from_path("bar.md", page_size=500))
|
||||
assert results[0].path == Path("bar.md")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -7,10 +7,13 @@ from PySide6.QtGui import (
|
||||
QAction,
|
||||
)
|
||||
from PySide6.QtWidgets import QMenu, QMenuBar
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from tagstudio.core.enums import SettingItems, ShowFilepathOption
|
||||
from tagstudio.core.library.alchemy.library import LibraryStatus
|
||||
from tagstudio.core.enums import ShowFilepathOption
|
||||
from tagstudio.core.library.alchemy.library import Library, LibraryStatus
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.qt.modals.settings_panel import SettingsPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.widgets.preview_panel import PreviewPanel
|
||||
|
||||
|
||||
@@ -23,7 +26,7 @@ from tagstudio.qt.widgets.preview_panel import PreviewPanel
|
||||
ShowFilepathOption.SHOW_FILENAMES_ONLY.value,
|
||||
],
|
||||
)
|
||||
def test_filepath_setting(qtbot, qt_driver, filepath_option):
|
||||
def test_filepath_setting(qtbot: QtBot, qt_driver: QtDriver, filepath_option: ShowFilepathOption):
|
||||
settings_panel = SettingsPanel(qt_driver)
|
||||
qtbot.addWidget(settings_panel)
|
||||
|
||||
@@ -31,10 +34,10 @@ def test_filepath_setting(qtbot, qt_driver, filepath_option):
|
||||
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()
|
||||
settings_panel.update_settings(qt_driver)
|
||||
|
||||
# Assert the setting is applied
|
||||
assert qt_driver.settings.value(SettingItems.SHOW_FILEPATH) == filepath_option
|
||||
assert qt_driver.settings.show_filepath == filepath_option
|
||||
|
||||
|
||||
# Tests to see if the file paths are being displayed correctly
|
||||
@@ -43,41 +46,47 @@ def test_filepath_setting(qtbot, qt_driver, filepath_option):
|
||||
[
|
||||
(
|
||||
ShowFilepathOption.SHOW_FULL_PATHS,
|
||||
lambda library: pathlib.Path(library.library_dir / "one/two/bar.md"),
|
||||
lambda library: 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")),
|
||||
(ShowFilepathOption.SHOW_RELATIVE_PATHS, lambda _: Path("one/two/bar.md")),
|
||||
(ShowFilepathOption.SHOW_FILENAMES_ONLY, lambda _: Path("bar.md")),
|
||||
],
|
||||
)
|
||||
def test_file_path_display(qt_driver, library, filepath_option, expected_path):
|
||||
def test_file_path_display(
|
||||
qt_driver: QtDriver, library: Library, filepath_option: ShowFilepathOption, 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))
|
||||
qt_driver.settings.show_filepath = filepath_option
|
||||
|
||||
# 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>"
|
||||
# Apply the mock value
|
||||
entry = library.get_entry(2)
|
||||
assert isinstance(entry, Entry)
|
||||
filename = entry.path
|
||||
assert library.library_dir is not None
|
||||
panel.file_attrs.update_stats(filepath=library.library_dir / filename)
|
||||
|
||||
# Assert the file path is displayed correctly
|
||||
assert panel.file_attrs.file_label.text() == file_str
|
||||
# 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(
|
||||
@@ -97,9 +106,9 @@ def test_file_path_display(qt_driver, library, filepath_option, expected_path):
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_title_update(qtbot, qt_driver, filepath_option, expected_title):
|
||||
def test_title_update(qt_driver: QtDriver, filepath_option: ShowFilepathOption, expected_title):
|
||||
base_title = qt_driver.base_title
|
||||
test_path = pathlib.Path("/dev/null")
|
||||
test_path = Path("/dev/null")
|
||||
open_status = LibraryStatus(
|
||||
success=True,
|
||||
library_path=test_path,
|
||||
@@ -107,7 +116,7 @@ def test_title_update(qtbot, qt_driver, filepath_option, expected_title):
|
||||
msg_description="",
|
||||
)
|
||||
# Set the file path option
|
||||
qt_driver.settings.setValue(SettingItems.SHOW_FILEPATH, filepath_option)
|
||||
qt_driver.settings.show_filepath = filepath_option
|
||||
menu_bar = QMenuBar()
|
||||
|
||||
qt_driver.open_recent_library_menu = QMenu(menu_bar)
|
||||
@@ -124,7 +133,7 @@ def test_title_update(qtbot, qt_driver, filepath_option, expected_title):
|
||||
qt_driver.folders_to_tags_action = QAction(menu_bar)
|
||||
|
||||
# Trigger the update
|
||||
qt_driver.init_library(pathlib.Path(test_path), open_status)
|
||||
qt_driver.init_library(test_path, open_status)
|
||||
|
||||
# Assert the title is updated correctly
|
||||
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(test_path, base_title))
|
||||
|
||||
28
tests/qt/test_global_settings.py
Normal file
28
tests/qt/test_global_settings.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from tagstudio.core.global_settings import GlobalSettings, Theme
|
||||
|
||||
|
||||
def test_read_settings():
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
settings_path = Path(tmp_dir) / "settings.toml"
|
||||
with open(settings_path, "a") as settings_file:
|
||||
settings_file.write("""
|
||||
language = "de"
|
||||
open_last_loaded_on_startup = true
|
||||
autoplay = true
|
||||
show_filenames_in_grid = true
|
||||
page_size = 1337
|
||||
show_filepath = 0
|
||||
dark_mode = 2
|
||||
""")
|
||||
|
||||
settings = GlobalSettings.read_settings(settings_path)
|
||||
assert settings.language == "de"
|
||||
assert settings.open_last_loaded_on_startup
|
||||
assert settings.autoplay
|
||||
assert settings.show_filenames_in_grid
|
||||
assert settings.page_size == 1337
|
||||
assert settings.show_filepath == 0
|
||||
assert settings.theme == Theme.SYSTEM
|
||||
@@ -1,7 +1,12 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import FilterState
|
||||
from tagstudio.core.library.json.library import ItemType
|
||||
from tagstudio.qt.widgets.item_thumb import ItemThumb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
# def test_update_thumbs(qt_driver):
|
||||
# qt_driver.frame_content = [
|
||||
# Entry(
|
||||
@@ -61,7 +66,7 @@ from tagstudio.qt.widgets.item_thumb import ItemThumb
|
||||
# assert qt_driver.selected == [0, 1, 2]
|
||||
|
||||
|
||||
def test_library_state_update(qt_driver):
|
||||
def test_library_state_update(qt_driver: "QtDriver"):
|
||||
# Given
|
||||
for entry in qt_driver.lib.get_entries(with_joins=True):
|
||||
thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100))
|
||||
@@ -73,7 +78,7 @@ def test_library_state_update(qt_driver):
|
||||
assert len(qt_driver.frame_content) == 2
|
||||
|
||||
# filter by tag
|
||||
state = FilterState.from_tag_name("foo").with_page_size(10)
|
||||
state = FilterState.from_tag_name("foo", page_size=10)
|
||||
qt_driver.filter_items(state)
|
||||
assert qt_driver.filter.page_size == 10
|
||||
assert len(qt_driver.frame_content) == 1
|
||||
@@ -88,7 +93,7 @@ def test_library_state_update(qt_driver):
|
||||
assert list(entry.tags)[0].name == "foo"
|
||||
|
||||
# When state property is changed, previous one is overwritten
|
||||
state = FilterState.from_path("*bar.md")
|
||||
state = FilterState.from_path("*bar.md", page_size=qt_driver.settings.page_size)
|
||||
qt_driver.filter_items(state)
|
||||
assert len(qt_driver.frame_content) == 1
|
||||
entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0])
|
||||
|
||||
@@ -7,18 +7,19 @@ from PySide6.QtCore import QSettings
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.driver import DriverMixin
|
||||
from tagstudio.core.enums import SettingItems
|
||||
from tagstudio.core.global_settings import GlobalSettings
|
||||
from tagstudio.core.library.alchemy.library import LibraryStatus
|
||||
|
||||
|
||||
class TestDriver(DriverMixin):
|
||||
def __init__(self, settings):
|
||||
def __init__(self, settings: GlobalSettings, cache: QSettings):
|
||||
self.settings = settings
|
||||
self.cached_values = cache
|
||||
|
||||
|
||||
def test_evaluate_path_empty():
|
||||
# Given
|
||||
settings = QSettings()
|
||||
driver = TestDriver(settings)
|
||||
driver = TestDriver(GlobalSettings(), QSettings())
|
||||
|
||||
# When
|
||||
result = driver.evaluate_path(None)
|
||||
@@ -29,8 +30,7 @@ def test_evaluate_path_empty():
|
||||
|
||||
def test_evaluate_path_missing():
|
||||
# Given
|
||||
settings = QSettings()
|
||||
driver = TestDriver(settings)
|
||||
driver = TestDriver(GlobalSettings(), QSettings())
|
||||
|
||||
# When
|
||||
result = driver.evaluate_path("/0/4/5/1/")
|
||||
@@ -41,9 +41,9 @@ def test_evaluate_path_missing():
|
||||
|
||||
def test_evaluate_path_last_lib_not_exists():
|
||||
# Given
|
||||
settings = QSettings()
|
||||
settings.setValue(SettingItems.LAST_LIBRARY, "/0/4/5/1/")
|
||||
driver = TestDriver(settings)
|
||||
cache = QSettings()
|
||||
cache.setValue(SettingItems.LAST_LIBRARY, "/0/4/5/1/")
|
||||
driver = TestDriver(GlobalSettings(), cache)
|
||||
|
||||
# When
|
||||
result = driver.evaluate_path(None)
|
||||
@@ -55,13 +55,16 @@ def test_evaluate_path_last_lib_not_exists():
|
||||
def test_evaluate_path_last_lib_present():
|
||||
# Given
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
settings_file = tmpdir + "/test_settings.ini"
|
||||
settings = QSettings(settings_file, QSettings.Format.IniFormat)
|
||||
settings.setValue(SettingItems.LAST_LIBRARY, tmpdir)
|
||||
settings.sync()
|
||||
cache_file = tmpdir + "/test_settings.ini"
|
||||
cache = QSettings(cache_file, QSettings.Format.IniFormat)
|
||||
cache.setValue(SettingItems.LAST_LIBRARY, tmpdir)
|
||||
cache.sync()
|
||||
|
||||
settings = GlobalSettings()
|
||||
settings.open_last_loaded_on_startup = True
|
||||
|
||||
makedirs(Path(tmpdir) / TS_FOLDER_NAME)
|
||||
driver = TestDriver(settings)
|
||||
driver = TestDriver(settings, cache)
|
||||
|
||||
# When
|
||||
result = driver.evaluate_path(None)
|
||||
|
||||
@@ -10,7 +10,7 @@ from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag
|
||||
|
||||
|
||||
def test_library_add_alias(library, generate_tag):
|
||||
def test_library_add_alias(library: Library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
@@ -19,50 +19,64 @@ def test_library_add_alias(library, generate_tag):
|
||||
alias_names: set[str] = set()
|
||||
alias_names.add("test_alias")
|
||||
library.update_tag(tag, parent_ids, alias_names, alias_ids)
|
||||
alias_ids = library.get_tag(tag.id).alias_ids
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag is not None
|
||||
alias_ids = set(tag.alias_ids)
|
||||
|
||||
assert len(alias_ids) == 1
|
||||
|
||||
|
||||
def test_library_get_alias(library, generate_tag):
|
||||
def test_library_get_alias(library: Library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
parent_ids: set[int] = set()
|
||||
alias_ids: set[int] = set()
|
||||
alias_ids: list[int] = []
|
||||
alias_names: set[str] = set()
|
||||
alias_names.add("test_alias")
|
||||
library.update_tag(tag, parent_ids, alias_names, alias_ids)
|
||||
alias_ids = library.get_tag(tag.id).alias_ids
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag is not None
|
||||
alias_ids = tag.alias_ids
|
||||
|
||||
assert library.get_alias(tag.id, alias_ids[0]).name == "test_alias"
|
||||
alias = library.get_alias(tag.id, alias_ids[0])
|
||||
assert alias is not None
|
||||
assert alias.name == "test_alias"
|
||||
|
||||
|
||||
def test_library_update_alias(library, generate_tag):
|
||||
tag: Tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
def test_library_update_alias(library: Library, generate_tag):
|
||||
tag: Tag | None = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag is not None
|
||||
|
||||
parent_ids: set[int] = set()
|
||||
alias_ids: set[int] = set()
|
||||
alias_ids: list[int] = []
|
||||
alias_names: set[str] = set()
|
||||
alias_names.add("test_alias")
|
||||
library.update_tag(tag, parent_ids, alias_names, alias_ids)
|
||||
alias_ids = library.get_tag(tag.id).alias_ids
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag is not None
|
||||
alias_ids = tag.alias_ids
|
||||
|
||||
assert library.get_alias(tag.id, alias_ids[0]).name == "test_alias"
|
||||
alias = library.get_alias(tag.id, alias_ids[0])
|
||||
assert alias is not None
|
||||
assert alias.name == "test_alias"
|
||||
|
||||
alias_names.remove("test_alias")
|
||||
alias_names.add("alias_update")
|
||||
library.update_tag(tag, parent_ids, alias_names, alias_ids)
|
||||
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag is not None
|
||||
assert len(tag.alias_ids) == 1
|
||||
assert library.get_alias(tag.id, tag.alias_ids[0]).name == "alias_update"
|
||||
alias = library.get_alias(tag.id, tag.alias_ids[0])
|
||||
assert alias is not None
|
||||
assert alias.name == "alias_update"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
def test_library_add_file(library):
|
||||
def test_library_add_file(library: Library):
|
||||
"""Check Entry.path handling for insert vs lookup"""
|
||||
assert library.folder is not None
|
||||
|
||||
entry = Entry(
|
||||
path=Path("bar.txt"),
|
||||
@@ -75,7 +89,7 @@ def test_library_add_file(library):
|
||||
assert library.has_path_entry(entry.path)
|
||||
|
||||
|
||||
def test_create_tag(library, generate_tag):
|
||||
def test_create_tag(library: Library, generate_tag):
|
||||
# tag already exists
|
||||
assert not library.add_tag(generate_tag("foo", id=1000))
|
||||
|
||||
@@ -85,10 +99,11 @@ def test_create_tag(library, generate_tag):
|
||||
assert tag.id == 123
|
||||
|
||||
tag_inc = library.add_tag(generate_tag("yyy"))
|
||||
assert tag_inc is not None
|
||||
assert tag_inc.id > 1000
|
||||
|
||||
|
||||
def test_tag_self_parent(library, generate_tag):
|
||||
def test_tag_self_parent(library: Library, generate_tag):
|
||||
# tag already exists
|
||||
assert not library.add_tag(generate_tag("foo", id=1000))
|
||||
|
||||
@@ -97,24 +112,25 @@ def test_tag_self_parent(library, generate_tag):
|
||||
assert tag
|
||||
assert tag.id == 123
|
||||
|
||||
library.update_tag(tag, {tag.id}, {}, {})
|
||||
library.update_tag(tag, {tag.id}, [], [])
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag is not None
|
||||
assert len(tag.parent_ids) == 0
|
||||
|
||||
|
||||
def test_library_search(library, generate_tag, entry_full):
|
||||
def test_library_search(library: Library, generate_tag, entry_full):
|
||||
assert library.entries_count == 2
|
||||
tag = list(entry_full.tags)[0]
|
||||
|
||||
results = library.search_library(
|
||||
FilterState.from_tag_name(tag.name),
|
||||
FilterState.from_tag_name(tag.name, page_size=500),
|
||||
)
|
||||
|
||||
assert results.total_count == 1
|
||||
assert len(results) == 1
|
||||
|
||||
|
||||
def test_tag_search(library):
|
||||
def test_tag_search(library: Library):
|
||||
tag = library.tags[0]
|
||||
|
||||
assert library.search_tags(tag.name.lower())
|
||||
@@ -130,24 +146,26 @@ def test_get_entry(library: Library, entry_min):
|
||||
assert len(result.tags) == 1
|
||||
|
||||
|
||||
def test_entries_count(library):
|
||||
def test_entries_count(library: Library):
|
||||
assert library.folder is not None
|
||||
entries = [Entry(path=Path(f"{x}.txt"), folder=library.folder, fields=[]) for x in range(10)]
|
||||
new_ids = library.add_entries(entries)
|
||||
assert len(new_ids) == 10
|
||||
|
||||
results = library.search_library(FilterState.show_all().with_page_size(5))
|
||||
results = library.search_library(FilterState.show_all(page_size=5))
|
||||
|
||||
assert results.total_count == 12
|
||||
assert len(results) == 5
|
||||
|
||||
|
||||
def test_parents_add(library, generate_tag):
|
||||
def test_parents_add(library: Library, generate_tag):
|
||||
# Given
|
||||
tag: Tag = library.tags[0]
|
||||
tag: Tag | None = library.tags[0]
|
||||
assert tag.id is not None
|
||||
|
||||
parent_tag = generate_tag("parent_tag_01")
|
||||
parent_tag = library.add_tag(parent_tag)
|
||||
assert parent_tag is not None
|
||||
assert parent_tag.id is not None
|
||||
|
||||
# When
|
||||
@@ -156,10 +174,11 @@ def test_parents_add(library, generate_tag):
|
||||
# Then
|
||||
assert tag.id is not None
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag is not None
|
||||
assert tag.parent_ids
|
||||
|
||||
|
||||
def test_remove_tag(library, generate_tag):
|
||||
def test_remove_tag(library: Library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("food", id=123))
|
||||
|
||||
assert tag
|
||||
@@ -171,7 +190,7 @@ def test_remove_tag(library, generate_tag):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_exclude", [True, False])
|
||||
def test_search_filter_extensions(library, is_exclude):
|
||||
def test_search_filter_extensions(library: Library, is_exclude: bool):
|
||||
# Given
|
||||
entries = list(library.get_entries())
|
||||
assert len(entries) == 2, entries
|
||||
@@ -181,7 +200,7 @@ def test_search_filter_extensions(library, is_exclude):
|
||||
|
||||
# When
|
||||
results = library.search_library(
|
||||
FilterState.show_all(),
|
||||
FilterState.show_all(page_size=500),
|
||||
)
|
||||
|
||||
# Then
|
||||
@@ -192,7 +211,7 @@ def test_search_filter_extensions(library, is_exclude):
|
||||
assert (entry.path.suffix == ".txt") == is_exclude
|
||||
|
||||
|
||||
def test_search_library_case_insensitive(library):
|
||||
def test_search_library_case_insensitive(library: Library):
|
||||
# Given
|
||||
entries = list(library.get_entries(with_joins=True))
|
||||
assert len(entries) == 2, entries
|
||||
@@ -202,7 +221,7 @@ def test_search_library_case_insensitive(library):
|
||||
|
||||
# When
|
||||
results = library.search_library(
|
||||
FilterState.from_tag_name(tag.name.upper()),
|
||||
FilterState.from_tag_name(tag.name.upper(), page_size=500),
|
||||
)
|
||||
|
||||
# Then
|
||||
@@ -212,12 +231,12 @@ def test_search_library_case_insensitive(library):
|
||||
assert results[0].id == entry.id
|
||||
|
||||
|
||||
def test_preferences(library):
|
||||
def test_preferences(library: Library):
|
||||
for pref in LibraryPrefs:
|
||||
assert library.prefs(pref) == pref.default
|
||||
|
||||
|
||||
def test_remove_entry_field(library, entry_full):
|
||||
def test_remove_entry_field(library: Library, entry_full):
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
library.remove_entry_field(title_field, [entry_full.id])
|
||||
@@ -226,7 +245,7 @@ def test_remove_entry_field(library, entry_full):
|
||||
assert not entry.text_fields
|
||||
|
||||
|
||||
def test_remove_field_entry_with_multiple_field(library, entry_full):
|
||||
def test_remove_field_entry_with_multiple_field(library: Library, entry_full):
|
||||
# Given
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
@@ -242,7 +261,7 @@ def test_remove_field_entry_with_multiple_field(library, entry_full):
|
||||
assert len(entry.text_fields) == 1
|
||||
|
||||
|
||||
def test_update_entry_field(library, entry_full):
|
||||
def test_update_entry_field(library: Library, entry_full):
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
library.update_entry_field(
|
||||
@@ -255,7 +274,7 @@ def test_update_entry_field(library, entry_full):
|
||||
assert entry.text_fields[0].value == "new value"
|
||||
|
||||
|
||||
def test_update_entry_with_multiple_identical_fields(library, entry_full):
|
||||
def test_update_entry_with_multiple_identical_fields(library: Library, entry_full):
|
||||
# Given
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
@@ -278,6 +297,7 @@ def test_update_entry_with_multiple_identical_fields(library, entry_full):
|
||||
|
||||
def test_mirror_entry_fields(library: Library, entry_full):
|
||||
# new entry
|
||||
assert library.folder is not None
|
||||
target_entry = Entry(
|
||||
folder=library.folder,
|
||||
path=Path("xxx"),
|
||||
@@ -295,12 +315,14 @@ def test_mirror_entry_fields(library: Library, entry_full):
|
||||
|
||||
# get new entry from library
|
||||
new_entry = library.get_entry_full(entry_id)
|
||||
assert new_entry is not None
|
||||
|
||||
# mirror fields onto new entry
|
||||
library.mirror_entry_fields(new_entry, entry_full)
|
||||
|
||||
# get new entry from library again
|
||||
entry = library.get_entry_full(entry_id)
|
||||
assert entry is not None
|
||||
|
||||
# make sure fields are there after getting it from the library again
|
||||
assert len(entry.fields) == 2
|
||||
@@ -311,6 +333,7 @@ def test_mirror_entry_fields(library: Library, entry_full):
|
||||
|
||||
|
||||
def test_merge_entries(library: Library):
|
||||
assert library.folder is not None
|
||||
a = Entry(
|
||||
folder=library.folder,
|
||||
path=Path("a"),
|
||||
@@ -327,10 +350,14 @@ def test_merge_entries(library: Library):
|
||||
try:
|
||||
ids = library.add_entries([a, b])
|
||||
entry_a = library.get_entry_full(ids[0])
|
||||
assert entry_a is not None
|
||||
entry_b = library.get_entry_full(ids[1])
|
||||
assert entry_b is not None
|
||||
tag_0 = library.add_tag(Tag(id=1000, name="tag_0"))
|
||||
tag_1 = library.add_tag(Tag(id=1001, name="tag_1"))
|
||||
assert tag_1 is not None
|
||||
tag_2 = library.add_tag(Tag(id=1002, name="tag_2"))
|
||||
assert tag_2 is not None
|
||||
library.add_tags_to_entries(ids[0], [tag_0.id, tag_2.id])
|
||||
library.add_tags_to_entries(ids[1], [tag_1.id])
|
||||
library.merge_entries(entry_a, entry_b)
|
||||
@@ -345,7 +372,7 @@ def test_merge_entries(library: Library):
|
||||
AssertionError()
|
||||
|
||||
|
||||
def test_remove_tags_from_entries(library, entry_full):
|
||||
def test_remove_tags_from_entries(library: Library, entry_full):
|
||||
removed_tag_id = -1
|
||||
for tag in entry_full.tags:
|
||||
removed_tag_id = tag.id
|
||||
@@ -370,7 +397,7 @@ def test_search_entry_id(library: Library, query_name: int, has_result):
|
||||
assert (result is not None) == has_result
|
||||
|
||||
|
||||
def test_update_field_order(library, entry_full):
|
||||
def test_update_field_order(library: Library, entry_full):
|
||||
# Given
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
@@ -416,98 +443,100 @@ def test_library_prefs_multiple_identical_vals():
|
||||
|
||||
|
||||
def test_path_search_ilike(library: Library):
|
||||
results = library.search_library(FilterState.from_path("bar.md"))
|
||||
results = library.search_library(FilterState.from_path("bar.md", page_size=500))
|
||||
assert results.total_count == 1
|
||||
assert len(results.items) == 1
|
||||
|
||||
|
||||
def test_path_search_like(library: Library):
|
||||
results = library.search_library(FilterState.from_path("BAR.MD"))
|
||||
results = library.search_library(FilterState.from_path("BAR.MD", page_size=500))
|
||||
assert results.total_count == 0
|
||||
assert len(results.items) == 0
|
||||
|
||||
|
||||
def test_path_search_default_with_sep(library: Library):
|
||||
results = library.search_library(FilterState.from_path("one/two"))
|
||||
results = library.search_library(FilterState.from_path("one/two", page_size=500))
|
||||
assert results.total_count == 1
|
||||
assert len(results.items) == 1
|
||||
|
||||
|
||||
def test_path_search_glob_after(library: Library):
|
||||
results = library.search_library(FilterState.from_path("foo*"))
|
||||
results = library.search_library(FilterState.from_path("foo*", page_size=500))
|
||||
assert results.total_count == 1
|
||||
assert len(results.items) == 1
|
||||
|
||||
|
||||
def test_path_search_glob_in_front(library: Library):
|
||||
results = library.search_library(FilterState.from_path("*bar.md"))
|
||||
results = library.search_library(FilterState.from_path("*bar.md", page_size=500))
|
||||
assert results.total_count == 1
|
||||
assert len(results.items) == 1
|
||||
|
||||
|
||||
def test_path_search_glob_both_sides(library: Library):
|
||||
results = library.search_library(FilterState.from_path("*one/two*"))
|
||||
results = library.search_library(FilterState.from_path("*one/two*", page_size=500))
|
||||
assert results.total_count == 1
|
||||
assert len(results.items) == 1
|
||||
|
||||
|
||||
def test_path_search_ilike_glob_equality(library: Library):
|
||||
results_ilike = library.search_library(FilterState.from_path("one/two"))
|
||||
results_glob = library.search_library(FilterState.from_path("*one/two*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("one/two", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*one/two*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
results_ilike = library.search_library(FilterState.from_path("bar.md"))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar.md*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("bar.md", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar.md*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
results_ilike = library.search_library(FilterState.from_path("bar"))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("bar", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
results_ilike = library.search_library(FilterState.from_path("bar.md"))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar.md*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("bar.md", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar.md*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
|
||||
def test_path_search_like_glob_equality(library: Library):
|
||||
results_ilike = library.search_library(FilterState.from_path("ONE/two"))
|
||||
results_glob = library.search_library(FilterState.from_path("*ONE/two*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("ONE/two", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*ONE/two*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
results_ilike = library.search_library(FilterState.from_path("BAR.MD"))
|
||||
results_glob = library.search_library(FilterState.from_path("*BAR.MD*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("BAR.MD", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*BAR.MD*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
results_ilike = library.search_library(FilterState.from_path("BAR.MD"))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar.md*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("BAR.MD", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*bar.md*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] != [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
results_ilike = library.search_library(FilterState.from_path("bar.md"))
|
||||
results_glob = library.search_library(FilterState.from_path("*BAR.MD*"))
|
||||
results_ilike = library.search_library(FilterState.from_path("bar.md", page_size=500))
|
||||
results_glob = library.search_library(FilterState.from_path("*BAR.MD*", page_size=500))
|
||||
assert [e.id for e in results_ilike.items] != [e.id for e in results_glob.items]
|
||||
results_ilike, results_glob = None, None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("md", 1), ("txt", 1), ("png", 0)])
|
||||
def test_filetype_search(library, filetype, num_of_filetype):
|
||||
results = library.search_library(FilterState.from_filetype(filetype))
|
||||
def test_filetype_search(library: Library, filetype, num_of_filetype):
|
||||
results = library.search_library(FilterState.from_filetype(filetype, page_size=500))
|
||||
assert len(results.items) == num_of_filetype
|
||||
|
||||
|
||||
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("png", 2), ("apng", 1), ("ng", 0)])
|
||||
def test_filetype_return_one_filetype(file_mediatypes_library, filetype, num_of_filetype):
|
||||
results = file_mediatypes_library.search_library(FilterState.from_filetype(filetype))
|
||||
def test_filetype_return_one_filetype(file_mediatypes_library: Library, filetype, num_of_filetype):
|
||||
results = file_mediatypes_library.search_library(
|
||||
FilterState.from_filetype(filetype, page_size=500)
|
||||
)
|
||||
assert len(results.items) == num_of_filetype
|
||||
|
||||
|
||||
@pytest.mark.parametrize(["mediatype", "num_of_mediatype"], [("plaintext", 2), ("image", 0)])
|
||||
def test_mediatype_search(library, mediatype, num_of_mediatype):
|
||||
results = library.search_library(FilterState.from_mediatype(mediatype))
|
||||
def test_mediatype_search(library: Library, mediatype, num_of_mediatype):
|
||||
results = library.search_library(FilterState.from_mediatype(mediatype, page_size=500))
|
||||
assert len(results.items) == num_of_mediatype
|
||||
|
||||
@@ -6,7 +6,7 @@ from tagstudio.core.query_lang.util import ParsingError
|
||||
|
||||
|
||||
def verify_count(lib: Library, query: str, count: int):
|
||||
results = lib.search_library(FilterState.from_search_query(query))
|
||||
results = lib.search_library(FilterState.from_search_query(query, page_size=500))
|
||||
assert results.total_count == count
|
||||
assert len(results.items) == count
|
||||
|
||||
@@ -136,4 +136,4 @@ def test_parent_tags(search_library: Library, query: str, count: int):
|
||||
)
|
||||
def test_syntax(search_library: Library, invalid_query: str):
|
||||
with pytest.raises(ParsingError) as e_info: # noqa: F841
|
||||
search_library.search_library(FilterState.from_search_query(invalid_query))
|
||||
search_library.search_library(FilterState.from_search_query(invalid_query, page_size=500))
|
||||
|
||||
Reference in New Issue
Block a user