Skip to content

Commit defe2a5

Browse files
committed
begin adding settings menu
1 parent c33d020 commit defe2a5

File tree

8 files changed

+151
-1
lines changed

8 files changed

+151
-1
lines changed

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ SQLAlchemy==2.0.34
1616
structlog==24.4.0
1717
typing_extensions>=3.10.0.0,<=4.11.0
1818
ujson>=5.8.0,<=5.9.0
19-
vtf2img==0.1.0
19+
vtf2img==0.1.0
20+
toml==0.10.2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__all__ = ["tssettings"]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from pathlib import Path
2+
3+
import toml
4+
from pydantic import BaseModel, Field
5+
6+
7+
# NOTE: pydantic also has a BaseSettings class (from pydantic-settings) that allows any settings
8+
# properties to be overwritten with environment variables. as tagstudio is not currently using
9+
# environment variables, i did not base it on that, but that may be useful in the future.
10+
class TSSettings(BaseModel):
11+
dark_mode: bool = Field(default=False)
12+
language: str = Field(default="en-US")
13+
14+
@staticmethod
15+
def read_settings(path: Path | str, **kwargs) -> "TSSettings":
16+
# library = kwargs.get("library")
17+
settings_data: dict[str, any] = dict()
18+
if path.exists():
19+
with open(path, "rb").read() as filecontents:
20+
if len(filecontents.strip()) != 0:
21+
settings_data = toml.loads(filecontents.decode("utf-8"))
22+
23+
# if library: #TODO: add library-specific settings
24+
# lib_settings_path = Path(library.folder / "settings.toml")
25+
# lib_settings_data: dict[str, any]
26+
# if lib_settings_path.exists:
27+
# with open(lib_settings_path, "rb") as filedata:
28+
# lib_settings_data = tomllib.load(filedata)
29+
# lib_settings = TSSettings(**lib_settings_data)
30+
31+
return TSSettings(**settings_data)
32+
33+
def to_dict(self) -> dict[str, any]:
34+
d = dict[str, any]()
35+
for prop_name, prop_value in self:
36+
d[prop_name] = prop_value
37+
38+
return d
39+
40+
def save(self, path: Path | str) -> None:
41+
with open(path, "w") as f:
42+
toml.dump(self.to_dict(), f)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import copy
2+
3+
from PySide6.QtWidgets import (
4+
QCheckBox,
5+
QComboBox,
6+
QHBoxLayout,
7+
QLabel,
8+
QVBoxLayout,
9+
)
10+
from src.core.settings import TSSettings
11+
from src.qt.widgets.panel import PanelWidget
12+
13+
14+
class SettingsModal(PanelWidget):
15+
def __init__(self, settings: TSSettings):
16+
super().__init__()
17+
self.tempSettings = copy.deepcopy(settings)
18+
19+
self.main = QVBoxLayout(self)
20+
21+
# ---
22+
self.darkMode_Label = QLabel()
23+
self.darkMode_Value = QCheckBox()
24+
self.darkMode_Row = QHBoxLayout()
25+
self.darkMode_Row.addWidget(self.darkMode_Label)
26+
self.darkMode_Row.addWidget(self.darkMode_Value)
27+
28+
self.darkMode_Label.setText("Dark Mode")
29+
self.darkMode_Value.setChecked(self.tempSettings.dark_mode)
30+
31+
self.darkMode_Value.stateChanged.connect(
32+
lambda state: self.set_property("dark_mode", bool(state))
33+
)
34+
35+
# ---
36+
self.language_Label = QLabel()
37+
self.language_Value = QComboBox()
38+
self.language_Row = QHBoxLayout()
39+
self.language_Row.addWidget(self.language_Label)
40+
self.language_Row.addWidget(self.language_Value)
41+
42+
self.language_Label.setText("Language")
43+
language_list = [ # TODO: put this somewhere else
44+
"en-US",
45+
"en-GB",
46+
"es-MX",
47+
# etc...
48+
]
49+
self.language_Value.addItems(language_list)
50+
self.language_Value.setCurrentIndex(language_list.index(self.tempSettings.language))
51+
self.language_Value.currentTextChanged.connect(
52+
lambda text: self.set_property("language", text)
53+
)
54+
55+
# ---
56+
self.main.addLayout(self.darkMode_Row)
57+
self.main.addLayout(self.language_Row)
58+
59+
def set_property(self, prop_name: str, value: any) -> None:
60+
setattr(self.tempSettings, prop_name, value)
61+
62+
def get_content(self) -> TSSettings:
63+
return self.tempSettings

