Fork PyRPL on GitHub

Source code for pyrpl.widgets.module_widgets.base_module_widget

"""
The basic functionality of all module widgets are inherited from the base
class :class:`.ModuleWidget`.

A module widget is delimited by a dashed-line (a QGroupBox). The following
menu is available on the top part of each ModuleWidget, directly behind the
name of the module (e.g. :code:`pid0`, :code:`pid1`, ...). Right click on
the item (e.g. :code:`.:Load:.`, :code:`.:Save:.`, ...) to access the associated
submenu:

* :code:`.:Load:.` Loads the state of the module from a list of previously saved states.
* :code:`.:Save:.` Saves the current state under a given state name.
* :code:`.:Erase:.` Erases one of the previously saved states.
* :code:`.:Edit:.` Opens a text window to edit the yml code of a state.
* :code:`.:Hide/Show:.` Hides or shows the content of the module widget.

Inside the module widget, different attribute values can be manipulated using
the shown sub-widgets (e.g. :code:`input`, :code:`setpoint`, :code:`max_voltage`, ...). The
modifications will take effect immediately. Only the module state
:code:`<current state>` is affected by these changes. Saving the state under
a different name only preserves the state at the moment of saving for later
retrieval.

At the next startup with the same config file, the :code:<current state> of
all modules is loaded.

If a module-widget is grayed out completely, it has been reserved by another,
higher-level module whose name appears in parentheses after the name of the
module (e.g. :code:`pid2 (output1)` means that the module :code:`pid2` is
being used by the module :code:`output1`, which is actually a submodule of the
:code:`lockbox` module). You can right-click anywhere on the grayed out
widget and click on "Free" to override that reservation and use the module
for your own purposes.

.. warning:: If you override a module reservation, the module in parenthesis
             might stop to function properly. A better practice is to identify
             the top-level module responsible for the reservation, remove its
             name from the list in your configuration file (located at
             /HOME/pyrpl_user_dir/config/<string_shown_in_top_bar_of_the_gui>.yml)
             and restart PyRPL with that configuration.
"""
from qtpy import QtCore, QtWidgets
from collections import OrderedDict
import functools
import logging
from ..yml_editor import YmlEditor


