Source code for pymodaq_gui.managers.parameter_manager

import numbers
from pathlib import Path
from typing import List, Union, Dict, Optional, Tuple, Any

from qtpy import QtWidgets, QtCore
from pymodaq_gui.managers.action_manager import ActionManager
from pymodaq_gui.parameter import Parameter, ParameterTree, ioxml, utils
from pymodaq_gui.utils.file_io import select_file
from pymodaq_utils.config import get_set_config_dir

from pymodaq_utils.logger import set_logger, get_module_name


logger = set_logger(get_module_name(__file__))


[docs] class ParameterTreeWidget(ActionManager): def __init__(self, action_list: tuple = ('save', 'update', 'load')): super().__init__() self.widget = QtWidgets.QWidget() self.widget.setLayout(QtWidgets.QVBoxLayout()) toolbar = QtWidgets.QToolBar() self.set_toolbar(toolbar) self.tree: ParameterTree = ParameterTree() self.widget.header = self.tree.header # for back-compatibility, widget behave a bit like a ParameterTree self.widget.listAllItems = self.tree.listAllItems # for back-compatibility #self.tree.setMinimumWidth(150) #self.tree.setMinimumHeight(300) # Making the buttons self.setup_actions(action_list) # Making the splitter self.splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) # Adding the toolbar + the parameter tree self.splitter.addWidget(toolbar) self.splitter.addWidget(self.tree) # Hiding toolbar self.splitter.setSizes([0, 300]) # Adding splitter to layout self.widget.layout().addWidget(self.splitter) self.widget.layout().setContentsMargins(0, 0, 0, 0)
[docs] def setup_actions(self, action_list: tuple = ('save', 'update', 'load')): """ See Also -------- ActionManager.add_action """ # Saving action self.add_action('save_settings', 'Save Settings', 'saveTree', "Save current settings in an xml file", visible = 'save' in action_list) # Update action self.add_action('update_settings', 'Update Settings', 'updateTree', "Update the settings from an xml file, the settings structure loaded must be identical to the current one", visible = 'update' in action_list) # Load action self.add_action('load_settings', 'Load Settings', 'openTree', "Load current settings from an xml file, the current settings structure is erased and is replaced by the new one", visible = 'load' in action_list)
[docs] class ParameterManager: """Class dealing with Parameter and ParameterTree Attributes ---------- params: list of dicts Defining the Parameter tree like structure settings_name: str The particular name to give to the object parent Parameter (self.settings) settings: Parameter The higher level (parent) Parameter settings_tree: QWidget widget Holding a ParameterTree and a toolbar for interacting with the tree tree: ParameterTree the underlying ParameterTree """ settings_name = 'custom_settings' params = [] def __init__(self, settings_name: Optional[str] = None, action_list: tuple = ('save', 'update', 'load'), ): if settings_name is None: settings_name = self.settings_name # create a settings tree to be shown eventually in a dock # object containing the settings defined in the preamble # create a settings tree to be shown eventually in a dock self._settings_tree = ParameterTreeWidget(action_list) self._settings_tree.get_action(f'save_settings').connect_to(self.save_settings_slot) self._settings_tree.get_action(f'update_settings').connect_to(self.update_settings_slot) self._settings_tree.get_action(f'load_settings').connect_to(self.load_settings_slot) self.settings = Parameter.create(name=settings_name, type='group', children=self.params, showTop=False) # create a Parameter # object containing the settings defined in the preamble self._settings_tree.tree.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) @property def settings_tree(self): return self._settings_tree.widget @property def tree(self): return self._settings_tree.tree @property def settings(self) -> Parameter: return self._settings @settings.setter def settings(self, settings: Union[Parameter, List[Dict[str, str]], Path]): settings = self.create_parameter(settings) self._settings = settings self.tree.setParameters(self._settings, showTop=False) # load the tree with this parameter object self._settings.sigTreeStateChanged.connect(self.parameter_tree_changed)
[docs] @staticmethod def create_parameter(settings: Union[Parameter, List[Dict[str, str]], Path]) -> Parameter: if isinstance(settings, List): _settings = Parameter.create(title='Settings', name='settings', type='group', children=settings, showTop=False) elif isinstance(settings, Path) or isinstance(settings, str): settings = Path(settings) _settings = Parameter.create(title='Settings', name='settings', type='group', showTop=False, children=ioxml.XML_file_to_parameter(str(settings))) elif isinstance(settings, Parameter): _settings = Parameter.create(title='Settings', name=settings.name(), type='group', showTop=False) _settings.restoreState(settings.saveState()) else: raise TypeError(f'Cannot create Parameter object from {settings}') return _settings
[docs] def parameter_tree_changed(self, param, changes): for param, change, data in changes: path = self._settings.childPath(param) if change == 'childAdded': self.child_added(param, data) elif change == 'value': self.value_changed(param) elif change == 'parent': self.param_deleted(param) elif change == 'options': self.options_changed(param, data) elif change == 'limits': self.limits_changed(param, data)
[docs] def value_changed(self, param: Parameter): """Non-mandatory method to be subclassed for actions to perform (methods to call) when one of the param's value in self._settings is changed For this to be triggered, the Parameter method: **setValue** should be used Parameters ---------- param: Parameter the parameter whose value just changed Examples -------- >>> if param.name() == 'do_something': >>> if param.value(): >>> print('Do something') >>> self.settings.child('main_settings', 'something_done').setValue(False) """ ...
[docs] def child_added(self, param: Parameter, data: Parameter): """Non-mandatory method to be subclassed for actions to perform when a param has been added in the attribute settings For this to be triggered, one of the Parameter methods: **addChild**, **addChildren** or **insertChildren** should be used Parameters ---------- param: Parameter the parameter where child will be added data: Parameter the child parameter """ pass
[docs] def param_deleted(self, param: Parameter): """Non-mandatory method to be subclassed for actions to perform when one of the param in self.settings has been deleted For this to be triggered, the Parameter method: **removeChild** should be used Parameters ---------- param: Parameter the parameter that has been deleted """ pass
[docs] def options_changed(self, param: Parameter, data: Dict[str, Any]): """Non-mandatory method to be subclassed for actions to perform when one of the options of any parameter in the settings attribute has been changed. For this to be triggered, the Parameter method: **setOpts** should be used Parameters ---------- param: Parameter the parameter chose option has been changed data: dict the key is the string of the changed option the value depend on the type of option """ pass
[docs] def limits_changed(self, param: Parameter, data: Tuple[numbers.Number, numbers.Number]): """Non-mandatory method to be subclassed for actions to perform when the limits of any parameter in the *settings* attribute has been changed For this to be triggered, the Parameter method: **setLimits** should be used Parameters ---------- param: Parameter the parameter chose option has been changed data: tuple of numbers tuple of float or int depending on the parameter type. For peculiar Parameter, could be a tuple of some other objects """ pass
[docs] def save_settings_slot(self, file_path: Path = None): """ Method to save the current settings using a xml file extension. The starting directory is the user config folder with a subfolder called settings folder Parameters ---------- file_path: Path Path like object pointing to a xml file encoding a Parameter object If None, opens a file explorer window to save manually a file """ if file_path is None or file_path is False: file_path = select_file(get_set_config_dir('settings', user=True), save=True, ext='xml', filter='*.xml', force_save_extension=True) else: file_path = Path(file_path) if '.xml' != file_path.suffix: return if file_path: ioxml.parameter_to_xml_file(self.settings, file_path.resolve()) logger.info(f'The settings have been successfully saved at {file_path}')
def _get_settings_from_file(self): return select_file(get_set_config_dir('settings', user=True), save=False, ext='xml', filter='*.xml', force_save_extension=True)
[docs] def load_settings_slot(self, file_path: Path = None): """ Method to load settings into the parameter using a xml file extension. The starting directory is the user config folder with a subfolder called settings folder Parameters ---------- file_path: Path Path like object pointing to a xml file encoding a Parameter object If None, opens a file explorer window to pick manually a file """ if file_path is None or file_path is False: file_path = self._get_settings_from_file() if file_path: self.settings = file_path.resolve() logger.info(f'The settings from {file_path} have been successfully loaded')
[docs] def update_settings_slot(self, file_path: Path = None): """ Method to update settings using a xml file extension. The file should define the same settings structure (names and children) The starting directory is the user config folder with a subfolder called settings folder Parameters ---------- file_path: Path Path like object pointing to a xml file encoding a Parameter object If None, opens a file explorer window to pick manually a file """ if file_path is None or file_path is False: file_path = self._get_settings_from_file() if file_path: _settings = self.create_parameter(file_path.resolve()) # Checking if both parameters have the same structure sameStruct = utils.compareStructureParameter(self.settings,_settings) if sameStruct: # Update if true self.settings = _settings logger.info(f'The settings from {file_path} have been successfully applied') else: logger.info(f'The loaded settings from {file_path} do not match the current settings structure and cannot be applied.')