diff --git a/etc/cryo.cfg b/etc/cryo.cfg index 3975843..ab4467c 100644 --- a/etc/cryo.cfg +++ b/etc/cryo.cfg @@ -8,7 +8,7 @@ description = short description [interface tcp] interface=tcp bindto=0.0.0.0 -bindport=10769 +bindport=10767 # protocol to use for this interface framing=eol encoding=demo @@ -39,3 +39,8 @@ timeout=900 # some (non-default) parameter properties pollinterval.export=False +# some parameter grouping +p.group='pid' +i.group='pid' +d.group='pid' + diff --git a/secop/client/baseclient.py b/secop/client/baseclient.py index 78add40..5b7a397 100644 --- a/secop/client/baseclient.py +++ b/secop/client/baseclient.py @@ -469,8 +469,11 @@ class Client(object): def getParameters(self, module): return self.describing_data['modules'][module]['parameters'].keys() + def getModuleProperties(self, module): + return self.describing_data['modules'][module]['properties'] + def getModuleBaseClass(self, module): - return self.describing_data['modules'][module]['interfaceclass'] + return self.getModuleProperties(module)['interface'] def getCommands(self, module): return self.describing_data['modules'][module]['commands'].keys() diff --git a/secop/devices/cryo.py b/secop/devices/cryo.py index caf2c37..42d58b1 100644 --- a/secop/devices/cryo.py +++ b/secop/devices/cryo.py @@ -65,15 +65,15 @@ class Cryostat(CryoBase): maxpower=PARAM("Maximum heater power", validator=nonnegative, default=1, unit="W", readonly=False, - group='heater', + group='heater_settings', ), heater=PARAM("current heater setting", validator=floatrange(0, 100), default=0, unit="%", - group='heater', + group='heater_settings', ), heaterpower=PARAM("current heater power", validator=nonnegative, default=0, unit="W", - group='heater', + group='heater_settings', ), target=PARAM("target temperature", validator=nonnegative, default=0, unit="K", @@ -110,17 +110,17 @@ class Cryostat(CryoBase): tolerance=PARAM("temperature range for stability checking", validator=floatrange(0, 100), default=0.1, unit='K', readonly=False, - group='window', + group='stability', ), window=PARAM("time window for stability checking", validator=floatrange(1, 900), default=30, unit='s', readonly=False, - group='window', + group='stability', ), timeout=PARAM("max waiting time for stabilisation check", validator=floatrange(1, 36000), default=900, unit='s', readonly=False, - group='window', + group='stability', ), ) CMDS = dict( diff --git a/secop/gui/mainwindow.py b/secop/gui/mainwindow.py index 16807af..5d3ae95 100644 --- a/secop/gui/mainwindow.py +++ b/secop/gui/mainwindow.py @@ -33,9 +33,9 @@ from secop.client.baseclient import Client as SECNode import sys ITEM_TYPE_NODE = QTreeWidgetItem.UserType + 1 -ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 2 -ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 3 - +ITEM_TYPE_GROUP = QTreeWidgetItem.UserType + 2 +ITEM_TYPE_MODULE = QTreeWidgetItem.UserType + 3 +ITEM_TYPE_PARAMETER = QTreeWidgetItem.UserType + 4 class QSECNode(SECNode, QObject): @@ -111,6 +111,8 @@ class MainWindow(QMainWindow): def on_treeWidget_currentItemChanged(self, current, previous): if current.type() == ITEM_TYPE_NODE: self._displayNode(current.text(0)) + elif current.type() == ITEM_TYPE_GROUP: + self._displayGroup(current.parent().text(0), current.text(0)) elif current.type() == ITEM_TYPE_MODULE: self._displayModule(current.parent().text(0), current.text(0)) elif current.type() == ITEM_TYPE_PARAMETER: diff --git a/secop/gui/modulectrl.py b/secop/gui/modulectrl.py index c35cc1d..399b99b 100644 --- a/secop/gui/modulectrl.py +++ b/secop/gui/modulectrl.py @@ -21,7 +21,7 @@ # # ***************************************************************************** -from PyQt4.QtGui import QWidget, QLabel, QMessageBox +from PyQt4.QtGui import QWidget, QLabel, QMessageBox, QCheckBox from PyQt4.QtCore import pyqtSignature as qtsig, Qt, pyqtSignal from secop.gui.util import loadUi @@ -55,6 +55,38 @@ class ParameterButtons(QWidget): self.setLineEdit.text()) +class ParameterGroup(QWidget): + def __init__(self, groupname, parent=None): + super(ParameterGroup, self).__init__(parent) + loadUi(self, 'paramgroup.ui') + + self._groupname = groupname + + self._row = 0 + self._widgets = [] + + self.paramGroupBox.setTitle('Group: ' + str(groupname)) + self.paramGroupBox.toggled.connect(self.on_toggle_clicked) + self.paramGroupBox.setChecked(False) + + def addWidgets(self, label, widget): + self._widgets.extend((label, widget)) + self.paramGroupBox.layout().addWidget(label, self._row, 0) + self.paramGroupBox.layout().addWidget(widget, self._row, 1) + label.hide() + widget.hide() + self._row += 1 + + def on_toggle_clicked(self): + print "ParameterGroup.on_toggle_clicked" + if self.paramGroupBox.isChecked(): + for w in self._widgets: + w.show() + else: + for w in self._widgets: + w.hide() + + class ModuleCtrl(QWidget): def __init__(self, node, module, parent=None): @@ -65,50 +97,128 @@ class ModuleCtrl(QWidget): self._lastclick = None self._paramWidgets = {} # widget cache do avoid garbage collection + self._groupWidgets = {} # cache of grouping widgets + + self._labelfont = self.font() + self._labelfont.setBold(True) self.moduleNameLabel.setText(module) self._initModuleWidgets() self._node.newData.connect(self._updateValue) + def _initModuleWidgets(self): initValues = self._node.queryCache(self._module) row = 0 - font = self.font() - font.setBold(True) - - for param in sorted(self._node.getParameters(self._module)): - labelstr = param + ':' - unit = self._node.getProperties(self._module, param).get('unit', - '') - descr = self._node.getProperties(self._module, - param).get('description', '') - - if unit: - labelstr = "%s (%s):" % (param, unit) - - label = QLabel(labelstr) - label.setFont(font) + # collect grouping information + paramsByGroup = {} # groupname -> [paramnames] + allGroups = set() + params = self._node.getParameters(self._module) + for param in params: props = self._node.getProperties(self._module, param) + group = props.get('group',None) + if group is not None: + allGroups.add(group) + paramsByGroup.setdefault(group, []).append(param) - buttons = ParameterButtons(self._module, param, - initValues[param].value, - props['readonly']) + groupWidgets = {} # groupname -> CheckBoxWidget for (un)folding - # buttons.setRequested.connect(self._node.setParameter) - buttons.setRequested.connect(self._set_Button_pressed) + # create and insert widgets into our QGridLayout + for param in sorted(allGroups.union(set(params))): + labelstr = param + ':' + if param in paramsByGroup: + group = param + # is the name of a group -> create (un)foldable label + checkbox = QCheckBox(labelstr) + checkbox.setFont(self._labelfont) + groupWidgets[param] = checkbox - if descr: - buttons.setToolTip(descr) + # check if there is a param of the same name too + if group in params: + # yes: create a widget for this as well + labelstr, buttons = self._makeEntry(param, initValues[param].value, nolabel=True, checkbox=checkbox, invert=True) + checkbox.setText(labelstr) + + # add to Layout (yes: ignore the label!) + self.paramGroupBox.layout().addWidget(checkbox, row, 0) + self.paramGroupBox.layout().addWidget(buttons, row, 1) + else: + self.paramGroupBox.layout().addWidget(checkbox, row, 0, 1, 2) # or .. 1, 2) ?? + row += 1 + + # loop over all params and insert and connect + for param in paramsByGroup[param]: + if param == group: + continue + label, buttons = self._makeEntry(param, initValues[param].value, checkbox=checkbox, invert=False) + + # add to Layout + self.paramGroupBox.layout().addWidget(label, row, 0) + self.paramGroupBox.layout().addWidget(buttons, row, 1) + row += 1 + + else: + # param is a 'normal' param: create a widget if it has no group or is named after a group (otherwise its created above) + props = self._node.getProperties(self._module, param) + if props.get('group', param) == param: + label, buttons = self._makeEntry(param, initValues[param].value) + + # add to Layout + self.paramGroupBox.layout().addWidget(label, row, 0) + self.paramGroupBox.layout().addWidget(buttons, row, 1) + row += 1 + + + def _makeEntry(self, param, initvalue, nolabel=False, checkbox=None, invert=False): + props = self._node.getProperties(self._module, param) + + description = props.get('description', '') + unit = props.get('unit','') + + if unit: + labelstr = '%s (%s):' % (param, unit) + else: + labelstr = '%s:' % (param,) + + if checkbox and not invert: + labelstr = ' ' + labelstr + + buttons = ParameterButtons(self._module, param, initvalue, props['readonly']) + buttons.setRequested.connect(self._set_Button_pressed) + + if description: + buttons.setToolTip(description) + + if nolabel: + label = labelstr + else: + label = QLabel(labelstr) + label.setFont(self._labelfont) + + if checkbox: + def stateChanged(newstate, buttons=buttons, label=None if nolabel else label, invert=invert): + if (newstate and not invert) or (invert and not newstate): + buttons.show() + if label: + label.show() + else: + buttons.hide() + if label: + label.hide() + # set initial state + stateChanged(0) + # connect + checkbox.stateChanged.connect(stateChanged) + + self._paramWidgets[param] = (label, buttons) + + return label, buttons - self.paramGroupBox.layout().addWidget(label, row, 0) - self.paramGroupBox.layout().addWidget(buttons, row, 1) - self._paramWidgets[param] = (label, buttons) - row += 1 def _set_Button_pressed(self, module, parameter, target): sig = (module, parameter, target) diff --git a/secop/gui/ui/paramview.ui b/secop/gui/ui/paramview.ui index bbe9976..5645b3a 100644 --- a/secop/gui/ui/paramview.ui +++ b/secop/gui/ui/paramview.ui @@ -6,7 +6,7 @@ 0 0 - 230 + 238 121 @@ -55,7 +55,7 @@ - Parameters: + Properties