tagstudio/src/qt/ts_qt.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
from src.core.library.alchemy.fields import _FieldID
7878
from src.core.library.alchemy.library import Entry, LibraryStatus
7979
from src.core.media_types import MediaCategories
80+
from src.core.settings import TSSettings
8081
from src.core.ts_core import TagStudioCore
8182
from src.core.utils.refresh_dir import RefreshDirTracker
8283
from src.core.utils.web import strip_web_protocol
@@ -90,6 +91,7 @@
9091
from src.qt.modals.fix_dupes import FixDupeFilesModal
9192
from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal
9293
from src.qt.modals.folders_to_tags import FoldersToTagsModal
94+
from src.qt.modals.settings_modal import SettingsModal
9395
from src.qt.modals.tag_database import TagDatabasePanel
9496
from src.qt.resource_manager import ResourceManager
9597
from src.qt.widgets.item_thumb import BadgeType, ItemThumb
@@ -243,6 +245,13 @@ def start(self) -> None:
243245
self.main_window.dragMoveEvent = self.drag_move_event # type: ignore[method-assign]
244246
self.main_window.dropEvent = self.drop_event # type: ignore[method-assign]
245247

248+
self.settings_path = (
249+
Path.home() / ".config/TagStudio" / "settings.toml"
250+
) # TODO: put this somewhere else
251+
self.newSettings = TSSettings.read_settings(
252+
self.settings_path
253+
) # TODO: make this cross-platform
254+
246255
splash_pixmap = QPixmap(":/images/splash.png")
247256
splash_pixmap.setDevicePixelRatio(self.main_window.devicePixelRatio())
248257
self.splash = QSplashScreen(splash_pixmap, Qt.WindowType.WindowStaysOnTopHint)
@@ -324,6 +333,12 @@ def start(self) -> None:
324333
file_menu.addAction(close_library_action)
325334

326335
# Edit Menu ============================================================
336+
settings_menu_action = QAction("&Settings", menu_bar)
337+
settings_menu_action.triggered.connect(lambda: self.open_settings_menu())
338+
edit_menu.addAction(settings_menu_action)
339+
340+
edit_menu.addSeparator()
341+
327342
new_tag_action = QAction("New &Tag", menu_bar)
328343
new_tag_action.triggered.connect(lambda: self.add_tag_action_callback())
329344
new_tag_action.setShortcut(
@@ -637,6 +652,21 @@ def add_tag_action_callback(self):
637652
)
638653
self.modal.show()
639654

655+
def open_settings_menu(self):
656+
self.modal = PanelModal(
657+
SettingsModal(self.newSettings),
658+
"Settings",
659+
"Settings",
660+
has_save=True,
661+
save_callback=(lambda x: self.update_settings(x)),
662+
)
663+
664+
self.modal.show()
665+
666+
def update_settings(self, settings: TSSettings):
667+
self.newSettings = settings
668+
self.newSettings.save(self.settings_path)
669+
640670
def select_all_action_callback(self):
641671
self.selected = list(range(0, len(self.frame_content)))
642672

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dark_mode = true
2+
language = "es-MX"
Binary file not shown.

tagstudio/tests/test_settings.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import pathlib
2+
3+
from src.core.settings.tssettings import TSSettings
4+
5+
CWD = pathlib.Path(__file__)
6+
7+
8+
def test_read_settings():
9+
settings = TSSettings.read_settings(CWD.parent / "example_settings.toml")
10+
assert settings.dark_mode
11+
assert settings.language == "es-MX"

0 commit comments

Comments
 (0)