[docs]class MyMenuLabel(QtWidgets.QLabel): """ A label on top of the menu widget that is able to display save or load menu. """ def __init__(self, module_widget): self.module_widget = module_widget self.module = module_widget.module super(MyMenuLabel, self).__init__(self.text, module_widget)
[docs] def get_menu(self): menu = QtWidgets.QMenu(self) self.actions = [] for state in self.module.states: action = QtWidgets.QAction(state, self) self.actions.append(action) action.triggered.connect(functools.partial(self.func, state)) menu.addAction(action) return menu
[docs] def contextMenuEvent(self, event): menu = self.get_menu() if menu is not None: menu.exec_(event.globalPos())
[docs]class LoadLabel(MyMenuLabel): """ "Load" label """ text = " .:Load:. "
[docs] def func(self, state): self.module.load_state(state)
[docs]class SaveLabel(MyMenuLabel): """ "Save" label """ text = " .:Save:. " def __init__(self, module_widget): super(SaveLabel, self).__init__(module_widget)
[docs] def func(self, state): self.module.save_state(state)
[docs] def get_menu(self): menu = super(SaveLabel, self).get_menu() action_new = QtWidgets.QAction('<New...>', self) action_new.triggered.connect(self.new_state) menu.addAction(action_new) return menu
[docs] def new_state(self): state, accept = QtWidgets.QInputDialog.getText(self, "Save %s " "state"%self.module.name, "Enter new state name:") state = str(state) if accept: if state in self.module.states: raise ValueError( "State %s of module %s already exists!"%( state, self.module.name)) self.module.save_state(state)
[docs]class EraseLabel(MyMenuLabel): """ "Erase" label """ text = " .:Erase:. "
[docs] def func(self, state): self.module.erase_state(state)
[docs]class EditLabel(MyMenuLabel): """ "Edit" label """ text = " .:Edit:. "
[docs] def func(self, state): editor = YmlEditor(self.module, state) self.module_widget.yml_editors[str(self.module.name) + '__' + str( state)] = editor editor.show()
[docs] def get_menu(self): menu = super(EditLabel, self).get_menu() action_current = QtWidgets.QAction('<Current>', self) action_current.triggered.connect(functools.partial(self.func, None)) others = menu.actions() if len(others)>0: other = others[0] menu.insertAction(other, action_current) else: menu.addAction(action_current) # will append the action at the end return menu
[docs]class HideShowLabel(MyMenuLabel): """ "Hide/Show" label """ text = " .:Hide/Show:. "
[docs] def get_menu(self): if hasattr(self, 'hidden') and self.hidden: self.module_widget.show_widget() self.hidden = False else: self.module_widget.hide_widget() self.hidden = True return None
[docs]class ReducedModuleWidget(QtWidgets.QGroupBox): """ Base class for a module Widget. In general, this is one of the DockWidget of the Pyrpl MainWindow. """ attribute_changed = QtCore.Signal() title_pos = (12, 0) def __init__(self, name, module, parent=None): super(ReducedModuleWidget, self).__init__(parent) self._logger = logging.getLogger(__name__) self.module = module self.name = name self.attribute_widgets = OrderedDict() self.yml_editors = dict() # optional widgets to edit the yml code of module on a per-state basis self.init_gui() # performs the automatic gui creation based on register_names # self.setStyleSheet("ModuleWidget{border:0;color: transparent;}") # frames and title hidden for software_modules # ModuleManagerWidget sets them visible for the HardwareModuleWidgets... self.create_title_bar() self.change_ownership() # also sets the title self.module._signal_launcher.connect_widget(self)
[docs] def init_main_layout(self, orientation='horizontal'): self.root_layout = QtWidgets.QHBoxLayout() self.main_widget = QtWidgets.QWidget() self.root_layout.addWidget(self.main_widget) if orientation == "vertical": self.main_layout = QtWidgets.QVBoxLayout() else: self.main_layout = QtWidgets.QHBoxLayout() self.main_widget.setLayout(self.main_layout) self.setLayout(self.root_layout)
[docs] def show_widget(self): """ shows the widget after it has been hidden """ self.main_widget.show()
[docs] def hide_widget(self): """ shows the widget after it has been hidden """ self.main_widget.hide()
[docs] def init_gui(self): """ To be overwritten in derived class :return: """ self.init_main_layout() self.init_attribute_layout()
[docs] def init_attribute_layout(self): """ Automatically creates the gui properties for the register_widgets in register_names. :return: """ if '\n' in self.module._gui_attributes: self.attributes_layout = QtWidgets.QVBoxLayout() self.main_layout.addLayout(self.attributes_layout) self.attribute_layout = QtWidgets.QHBoxLayout() self.attributes_layout.addLayout(self.attribute_layout) else: self.attribute_layout = QtWidgets.QHBoxLayout() self.main_layout.addLayout(self.attribute_layout) for attr_name in self.module._gui_attributes: if attr_name == '\n': self.attribute_layout = QtWidgets.QHBoxLayout() self.attributes_layout.addLayout(self.attribute_layout) else: attribute_value = getattr(self.module, attr_name) # needed for # passing the instance to the descriptor attribute = getattr(self.module.__class__, attr_name) if callable(attribute): # assume that attribute is a function widget = QtWidgets.QPushButton(attr_name) widget.clicked.connect(getattr(self.module, attr_name)) else: # standard case: make attribute widget widget = attribute._create_widget(self.module) if widget is None: continue widget.value_changed.connect(self.attribute_changed) self.attribute_widgets[attr_name] = widget self.attribute_layout.addWidget(widget) self.attribute_layout.addStretch(1)
[docs] def update_attribute_by_name(self, name, new_value_list): """ SLOT: don't change name unless you know what you are doing Updates a specific attribute. New value is passed as a 1-element list to avoid typing problems in signal-slot. """ if name in self.module._gui_attributes: widget = self.attribute_widgets[str(name)] try: # try to propagate the change of attribute to the widget widget.update_attribute_by_name(new_value_list) except: # directly set the widget value otherwise self.attribute_widgets[str(name)].widget_value = new_value_list[0]
[docs] def change_options(self, select_attribute_name, new_options): """ SLOT: don't change name unless you know what you are doing New options should be displayed for some SelectAttribute. """ if select_attribute_name in self.module._gui_attributes: self.attribute_widgets[str(select_attribute_name)].change_options(new_options)
[docs] def refresh_filter_options(self, filter_attribute_name): """ SLOT: don't change name unless you know what you are doing New options should be displayed for some FilterProperty. """ if filter_attribute_name in self.module._gui_attributes: self.attribute_widgets[str( filter_attribute_name)].refresh_options(self.module)
[docs] def change_ownership(self): """ SLOT: don't change name unless you know what you are doing Display the new ownership """ #name = self.module.pyrpl.name + " - " + self.module.name name = self.module.name if self.module.owner is not None: self.setEnabled(False) self.set_title(name + ' (' + self.module.owner + ')') else: self.setEnabled(True) self.set_title(name)
[docs] def set_title(self, title): return self.setTitle(str(title))
[docs] def create_title_bar(self): # manage spacings of title bar / box around module for v in [self.main_layout]: v.setSpacing(0) v.setContentsMargins(5, 1, 0, 0)
[docs] def save_curve(self, x_values, y_values, **attributes): """ Saves the curve in some database system. To change the database system, overwrite this function or patch Module.curvedb if the interface is identical. :param x_values: numpy array with x values :param y_values: numpy array with y values :param attributes: extra curve parameters (such as relevant module settings) """ c = self.curve_class.create(x_values, y_values, **attributes) c.name = attributes["curve_name"] return c
[docs]class ModuleWidget(ReducedModuleWidget): """ Base class for a module Widget. In general, this is one of the DockWidget of the Pyrpl MainWindow. """
[docs] def set_title(self, title): title = str(title) if hasattr(self, "title_label"): # ModuleManagerWidgets don't have a title_label self.title_label.setText(title) self.title_label.adjustSize() self.title_label.move(*self.title_pos) self.load_label.move(self.title_label.width() + self.title_pos[0], self.title_pos[1]) self.save_label.move(self.load_label.width() + self.load_label.pos().x(), self.title_pos[1]) self.erase_label.move(self.save_label.width() + self.save_label.pos().x(), self.title_pos[1]) self.edit_label.move(self.erase_label.width() + self.erase_label.pos().x(), self.title_pos[1]) self.hideshow_label.move(self.edit_label.width() + self.edit_label.pos().x(), self.title_pos[1])
[docs] def create_title_bar(self): self.title_label = QtWidgets.QLabel("yo", parent=self) # title should be at the top-left corner of the widget self.load_label = LoadLabel(self) self.load_label.adjustSize() self.save_label = SaveLabel(self) self.save_label.adjustSize() self.erase_label = EraseLabel(self) self.erase_label.adjustSize() self.edit_label = EditLabel(self) self.edit_label.adjustSize() self.hideshow_label = HideShowLabel(self) self.hideshow_label.adjustSize() # self.setStyleSheet("ModuleWidget{border: 1px dashed gray;color: black;}") self.setStyleSheet("ModuleWidget{margin: 0.1em; margin-top:0.6em; border: 1 dotted gray;border-radius:5}") # margin-top large enough for border to be in the middle of title self.layout().setContentsMargins(0, 5, 0, 0)