Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: begin adding settings menu #647

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
vtf2img==0.1.0
toml==0.10.2
1 change: 1 addition & 0 deletions tagstudio/src/core/settings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__all__ = ["tssettings"]
42 changes: 42 additions & 0 deletions tagstudio/src/core/settings/tssettings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path

import toml
from pydantic import BaseModel, Field

Check failure on line 4 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Cannot find implementation or library stub for module named "pydantic" [import-not-found] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:4:1: error: Cannot find implementation or library stub for module named "pydantic" [import-not-found]


# 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()

Check failure on line 17 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Function "builtins.any" is not valid as a type [valid-type] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:17:34: error: Function "builtins.any" is not valid as a type [valid-type]
if path.exists():

Check failure on line 18 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Item "str" of "Path | str" has no attribute "exists" [union-attr] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:18:12: error: Item "str" of "Path | str" has no attribute "exists" [union-attr]
with open(path, "rb").read() as filecontents:

Check failure on line 19 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 "bytes" has no attribute "__enter__"; maybe "__iter__"? [attr-defined] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:19:18: error: "bytes" has no attribute "__enter__"; maybe "__iter__"? [attr-defined]

Check failure on line 19 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 "bytes" has no attribute "__exit__" [attr-defined] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:19:18: error: "bytes" has no attribute "__exit__" [attr-defined]
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]:

Check failure on line 33 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Function "builtins.any" is not valid as a type [valid-type] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:33:36: error: Function "builtins.any" is not valid as a type [valid-type]
d = dict[str, any]()

Check failure on line 34 in tagstudio/src/core/settings/tssettings.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Function "builtins.any" is not valid as a type [valid-type] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/core/settings/tssettings.py:34:23: error: Function "builtins.any" is not valid as a type [valid-type]
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)
63 changes: 63 additions & 0 deletions tagstudio/src/qt/modals/settings_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import copy

from PySide6.QtWidgets import (
QCheckBox,
QComboBox,
QHBoxLayout,
QLabel,
QVBoxLayout,
)
from src.core.settings import TSSettings

Check failure on line 10 in tagstudio/src/qt/modals/settings_modal.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Module "src.core.settings" has no attribute "TSSettings" [attr-defined] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/qt/modals/settings_modal.py:10:1: error: Module "src.core.settings" has no attribute "TSSettings" [attr-defined]
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:

Check failure on line 59 in tagstudio/src/qt/modals/settings_modal.py

View workflow job for this annotation

GitHub Actions / Run MyPy

[mypy] reported by reviewdog 🐶 Function "builtins.any" is not valid as a type [valid-type] Raw Output: /home/runner/work/TagStudio/TagStudio/tagstudio/src/qt/modals/settings_modal.py:59:51: error: Function "builtins.any" is not valid as a type [valid-type]
setattr(self.tempSettings, prop_name, value)

def get_content(self) -> TSSettings:
return self.tempSettings
30 changes: 30 additions & 0 deletions tagstudio/src/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)))

Expand Down
2 changes: 2 additions & 0 deletions tagstudio/tests/example_settings.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dark_mode = true
language = "es-MX"
Binary file not shown.
11 changes: 11 additions & 0 deletions tagstudio/tests/test_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pathlib

from src.core.settings.tssettings import TSSettings

CWD = pathlib.Path(__file__)


def test_read_settings():
settings = TSSettings.read_settings(CWD.parent / "example_settings.toml")
assert settings.dark_mode
assert settings.language == "es-MX"
Loading