From 11d055347551dd9f59be8817276fdef31a6f6095 Mon Sep 17 00:00:00 2001 From: python357-1 Date: Sun, 15 Dec 2024 17:39:37 -0600 Subject: [PATCH] feat: begin adding settings menu --- requirements.txt | 3 +- tagstudio/src/core/settings/__init__.py | 1 + tagstudio/src/core/settings/tssettings.py | 42 ++++++++++++ tagstudio/src/qt/modals/settings_modal.py | 63 ++++++++++++++++++ tagstudio/src/qt/ts_qt.py | 30 +++++++++ tagstudio/tests/example_settings.toml | 2 + .../.TagStudio/ts_library.sqlite | Bin 86016 -> 86016 bytes tagstudio/tests/test_settings.py | 11 +++ 8 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 tagstudio/src/core/settings/__init__.py create mode 100644 tagstudio/src/core/settings/tssettings.py create mode 100644 tagstudio/src/qt/modals/settings_modal.py create mode 100644 tagstudio/tests/example_settings.toml create mode 100644 tagstudio/tests/test_settings.py diff --git a/requirements.txt b/requirements.txt index 9eb290390..a2d3d8b6e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,5 @@ SQLAlchemy==2.0.34 structlog==24.4.0 typing_extensions>=3.10.0.0,<=4.11.0 ujson>=5.8.0,<=5.9.0 -vtf2img==0.1.0 \ No newline at end of file +vtf2img==0.1.0 +toml==0.10.2 \ No newline at end of file diff --git a/tagstudio/src/core/settings/__init__.py b/tagstudio/src/core/settings/__init__.py new file mode 100644 index 000000000..32f4a7ea9 --- /dev/null +++ b/tagstudio/src/core/settings/__init__.py @@ -0,0 +1 @@ +__all__ = ["tssettings"] diff --git a/tagstudio/src/core/settings/tssettings.py b/tagstudio/src/core/settings/tssettings.py new file mode 100644 index 000000000..2d56f7303 --- /dev/null +++ b/tagstudio/src/core/settings/tssettings.py @@ -0,0 +1,42 @@ +from pathlib import Path + +import toml +from pydantic import BaseModel, Field + + +# 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 TSSettings(BaseModel): + dark_mode: bool = Field(default=False) + language: str = Field(default="en-US") + + @staticmethod + def read_settings(path: Path | str, **kwargs) -> "TSSettings": + # library = kwargs.get("library") + settings_data: dict[str, any] = dict() + if path.exists(): + with open(path, "rb").read() as filecontents: + if len(filecontents.strip()) != 0: + settings_data = toml.loads(filecontents.decode("utf-8")) + + # if library: #TODO: add library-specific settings + # lib_settings_path = Path(library.folder / "settings.toml") + # lib_settings_data: dict[str, any] + # if lib_settings_path.exists: + # with open(lib_settings_path, "rb") as filedata: + # lib_settings_data = tomllib.load(filedata) + # lib_settings = TSSettings(**lib_settings_data) + + return TSSettings(**settings_data) + + def to_dict(self) -> dict[str, any]: + d = dict[str, any]() + for prop_name, prop_value in self: + d[prop_name] = prop_value + + return d + + def save(self, path: Path | str) -> None: + with open(path, "w") as f: + toml.dump(self.to_dict(), f) diff --git a/tagstudio/src/qt/modals/settings_modal.py b/tagstudio/src/qt/modals/settings_modal.py new file mode 100644 index 000000000..f3772b7b6 --- /dev/null +++ b/tagstudio/src/qt/modals/settings_modal.py @@ -0,0 +1,63 @@ +import copy + +from PySide6.QtWidgets import ( + QCheckBox, + QComboBox, + QHBoxLayout, + QLabel, + QVBoxLayout, +) +from src.core.settings import TSSettings +from src.qt.widgets.panel import PanelWidget + + +class SettingsModal(PanelWidget): + def __init__(self, settings: TSSettings): + super().__init__() + self.tempSettings = copy.deepcopy(settings) + + self.main = QVBoxLayout(self) + + # --- + self.darkMode_Label = QLabel() + self.darkMode_Value = QCheckBox() + self.darkMode_Row = QHBoxLayout() + self.darkMode_Row.addWidget(self.darkMode_Label) + self.darkMode_Row.addWidget(self.darkMode_Value) + + self.darkMode_Label.setText("Dark Mode") + self.darkMode_Value.setChecked(self.tempSettings.dark_mode) + + self.darkMode_Value.stateChanged.connect( + lambda state: self.set_property("dark_mode", bool(state)) + ) + + # --- + self.language_Label = QLabel() + self.language_Value = QComboBox() + self.language_Row = QHBoxLayout() + self.language_Row.addWidget(self.language_Label) + self.language_Row.addWidget(self.language_Value) + + self.language_Label.setText("Language") + language_list = [ # TODO: put this somewhere else + "en-US", + "en-GB", + "es-MX", + # etc... + ] + self.language_Value.addItems(language_list) + self.language_Value.setCurrentIndex(language_list.index(self.tempSettings.language)) + self.language_Value.currentTextChanged.connect( + lambda text: self.set_property("language", text) + ) + + # --- + self.main.addLayout(self.darkMode_Row) + self.main.addLayout(self.language_Row) + + def set_property(self, prop_name: str, value: any) -> None: + setattr(self.tempSettings, prop_name, value) + + def get_content(self) -> TSSettings: + return self.tempSettings diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 0f7f2a270..908f826c0 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -77,6 +77,7 @@ from src.core.library.alchemy.fields import _FieldID from src.core.library.alchemy.library import Entry, LibraryStatus from src.core.media_types import MediaCategories +from src.core.settings import TSSettings from src.core.ts_core import TagStudioCore from src.core.utils.refresh_dir import RefreshDirTracker from src.core.utils.web import strip_web_protocol @@ -90,6 +91,7 @@ from src.qt.modals.fix_dupes import FixDupeFilesModal from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal from src.qt.modals.folders_to_tags import FoldersToTagsModal +from src.qt.modals.settings_modal import SettingsModal from src.qt.modals.tag_database import TagDatabasePanel from src.qt.resource_manager import ResourceManager from src.qt.widgets.item_thumb import BadgeType, ItemThumb @@ -243,6 +245,13 @@ def start(self) -> None: self.main_window.dragMoveEvent = self.drag_move_event # type: ignore[method-assign] self.main_window.dropEvent = self.drop_event # type: ignore[method-assign] + self.settings_path = ( + Path.home() / ".config/TagStudio" / "settings.toml" + ) # TODO: put this somewhere else + self.newSettings = TSSettings.read_settings( + self.settings_path + ) # TODO: make this cross-platform + splash_pixmap = QPixmap(":/images/splash.png") splash_pixmap.setDevicePixelRatio(self.main_window.devicePixelRatio()) self.splash = QSplashScreen(splash_pixmap, Qt.WindowType.WindowStaysOnTopHint) @@ -324,6 +333,12 @@ def start(self) -> None: file_menu.addAction(close_library_action) # Edit Menu ============================================================ + settings_menu_action = QAction("&Settings", menu_bar) + settings_menu_action.triggered.connect(lambda: self.open_settings_menu()) + edit_menu.addAction(settings_menu_action) + + edit_menu.addSeparator() + new_tag_action = QAction("New &Tag", menu_bar) new_tag_action.triggered.connect(lambda: self.add_tag_action_callback()) new_tag_action.setShortcut( @@ -637,6 +652,21 @@ def add_tag_action_callback(self): ) self.modal.show() + def open_settings_menu(self): + self.modal = PanelModal( + SettingsModal(self.newSettings), + "Settings", + "Settings", + has_save=True, + save_callback=(lambda x: self.update_settings(x)), + ) + + self.modal.show() + + def update_settings(self, settings: TSSettings): + self.newSettings = settings + self.newSettings.save(self.settings_path) + def select_all_action_callback(self): self.selected = list(range(0, len(self.frame_content))) diff --git a/tagstudio/tests/example_settings.toml b/tagstudio/tests/example_settings.toml new file mode 100644 index 000000000..ca51b1a82 --- /dev/null +++ b/tagstudio/tests/example_settings.toml @@ -0,0 +1,2 @@ +dark_mode = true +language = "es-MX" \ No newline at end of file diff --git a/tagstudio/tests/fixtures/search_library/.TagStudio/ts_library.sqlite b/tagstudio/tests/fixtures/search_library/.TagStudio/ts_library.sqlite index 449f380b02fad6ca8fbcc08dddf2ca8d99116aaf..da75a748cae76c6426ca21d168e6cc4f11cc9ff8 100644 GIT binary patch delta 37 tcmZozz}m2Yb%GQVqxnP`CpJcN2ED4njVW!?69f2~e@JitA5TdPHYUH81%}zH>R{n^KrMQvoVM=@H3R8<`x(>|B&APLz