from dataclasses import dataclass
from pymodaq.control_modules.move_utility_classes import HW_SETTINGS_KEY as ACTUATOR_SETTINGS_KEY
import time
from typing import Callable, TYPE_CHECKING, Union, Tuple
from qtpy import QtWidgets, QtCore
from serializall import SerializableFactory
from pymodaq.utils.managers.modules import ModuleType
from pymodaq_gui.managers.parameter_manager import ParameterManager
from pymodaq_utils.enums import StrEnum
from pymodaq_utils.abstract import abstract_attribute
from pymodaq_utils.logger import set_logger, get_module_name
from pymodaq_gui.parameter.utils import Parameter, ParameterWithPath
from pymodaq_utils.enums import StrEnum
logger = set_logger(get_module_name(__file__))
ser_factory = SerializableFactory()
if TYPE_CHECKING:
from pymodaq_gui.managers.settings.utils import SettingsManagerModel
from pymodaq_gui.managers.settings.settings_manager import SettingsManager
from pymodaq.dashboard import DashBoard
[docs]
class SubEntryError(Exception):
pass
[docs]
class SubEntryHandlerTypes(StrEnum):
SETTINGS = 'Settings'
[docs]
@SerializableFactory.register_decorator()
@dataclass
class SubEntry:
""" Depending on the module or modules this manager handles,
the attributes below should be completed as well as the serialization/deserialization
"""
entry_type: SubEntryHandlerTypes | str
module_name: str
setting: ParameterWithPath
def __eq__(self, other: 'SubEntry'):
return (self.entry_type == other.entry_type and
self.module_name == other.module_name and
self.setting == other.setting)
def __repr__(self):
return f"SubEntry({self.entry_type}:"\
f" {self.setting.value()})"
[docs]
@staticmethod
def serialize(entry: 'SubEntry') -> bytes:
"""
"""
bytes_string = b''
bytes_string += ser_factory.get_apply_serializer(str(entry.entry_type))
bytes_string += ser_factory.get_apply_serializer(entry.setting)
bytes_string += ser_factory.get_apply_serializer(entry.module_name)
return bytes_string
[docs]
@classmethod
def deserialize(cls,
bytes_str: bytes) -> Union['SubEntry',
Tuple['SubEntry', bytes]]:
"""Convert bytes into a ParameterWithPath object
Returns
-------
ParameterWithPath: the decoded object
bytes: the remaining bytes string if any
"""
entry_type , remaining_bytes = ser_factory.get_apply_deserializer(bytes_str, False)
entry_type = entry_type
parameter_with_path, remaining_bytes = ser_factory.get_apply_deserializer(remaining_bytes, False)
module_name, remaining_bytes = ser_factory.get_apply_deserializer(remaining_bytes, False)
return SubEntry(entry_type, module_name,
parameter_with_path), remaining_bytes
[docs]
class SubEntryHandler(QtCore.QObject):
new_entry = QtCore.Signal(SubEntry)
executed_signal = QtCore.Signal(int) # to be emited when execution is done
execution_failed = QtCore.Signal(Exception)
handler_name: SubEntryHandlerTypes = abstract_attribute() # to reimplement in real dialogs
use_dialog = True
sub_entry_done = QtCore.Signal(bool)
def __init__(self,
model: 'SettingsManagerModel',
settings: Parameter,
*args,
ind_subentry: int = None,
**kwargs
):
super().__init__()
self.settings: Parameter = settings
self.model: SettingsManagerModel = model
self._ind_subentry = ind_subentry # the current index with subentries, see _execute_entry
logger.debug(f'subhandler with index {ind_subentry}')
[docs]
@staticmethod
def get_module(entry: SubEntry, *args, **kwargs) -> ParameterManager:
""" Get the ParameterManager module on which the settings will be applied
To be reimplemented
"""
raise NotImplementedError
[docs]
def show_dialog(self):
self.setup_ui()
[docs]
def setup_ui(self):
self.dialog = QtWidgets.QDialog()
vlayout = QtWidgets.QVBoxLayout()
self.widget = QtWidgets.QWidget()
self.widget.setLayout(QtWidgets.QHBoxLayout())
self.setup_widgets()
vlayout.addWidget(self.widget)
self.dialog.setLayout(vlayout)
buttonBox = QtWidgets.QDialogButtonBox(parent=self.dialog)
buttonBox.addButton("Ok", QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole)
buttonBox.accepted.connect(self.subentry_set)
buttonBox.addButton("Cancel", QtWidgets.QDialogButtonBox.ButtonRole.RejectRole)
buttonBox.rejected.connect(self.dialog.reject)
vlayout.addWidget(buttonBox)
self.dialog.setWindowTitle(f"Fill in information about this {self.handler_name}")
self.dialog.open()
[docs]
def subentry_set(self):
self.dialog.accept()
self.model.add_data(self.model.rowCount(),
self.get_subentry_from_dialog())
[docs]
def setup_widgets(self):
""" To reimplement
You can use self.widget as the container for your custom widgets!
"""
pass
[docs]
def get_subentry_from_dialog(self) -> SubEntry:
""" Get a StateSubEntry from the dialog
To be reimplemented """
raise NotImplementedError
[docs]
def execute_subentry(self, entry: SubEntry, **kwargs):
""" Execute the given subentry and emit the signal sub_entry_done when done
"""
raise NotImplementedError
[docs]
class SubEntryHandlerFactory:
"""The factory class to get SubEntry handlers"""
handlers_registry = {}
[docs]
@classmethod
def register_handler(cls) -> Callable:
"""Class decorator method to register SubEntryHandlers class to the internal
registry.
Must be used as a decorator above the definition of an SubEntryHandler inherited class.
The entry class must implement specific class attributes and methods
"""
def inner_wrapper(wrapped_class: SubEntryHandler) -> Callable:
subentry_name = wrapped_class.handler_name
if subentry_name not in cls.handlers_registry:
cls.handlers_registry[subentry_name] = wrapped_class
else:
logger.info(f"Subentry {subentry_name} already registered")
# Return wrapped_class
return wrapped_class
# Return decorated function
return inner_wrapper
[docs]
@classmethod
def get_subentry_handler(cls, subentry_name: str) -> type[SubEntryHandler]:
"""Factory command to get registered subentry handler.
This method gets the appropriate executor class from the registry
"""
if subentry_name not in cls.handlers_registry:
raise KeyError(f".{subentry_name} is not a supported entry.")
return cls.handlers_registry[subentry_name]
@property
def entries(self) -> list[str]:
return [entry for entry in self.handlers_registry.keys()]
[docs]
@SubEntryHandlerFactory.register_handler()
class SettingsEntryHandler(SubEntryHandler):
handler_name = SubEntryHandlerTypes.SETTINGS
use_dialog = False
[docs]
def execute_subentry(self, entry: SubEntry,
*args, **kwargs):
""" Execute the given subentry
In general, should get first the module on which the settings will be applied
Then apply the Settings subentry to this module
Examples
--------
::
module = self.get_module(entry, *args, **kwargs)
module.settings.child(*entry.setting.path).setValue(entry.setting.value())
"""
raise NotImplementedError