From ac66ce99dffe35e99b34e59645630f8323097aa9 Mon Sep 17 00:00:00 2001 From: Kevin Phillips Date: Sun, 2 Aug 2020 19:17:29 -0300 Subject: [PATCH] Fixes #18 Added support for application settings --- main.pyproject | 2 +- project.prop | 2 + requirements.txt | 1 + src/friendlypics2/data/ui/about_dlg.ui | 8 ++- src/friendlypics2/data/ui/main_window.ui | 8 ++- src/friendlypics2/data/ui/settings_dlg.ui | 66 +++++++++++++++++++++++ src/friendlypics2/dialogs/about_dlg.py | 4 +- src/friendlypics2/dialogs/main_window.py | 32 +++++++---- src/friendlypics2/dialogs/settings_dlg.py | 35 ++++++++++++ src/friendlypics2/misc/app_settings.py | 52 ++++++++++++++++++ 10 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 src/friendlypics2/data/ui/settings_dlg.ui create mode 100644 src/friendlypics2/dialogs/settings_dlg.py create mode 100644 src/friendlypics2/misc/app_settings.py diff --git a/main.pyproject b/main.pyproject index 60f6b6f..463642d 100644 --- a/main.pyproject +++ b/main.pyproject @@ -1,3 +1,3 @@ { - "files": ["main.py","form.ui","src/friendlypics2/data/ui/main_window.ui","src/friendlypics2/data/ui/form.ui","pinterest_dump.ui","src/friendlypics2/data/ui/pinterest_dump.ui","src/friendlypics2/data/ui/about_dlg.ui"] + "files": ["src/friendlypics2/data/ui/form.ui","src/friendlypics2/data/ui/pinterest_dump.ui","pinterest_dump.ui","src/friendlypics2/data/ui/about_dlg.ui","src/friendlypics2/data/ui/main_window.ui","main.py","form.ui","src/friendlypics2/data/ui/settings_dlg.ui"] } diff --git a/project.prop b/project.prop index 0c3835b..4717f23 100644 --- a/project.prop +++ b/project.prop @@ -8,6 +8,8 @@ "qtpy", "pyside2", "friendlypins", + "pyyaml", + "appdirs" ], "DEV_DEPENDENCIES" : [ "pytest", diff --git a/requirements.txt b/requirements.txt index 229a2b7..9e2ed0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,6 +37,7 @@ pytest==6.0.1 pytest-cov==2.10.0 python-dateutil==2.8.1 pytz==2020.1 +PyYAML==5.3.1 QtPy==1.9.0 requests==2.24.0 shiboken2==5.15.0 diff --git a/src/friendlypics2/data/ui/about_dlg.ui b/src/friendlypics2/data/ui/about_dlg.ui index cce61c8..121588c 100644 --- a/src/friendlypics2/data/ui/about_dlg.ui +++ b/src/friendlypics2/data/ui/about_dlg.ui @@ -2,6 +2,9 @@ Dialog + + Qt::ApplicationModal + 0 @@ -11,7 +14,10 @@ - Dialog + About... + + + true diff --git a/src/friendlypics2/data/ui/main_window.ui b/src/friendlypics2/data/ui/main_window.ui index 76af844..f5206de 100644 --- a/src/friendlypics2/data/ui/main_window.ui +++ b/src/friendlypics2/data/ui/main_window.ui @@ -44,6 +44,7 @@ + @@ -101,7 +102,12 @@ true - Debug Window + &Debug Window + + + + + &Settings... diff --git a/src/friendlypics2/data/ui/settings_dlg.ui b/src/friendlypics2/data/ui/settings_dlg.ui new file mode 100644 index 0000000..72cbd52 --- /dev/null +++ b/src/friendlypics2/data/ui/settings_dlg.ui @@ -0,0 +1,66 @@ + + + settings_dialog + + + Qt::ApplicationModal + + + + 0 + 0 + 507 + 451 + + + + Settings + + + true + + + + + + + + + + + + 0 + 0 + + + + &Cancel + + + false + + + true + + + + + + + + 0 + 0 + + + + &Save + + + + + + + + + + diff --git a/src/friendlypics2/dialogs/about_dlg.py b/src/friendlypics2/dialogs/about_dlg.py index ecd8192..7796e0f 100644 --- a/src/friendlypics2/dialogs/about_dlg.py +++ b/src/friendlypics2/dialogs/about_dlg.py @@ -12,8 +12,6 @@ class AboutDialog(QDialog): def __init__(self, parent): super().__init__(parent) self._log = logging.getLogger(__name__) - self.setWindowTitle("About...") - self.setModal(True) self.settings = QSettings() self._load_ui() # Flag indicating whether the user has requested the GUI settings to be reset. @@ -21,7 +19,7 @@ def __init__(self, parent): self.cleared = False def _load_ui(self): - """Internal helper method that configures the UI for the main window""" + """Internal helper method that configures the UI for the dialog""" load_ui("about_dlg.ui", self) self.title_label = self.findChild(QLabel, "title_label") diff --git a/src/friendlypics2/dialogs/main_window.py b/src/friendlypics2/dialogs/main_window.py index c83b1cb..f4f76a5 100644 --- a/src/friendlypics2/dialogs/main_window.py +++ b/src/friendlypics2/dialogs/main_window.py @@ -7,6 +7,8 @@ from friendlypics2.misc.gui_helpers import load_ui, generate_screen_id, settings_group_context from friendlypics2.misc.app_helpers import is_mac_app_bundle from friendlypics2.dialogs.about_dlg import AboutDialog +from friendlypics2.misc.app_settings import AppSettings +from friendlypics2.dialogs.settings_dlg import SettingsDialog class MainWindow(QMainWindow): @@ -17,6 +19,9 @@ def __init__(self): self._log = logging.getLogger(__name__) self._disable_window_save = False + # Initialize app settings + self._app_settings = AppSettings() + # Initialize window self._log.debug("Initializing main window...") self.setWindowTitle("Friendly Pics") @@ -62,9 +67,12 @@ def _load_ui(self): """Internal helper method that configures the UI for the main window""" load_ui("main_window.ui", self) - self.window_debug_menu.triggered.connect(self.window_debug_click) self.file_open_menu.triggered.connect(self.file_open_click) self.file_open_menu.setShortcut(QKeySequence.Open) + self.file_settings_menu.triggered.connect(self.file_settings_click) + + self.window_debug_menu.triggered.connect(self.window_debug_click) + self.help_about_menu.triggered.connect(self.help_about_click) # Hack: for testing on MacOS we convert menu bar to non native @@ -74,14 +82,6 @@ def _load_ui(self): if not is_mac_app_bundle(): self.menuBar().setNativeMenuBar(False) - @Slot() - def window_debug_click(self): - """event handler for when the window->debug menu is clicked""" - if self.window_debug_menu.isChecked(): - self.debug_dock.show() - else: - self.debug_dock.hide() - def _load_window_state(self): """Restores window layout to it's previous state. Must be called after _load_ui""" # Load all settings for this specific window @@ -136,6 +136,20 @@ def help_about_click(self): dlg.exec_() self._disable_window_save = dlg.cleared + @Slot() + def window_debug_click(self): + """event handler for when the window->debug menu is clicked""" + if self.window_debug_menu.isChecked(): + self.debug_dock.show() + else: + self.debug_dock.hide() + + @Slot() + def file_settings_click(self): + """event handler for when the file->settings menu is clicked""" + dlg = SettingsDialog(self, self._settings) + dlg.exec_() + def closeEvent(self, event): # pylint: disable=invalid-name """event handler called when the application is about to close diff --git a/src/friendlypics2/dialogs/settings_dlg.py b/src/friendlypics2/dialogs/settings_dlg.py new file mode 100644 index 0000000..89b8614 --- /dev/null +++ b/src/friendlypics2/dialogs/settings_dlg.py @@ -0,0 +1,35 @@ +"""Logic for the application settings UI""" +import logging +from qtpy.QtWidgets import QDialog +from qtpy.QtCore import Slot, QSettings +from friendlypics2.misc.gui_helpers import load_ui + + +class SettingsDialog(QDialog): + """Logic for managing application settings dialog""" + def __init__(self, parent, settings): + super().__init__(parent) + self._settings = settings + self._log = logging.getLogger(__name__) + self.settings = QSettings() + self._load_ui() + + def _load_ui(self): + """Internal helper method that configures the UI for the dialog""" + load_ui("settings_dlg.ui", self) + + self.cancel_button.clicked.connect(self.close) + self.save_button.clicked.connect(self._save_clicked) + + # TODO: populate the settings tree view + # TODO: make a custom "model" class to render the AppSettings class + self.settings_view.add_item + + # Center the about box on the parent window + parent_geom = self.parent().geometry() + self.move(parent_geom.center() - self.rect().center()) + + @Slot() + def _save_clicked(self): + """Callback for when the user clicks the save button""" + self._log.debug("Saving") diff --git a/src/friendlypics2/misc/app_settings.py b/src/friendlypics2/misc/app_settings.py new file mode 100644 index 0000000..03538be --- /dev/null +++ b/src/friendlypics2/misc/app_settings.py @@ -0,0 +1,52 @@ +"""Interface for persisting application settings""" +import logging +from pathlib import Path +import json + +import yaml +from appdirs import user_config_dir +from friendlypics2.version import __version__ + + +class AppSettings: + """Interface for accessing and persisting application setings""" + def __init__(self): + self._log = logging.getLogger(__name__) + temp = user_config_dir("Friendly Pics 2", "The Friendly Coder", __version__) + self._filename = Path(temp).joinpath("appsettings.yml") + self._log.debug(self._filename) + + if self._filename.exists(): + self._data = yaml.safe_load(self._filename.read_text()) + else: + self._data = dict() + self._data["file_version"] = "1.0" + + def __str__(self): + return json.dumps(self._data, indent=4) + + @property + def path(self): + """Location of the config file managed by this class""" + return self._filename + + @property + def pinterest_user(self): + """str: user to authenticate with to Pinterest""" + return self._data.get("services", dict()).get("pinterest", dict()).get("username") + + @pinterest_user.setter + def pinterest_user(self, value): + # TODO: consider saving config data every time a setter is accessed + self._data["services"]["pinterest"]["username"] = value + + @property + def file_version(self): + """str: gets the schema version for the config file""" + assert "file_version" in self._data + return self._data.get("file_version") + + def save(self): + """Saves the current contents of the app settings for later reference""" + with self._filename.open("w") as config_file: + yaml.safe_dump(self._data, config_file)