Source code for pymodaq.utils.managers.parameter_manager

from pathlib import Path
from typing import List, Union, Dict

from qtpy import QtWidgets, QtCore
from pymodaq.utils.managers.action_manager import ActionManager
from pymodaq.utils.parameter import Parameter, ParameterTree, ioxml, utils
from pymodaq.utils.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__))

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)            

        


    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: 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 = Parameter.create(name=settings_name, type='group', children=self.params) # create a Parameter # object containing the settings defined in the preamble @property def settings_tree(self): return self._settings_tree.widget @property def tree(self): return self._settings_tree.tree @property def settings(self): 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) @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) elif isinstance(settings, Path) or isinstance(settings, str): settings = Path(settings) _settings = Parameter.create(title='Settings', name='settings', type='group', children=ioxml.XML_file_to_parameter(str(settings))) elif isinstance(settings, Parameter): _settings = Parameter.create(title='Settings', name=settings.name(), type='group') _settings.restoreState(settings.saveState()) else: raise TypeError(f'Cannot create Parameter object from {settings}') return _settings 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)
[docs] def value_changed(self, param): """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 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, data): """Non-mandatory method to be subclassed for actions to perform when a param has been added in self.settings Parameters ---------- param: Parameter the parameter where child will be added data: Parameter the child parameter """ pass
[docs] def param_deleted(self, param): """Non-mandatory method to be subclassed for actions to perform when one of the param in self.settings has been deleted Parameters ---------- param: Parameter the parameter that has been deleted """ 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.')
if __name__ == '__main__': class RealParameterManager(ParameterManager): params = {'title': 'Numbers:', 'name': 'numbers', 'type': 'group', 'children': [ {'title': 'Standard float', 'name': 'afloat', 'type': 'float', 'value': 20., 'min': 1., 'tip': 'displays this text as a tooltip'}, {'title': 'Linear Slide float', 'name': 'linearslidefloat', 'type': 'slide', 'value': 50, 'default': 50, 'min': 0, 'max': 123, 'subtype': 'linear'}, {'title': 'Log Slide float', 'name': 'logslidefloat', 'type': 'slide', 'value': 50, 'default': 50, 'min': 1e-5, 'max': 1e5, 'subtype': 'log'}, ]}, import sys from qtpy import QtWidgets app = QtWidgets.QApplication(sys.argv) param_manager = RealParameterManager() param_manager.settings_tree.show() sys.exit(app.exec())