Source code for pymodaq_gui.managers.settings.settings_manager


from typing import Union, TYPE_CHECKING
from pathlib import Path
import sys

import toml
from qtpy import QtWidgets, QtCore, QtGui
from qtpy.QtCore import Qt
from qtpy.QtGui import QKeySequence
from qtpy.QtCore import QModelIndex

from pymodaq_gui.utils.widgets.widget_with_label_title import WidgetWithLabelTitle
from pymodaq_utils.logger import set_logger, get_module_name
from pymodaq_utils.config import GlobalConfig as Config, get_set_local_dir, get_set_config_dir
from pymodaq_utils.utils import capitalize, read_binary_and_deserialize

from pymodaq_gui.parameter import Parameter, ioxml
from pymodaq_gui.parameter.utils import ParameterWithPath

from pymodaq_gui.parameter.ioxml import VALID_FOR_CONFIGURATION

from pymodaq_gui.managers.settings.subentries import (
    SubEntryHandlerFactory, SubEntryHandler, SubEntryError, SubEntryHandlerTypes, SubEntry)
from pymodaq_gui.managers.settings.utils import (
    SettingsManagerParameterTree, SettingsManagerModel, SettingsManagerTableView,
    ParameterDelegate, EntryActions)


from pymodaq_gui.managers.manager_base import ManagerBase, ManagerActions


if TYPE_CHECKING:
    from pymodaq.dashboard import DashBoard


logger = set_logger(get_module_name(__file__))
handler_factory = SubEntryHandlerFactory()

config = Config()


