# Copyright (c) 2023 Adrian Niemann Dmitry Puzyrev
#
# This file is part of RodTracker.
# RodTracker is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# RodTracker is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with RodTracker. If not, see <http://www.gnu.org/licenses/>.
"""**TBD**"""
import json
from abc import abstractmethod
from PyQt5 import QtWidgets, QtCore
from RodTracker import SETTINGS_FILE, CONFIG_DIR
[docs]class Configuration(QtCore.QObject):
"""Generic class that shall hold configurations/settings."""
_default: dict = {}
_contents: dict = {}
path: str = str(CONFIG_DIR / "configurations.json")
"""str : Path to the file for storing this configuration."""
[docs] def read(self, path: str = None):
"""Reads configurations from a file and saves them in this object.
Parameters
----------
path : str, optional
Path of the file to read. If no path is given the classes saved
path is used, this might be the default path or a programmatically
exchanged path.
Returns
-------
None
"""
if path is None:
path = self.path
with open(path, 'r') as f:
contents = json.load(f)
if contents is None:
raise FileNotFoundError
else:
for key in self._default.keys():
if key not in contents.keys():
contents[key] = self._default[key]
continue
for inner_key in self._default[key].keys():
if inner_key not in contents[key].keys():
contents[key][inner_key] = \
self._default[key][inner_key]
self._contents = contents
[docs] @abstractmethod
def reset_to_default(self):
"""Resets the configuration values to their defaults."""
pass
[docs] @abstractmethod
def show_dialog(self, parent):
"""Displays a dialog for the user to change the configuration(s)."""
pass
[docs] @classmethod
def save(cls, new_path: str = None, new_data: dict = None):
"""Saves the :attr:`contents` to a location on disk.
The saving location is determined by the objects property :attr:`path`
if the parameter is left out.
Parameters
----------
new_path : str, optional
Location to save the contents to. If this parameter is left
blank the :class:`Configuration` object's :attr:`path` property is
used.
new_data : dict, optional
Configuration data, that is supposed to be saved and therefore
replace the old configuration data.
Returns
-------
None
"""
if new_data is not None:
cls._contents = new_data
if new_path is not None:
cls.path = new_path
to_file = json.dumps(cls._contents, indent=2)
with open(cls.path, 'w') as out:
out.write(to_file)
[docs]class Settings(Configuration):
"""Holds settings for the GUI.
Parameters
----------
path : str, optional
Location and name where the settings are saved as a ``*.json`` file.
The default location is used, if no path is given.
.. admonition:: Signals
- :attr:`settings_changed`
Attributes
----------
parent : QWidget
"""
path = str(SETTINGS_FILE)
"""str : Location and name where the settings are saved as a ``*.json``
file.
"""
_default = {
"visual": {
"rod_thickness": 3,
"rod_color": [0, 255, 255],
"number_offset": 15,
"number_color": [0, 0, 0],
"number_size": 11,
"boundary_offset": 5,
"position_scaling": 1.0,
},
"data": {
"images_root": "./",
"positions_root": "./",
},
"functional": {
"rod_increment": 1.0,
},
"experiment": {
"number_rods": 25,
"box_width": 112,
"box_height": 80,
"box_depth": 80,
},
}
_contents = _default
parent: QtWidgets.QMainWindow
settings_changed = QtCore.pyqtSignal([dict], name="settings_changed")
"""pyqtSignal(dict) : Propagates settings that have potentially been
changed.
"""
def __init__(self, path: str = None):
super().__init__()
if path is not None:
self.path = path
self.read()
[docs] def read(self, path: str = None):
try:
super().read(path)
except FileNotFoundError:
if path is None:
path = self.path
if self._contents is None:
self.reset_to_default()
self.save(new_path=path)
self.send_settings()
[docs] def reset_to_default(self):
self._contents = self._default
self.send_settings()
[docs] def send_settings(self):
"""Sends the current settings as one signal per settings category.
.. hint::
**Emits**
- :attr:`settings_changed` **(potentially repeatedly)**
"""
for _, category in self._contents.items():
self.settings_changed.emit(category)
[docs] def update_field(self, category: str, field: str, value):
"""Update one of the settings and notify other objects about it.
Parameters
----------
category : str
Category of settings.
field : str
Field/setting within the `category`.
value : any
New value of the setting.
"""
self._contents[category][field] = value
self.save(new_data=self._contents)
self.send_settings()