Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5b3ad20
feat: add tab widget
Computerdores Mar 12, 2025
0c942e2
refactor: move languages dict to translations.py
Computerdores Mar 12, 2025
2403529
refactor: move build of Settings Modal to SettingsPanel class
Computerdores Mar 12, 2025
7968cff
feat: hide title label
Computerdores Mar 12, 2025
c65eec6
feat: global settings class
Computerdores Mar 12, 2025
fad74a0
fix: initialise settings
Computerdores Mar 12, 2025
a56d70c
fix: properly store grid files changes
Computerdores Mar 12, 2025
84384f4
fix: placeholder text for library settings
Computerdores Mar 12, 2025
2593b59
feat: add ui elements for remaining global settings
Computerdores Mar 12, 2025
4e7406f
feat: add page size setting
Computerdores Mar 12, 2025
feaed58
Merge branch 'main' into feat/settings_menu
Computerdores Mar 13, 2025
7f3d8ca
fix: version mismatch between pydantic and typing_extensions
Computerdores Mar 13, 2025
4aee8d2
Merge branch 'main' into feat/settings_menu
Computerdores Mar 13, 2025
e44316c
fix: update test_driver.py
Computerdores Mar 13, 2025
4ef2b16
fix(test_file_path_options): replace patch with change of settings
Computerdores Mar 13, 2025
fca2ce4
feat: setting for dark mode
Computerdores Mar 13, 2025
1aba3eb
fix: only show restart_label when necessary
Computerdores Mar 13, 2025
6f0e3ed
fix: change modal from "done" type to "Save/Cancel" type
Computerdores Mar 13, 2025
3023051
feat: add test for GlobalSettings
Computerdores Mar 13, 2025
f4621df
docs: mark roadmap item as completed
Computerdores Mar 13, 2025
76972da
fix(test_filepath_setting): Mock the app field of QtDriver
Computerdores Mar 13, 2025
9a41873
Merge branch 'main' into feat/settings_menu
CyanVoxel Mar 19, 2025
14d1e04
Update src/tagstudio/main.py
Computerdores Mar 20, 2025
28a663f
fix: address review suggestions
Computerdores Mar 20, 2025
aba2f1c
fix: page size setting
Computerdores Mar 20, 2025
eb32baa
feat: change dark mode option to theme dropdown
Computerdores Mar 20, 2025
32a2c2c
fix: test was expecting wrong behaviour
Computerdores Mar 20, 2025
66ac034
fix: test was testing for correct behaviour, fix behaviour instead
Computerdores Mar 20, 2025
740f1ec
fix: test fr fr
Computerdores Mar 20, 2025
67339d6
fix: tests fr fr fr
Computerdores Mar 20, 2025
e119b3b
fix: tests fr fr fr fr
Computerdores Mar 20, 2025
01a2bc3
fix: update test
Computerdores Mar 20, 2025
891db6d
fix: tests fr fr fr fr fr
Computerdores Mar 20, 2025
de8543e
fix: select all was selecting hidden entries
Computerdores Mar 25, 2025
0478eb4
fix: create more thumbitems as necessary
Computerdores Mar 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/updates/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
14 changes: 8 additions & 6 deletions src/tagstudio/core/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

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."""
Expand All @@ -21,17 +23,17 @@ def evaluate_path(self, open_path: str | None) -> LibraryStatus:
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

Expand Down
7 changes: 0 additions & 7 deletions src/tagstudio/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,9 @@
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):
Expand Down Expand Up @@ -81,5 +75,4 @@ class LibraryPrefs(DefaultEnum):

IS_EXCLUDE_LIST = True
EXTENSION_LIST = [".json", ".xmp", ".aae"]
PAGE_SIZE = 500
DB_VERSION = 9
70 changes: 70 additions & 0 deletions src/tagstudio/core/global_settings.py
Original file line number Diff line number Diff line change
@@ -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())
37 changes: 17 additions & 20 deletions src/tagstudio/core/library/alchemy/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -94,35 +94,32 @@ def offset(self):
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)
Expand Down
2 changes: 1 addition & 1 deletion src/tagstudio/core/utils/dupe_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def refresh_dupe_files(self, results_filepath: str | Path):
continue

results = self.library.search_library(
FilterState.from_path(path_relative),
FilterState.from_path(path_relative, page_size=500),
)

if not results:
Expand Down
19 changes: 10 additions & 9 deletions src/tagstudio/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,19 @@ def main():
type=str,
help="Path to a TagStudio Library folder to open on start.",
)
parser.add_argument(
"-s",
"--settings-file",
dest="settings_file",
type=str,
help="Path to a TagStudio .toml global settings file to use.",
)
parser.add_argument(
"-c",
"--config-file",
dest="config_file",
"--cache-file",
dest="cache_file",
type=str,
help="Path to a TagStudio .ini or .plist config file to use.",
help="Path to a TagStudio .ini or .plist cache file to use.",
)

# parser.add_argument('--browse', dest='browse', action='store_true',
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/tagstudio/qt/cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self):
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:
Expand Down
Loading