[docs] def get_set_settings_path(subfolder: str = '', user=False): """ creates and return the config folder path for this manager files """ if subfolder != '': target_path = get_set_config_dir('settings', user=user).joinpath(subfolder) else: target_path = get_set_config_dir('settings', user=user) target_path.mkdir(parents=True, exist_ok=True) return target_path
[docs] class SettingsManager(ManagerBase): """ Main class managing settings values to be restored. Could be extended with special subentries dealing with other features, see StateManager """ entry_type = 'settings' entry_extension ='.settings' icon_name = 'settings' settings_handler = SubEntryHandlerTypes.SETTINGS def __init__(self, dashboard: 'DashBoard' = None, handler_id='settings'): self.subentry_handler: SubEntryHandler = None self.config_model = SettingsManagerModel(save_path=self.get_entry_folder()) super().__init__(dashboard=dashboard, tree=SettingsManagerParameterTree( manager=self, handler_id=handler_id)) self.settings_to_apply: WidgetWithLabelTitle = None
[docs] def get_entry_folder(self, **kwargs_to_entry_folder) -> Path: """Get the folder path where the managed entries are stored.""" return get_set_settings_path()
[docs] def save_entries(self, entry_path: Path = None): self.config_model.save(entry_path)
[docs] @staticmethod def format_subentries(entries: list[SubEntry]): return [(f'{capitalize(entry.entry_type)} for ' f'{entry.module_name} - ' f'{entry.setting.parameter.title()} ' f'{entry.setting.value()}') for entry in entries]
def _execute_entry(self, entry_path: Path = None, **kwargs) -> bool: """Applies the entry from the given file in the manager. Parameters: ----------- file : Path The path to the settings file to be applied. """ if entry_path is None: entry_path = self.entry_filepath config_subentries = read_binary_and_deserialize(entry_path) if len(config_subentries) > 0: self.show_subentries(config_subentries, f'Loading {capitalize(self.entry_type)}: {self.entry}') for ind, entry in enumerate(config_subentries): subentry_handler = handler_factory.get_subentry_handler(entry.entry_type)( self.config_model, self.settings) try: subentry_handler.execute_subentry(entry) self.subentries_model.set_status(ind, True) QtWidgets.QApplication.processEvents() QtCore.QThread.msleep(0) except (SubEntryError, NotImplementedError) as e: logger.exception(str(e)) self.subentries_model.set_status(ind, False) self.close_subentries_display(1000) return True
[docs] def populate_from_settings(self, settings: Parameter): """ Initialize the state from a Parameter settings. Parameters ---------- settings : Parameter Settings containing all modules configuration """ self.settings = settings self.set_readonly_setting(self.settings) self.display_settings(display_all=False, param=self.settings) self.set_drag_mode_recursive(self.settings, movable=True, drop_enabled=True)
[docs] def populate_from_file(self, file_path: Path): """ for quick testing purpose, not meant to be used at the end""" children = ioxml.XML_file_to_parameter(file_path) settings = Parameter.create( title="Control Modules:", name="control_modules", type="group", children=children, ) self.populate_from_settings(settings)
[docs] def add_subentry(self, special_entry_name: str): """ Create an entry in the table from one of the special SubEntryHandlers""" self.subentry_handler = handler_factory.get_subentry_handler(special_entry_name)( self.config_model, self.settings, self) self.subentry_handler.show_dialog()
[docs] def setup_docks_and_widgets(self): self.tree.setDragEnabled(True) self.tree.setAcceptDrops(False) self.tree.setDragDropMode(QtWidgets.QTableView.DragDropMode.DragOnly) self.tree.doubleClicked.connect(self.add_setting) self.table_out = SettingsManagerTableView(True) self.table_out.horizontalHeader().ResizeMode(QtWidgets.QHeaderView.ResizeToContents) self.table_out.horizontalHeader().setStretchLastSection(True) self.table_out.setSelectionBehavior(QtWidgets.QTableView.SelectRows) #self.table_out.setSelectionMode(QtWidgets.QTableView.SingleSelection) self.table_out.setDragDropMode(QtWidgets.QTableView.DragDropMode.DragDrop) self.table_out.setDefaultDropAction(QtCore.Qt.DropAction.MoveAction) self.table_out.setModel(self.config_model) self.table_out.add_data_signal[str].connect(self.add_subentry) self.table_out.remove_row_signal[int].connect(self.config_model.remove_data) self.table_out.load_data_signal.connect(self.config_model.load) self.table_out.save_data_signal.connect(self.config_model.save) self.delegate = ParameterDelegate() self.table_out.setItemDelegate(self.delegate) layout = QtWidgets.QHBoxLayout() hwidget = QtWidgets.QWidget() hlayout = QtWidgets.QHBoxLayout() hwidget.setLayout(hlayout) self.vlayout_right = QtWidgets.QVBoxLayout() valyout_left = QtWidgets.QVBoxLayout() self.widget_buttons = QtWidgets.QWidget() self.widget_buttons.setLayout(QtWidgets.QVBoxLayout()) self.widget_buttons.layout().addStretch() self.widget_buttons.layout().addStretch() layout.addWidget(hwidget) hlayout.addLayout(valyout_left) hlayout.addWidget(self.widget_buttons) hlayout.addLayout(self.vlayout_right) self.settings_to_pick = WidgetWithLabelTitle('Select Settings:', self.settings_tree) self.settings_to_apply = WidgetWithLabelTitle('Settings to apply:', self.table_out) valyout_left.addWidget(self.settings_to_pick) self.vlayout_right.addWidget(self.settings_to_apply) self.main_widget.setLayout(layout)
[docs] def setup_menus_and_toolbars(self, menubar: QtWidgets.QMenuBar = None): move_toolbar = self.add_toolbar('move', 'Move') move_toolbar.setOrientation(QtCore.Qt.Orientation.Vertical) self.widget_buttons.layout().insertWidget(1, move_toolbar) self.add_toolbar('actions', 'Actions', parent=self.mainwindow) self.vlayout_right.insertWidget(0, self.toolbar)
[docs] def setup_actions(self): self.add_action('show_all_settings', 'Show All Settings', 'EditFind', checkable=True, tip='If Checked: display all settings (in green, settings that can be configured)' ' otherwise only configurables ones', toolbar='actions') self.add_action(EntryActions.ADD, 'Add', 'arrow_circle_right', toolbar='move', tip='Add the current Parameter item', icon_color=self.get_theme().green, ) self.add_action(EntryActions.REMOVE, 'Remove', 'arrow_circle_left', toolbar='move', tip='Delete the current Configuration item ("Del")', icon_color=self.get_theme().red, shortcut=Qt.Key.Key_Delete) self.add_action(EntryActions.UP, 'Move Up', 'arrow_circle_up', toolbar='move', tip='Move UP the current Configuration item ("Ctrl+Up")', icon_color=self.get_theme().blue, shortcut=QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Up)) self.add_action(EntryActions.DOWN, 'Move Down', 'arrow_circle_down', toolbar='move', icon_color=self.get_theme().orange, tip='Move Down the current Configuration item ("Ctrl+Down")', shortcut=QKeySequence(Qt.Modifier.CTRL | Qt.Key.Key_Down)) self.toolbar.addSeparator()
[docs] def connect_things(self): self.connect_action(EntryActions.ADD, self.add_setting) self.connect_action(EntryActions.REMOVE, self.remove_setting) self.connect_action(EntryActions.UP, self.move_up_setting) self.connect_action(EntryActions.DOWN, self.move_down_setting) self.connect_action('show_all_settings', self.display_settings)
def _update_entry(self, entry: Path): self.config_model.load(self.entry_filepath)
[docs] def update_settings(self, settings: Union[Parameter, Path, str] = None): if settings is None: settings = self._get_settings_from_file() if settings == '': return if isinstance(settings, Parameter): self.populate_from_settings(settings) elif isinstance(settings, Path): self.populate_from_file(settings) else: raise TypeError(f'Cannot load settings from {settings}, should be a Parameter or a Path')
[docs] def set_readonly_setting(self, param: Parameter = None): """ Set all settings as readonly but configure the VALID_FOR_CONFIGURATION option: if initially readonly: VALID_FOR_CONFIGURATION is set to its value (if existing otherwise True if not specified) else: VALID_FOR_CONFIGURATION is set to False See Also -------- pymodaq_gui.parameter.ioxml.VALID_FOR_CONFIGURATION pymodaq.control_modules.move_utility_classes.params pymodaq.control_modules.viewer_utility_classes.params """ if not param.readonly(): param.setOpts(**{'readonly': True, VALID_FOR_CONFIGURATION: param.opts.get(VALID_FOR_CONFIGURATION, True)}) else: if not param.opts.get(VALID_FOR_CONFIGURATION, False): param.setOpts(**{VALID_FOR_CONFIGURATION: False}) for child in param.children(): self.set_readonly_setting(child)
[docs] def display_settings(self, display_all: bool = True, param: Parameter = None): if param is None: param = self.settings if display_all: param.setOpts(visible=True) if param.opts[VALID_FOR_CONFIGURATION]: brush = QtGui.QBrush(QtCore.Qt.GlobalColor.green) for item in param.items: for ind_col in range(item.columnCount()): item.setForeground(ind_col, brush) else: param.setOpts(visible=param.opts[VALID_FOR_CONFIGURATION]) if param.opts[VALID_FOR_CONFIGURATION]: brush = QtGui.QBrush(QtCore.Qt.GlobalColor.white) for item in param.items: for ind_col in range(item.columnCount()): item.setForeground(ind_col, brush) for child in param.children(): self.display_settings(display_all, child)
[docs] def set_drag_mode_recursive(self, param: Parameter, movable=True, drop_enabled=True): if param.opts.get(VALID_FOR_CONFIGURATION, True): param.setOpts(movable=movable, dropEnabled=drop_enabled) for child in param.children(): self.set_drag_mode_recursive(child, movable, drop_enabled)
[docs] @staticmethod def get_module_from_param(param: ParameterWithPath) -> str: """ should return the module name from data bundled in the ParameterWithPath To be reimplemented Parameters ---------- param: ParameterWithPath """ return ''
[docs] def add_setting(self): if self.tree.currentItem() is not None: current_setting = self.tree.currentItem().param try: module = self.get_module_from_param(ParameterWithPath(current_setting)) except KeyError: module = 'None' entry = SubEntry(self.settings_handler, module, ParameterWithPath(current_setting)) entries = self.config_model.split_entry(entry) for entry in entries: self.config_model.add_data(self.config_model.rowCount(), entry)
[docs] def do_things_for_new_creation(self): self.table_out.setCurrentIndex(self.table_out.model().index(0, 0)) self.table_out.clear()
[docs] def remove_setting(self): index_0 = self.table_out.selectedIndexes()[0] indexes = list(set([index.row() for index in self.table_out.selectedIndexes()])) indexes.sort() for index in indexes[::-1]: #start with the highest row if index != -1: self.config_model.remove_data(index) self.table_out.setCurrentIndex(index_0)
[docs] def move_up_setting(self): indexes = list(set([index.row() for index in self.table_out.selectedIndexes()])) indexes.sort() if indexes[0] == 0: return else: for index in indexes: #start with the lowest row if index != -1: # means no selected row self.config_model.moveRow(QModelIndex(), index, QModelIndex(), index-1)
[docs] def move_down_setting(self): indexes = list(set([index.row() for index in self.table_out.selectedIndexes()])) indexes.sort() if indexes[-1] + 1 == self.config_model.rowCount(): return else: for index in indexes[::-1]: #start with the highest row if index != -1: # means no selected row self.config_model.moveRow(QModelIndex(), index, QModelIndex(), index+2)
if __name__ == "__main__": from pymodaq_gui.qt_utils import mkQApp app = mkQApp('SettingsManager') prog = SettingsManager(dashboard=None) prog.update_settings(Path('../../resources/scan_settings.xml')) prog.mainwindow.show() prog.enable_actions(True) sys.exit(app.exec())