class ChoiceElement(MeasElement): """Base for elements that allow an arbitrary choice.""" CACHE_KEY = '' SORT_KEY = lambda self, x: None # keep previous ordering VALUES = [] def createWidget(self, parent, client): if self.CACHE_KEY: values = client.getDeviceParam(*self.CACHE_KEY.split('/')) try: values = sorted(values or [], key=self.SORT_KEY) except TypeError: # unsortable? pass else: values = self.VALUES self._values = values self._widget = QComboBox(parent) self._widget.addItems(self._values) if self.value is not None and self.value in self._values: self._widget.setCurrentIndex(self._values.index(self.value)) elif self.value is None and self._values: self.value = self._values[0] self._widget.currentIndexChanged.connect(self._updateValue) return self._widget def _updateValue(self, index): self.value = self._values[index] self.changed.emit(self.value)
class AliasWidget(QFrame): def __init__(self, parent, name, selections, preselect): QFrame.__init__(self, parent) self.name = name self.selections = selections layout = QHBoxLayout() layout.addWidget(QLabel(name, self)) self.combo = QComboBox(self) self.combo.addItems(selections) if preselect in selections: self.combo.setCurrentIndex(selections.index(preselect)) else: self.combo.setCurrentIndex(0) layout.addWidget(self.combo) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) def setSelections(self, selections, preselect): if selections != self.selections: self.selections = selections self.combo.clear() self.combo.addItems(selections) if preselect in selections: self.combo.setCurrentIndex(selections.index(preselect)) else: self.combo.setCurrentIndex(0) def getSelection(self): return self.combo.currentText()
class Detector(MeasElement): """Element for selecting detector distance, depending on selector.""" CACHE_KEY = 'detector/presets' SORT_KEY = lambda self, x: num_sort(x) LABEL = 'Detector' _allvalues = None def clientUpdate(self, client): self._allvalues = client.getDeviceParam(*self.CACHE_KEY.split('/')) self._values = [] def createWidget(self, parent, client): self.clientUpdate(client) self._widget = QComboBox(parent) self._updateWidget() self._widget.currentIndexChanged.connect(self._updateValue) return self._widget def _updateWidget(self): self._widget.clear() self._widget.addItems(self._values) if self.value in self._values: self._widget.setCurrentIndex(self._values.index(self.value)) def otherChanged(self, eltype, value): if eltype == 'selector' and self._allvalues is not None: self._values = sorted(self._allvalues[value], key=self.SORT_KEY) if self.value not in self._values: if self._values: self.value = self._values[0] else: self.value = None if self._widget is not None: self._updateWidget() def _updateValue(self, index): self.value = self._values[index] self.changed.emit(self.value)
class DataExportDialog(QFileDialog): def __init__(self, viewplot, curvenames, *args): QFileDialog.__init__(self, viewplot, *args) self.setOption(self.DontConfirmOverwrite, False) # allow adding some own widgets self.setOption(self.DontUseNativeDialog, True) self.setAcceptMode(QFileDialog.AcceptSave) layout = self.layout() layout.addWidget(QLabel('Curve:', self), 4, 0) self.curveCombo = QComboBox(self) if len(curvenames) > 1: self.curveCombo.addItem('all (in separate files)') self.curveCombo.addItem('all (in one file, multiple data columns)') self.curveCombo.addItems(curvenames) layout.addWidget(self.curveCombo, 4, 1) layout.addWidget(QLabel('Time format:', self), 5, 0) self.formatCombo = QComboBox(self) self.formatCombo.addItems([ 'Seconds since first datapoint', 'UNIX timestamp', 'Text timestamp (YYYY-MM-dd.HH:MM:SS)' ]) layout.addWidget(self.formatCombo, 5, 1)
class Chopper(MeasElement): """Element for selecting chopper TOF resolution.""" CACHE_KEY = 'chopper/resolutions' SORT_KEY = lambda self, x: num_sort(x) LABEL = 'TOF dλ/λ' def createWidget(self, parent, client): resos = client.getDeviceParam(*self.CACHE_KEY.split('/')) self._values = ['off'] + ['%.1f%%' % v for v in (resos or [])] + ['manual'] self._widget = QComboBox(parent) self._widget.addItems(self._values) if self.value is not None and self.value in self._values: self._widget.setCurrentIndex(self._values.index(self.value)) elif self.value is None and self._values: self.value = self._values[0] self._widget.currentIndexChanged.connect(self._updateValue) return self._widget def _updateValue(self, index): self.value = self._values[index] self.changed.emit(self.value)
class ControlDialog(QDialog): """Dialog opened to control and view details for one device.""" closed = pyqtSignal(object) def __init__(self, parent, devname, devinfo, devitem, log, expert): QDialog.__init__(self, parent) loadUi(self, 'panels/devices_one.ui') self.log = log self.device_panel = parent self.client = parent.client self.devname = devname self.devinfo = devinfo self.devitem = devitem self.paramItems = {} self.moveBtn = None self.target = None self._reinit() self._show_extension(expert) if self.target: self.target.setFocus() def _reinit(self): classes = self.devinfo.classes if sip.isdeleted(self.devitem): # The item we're controlling has been removed from the list (e.g. # due to client reconnect), get it again. self.devitem = self.device_panel._devitems.get( self.devname.lower()) # No such device anymore... if self.devitem is None: self.close() return self.deviceName.setText('Device: %s' % self.devname) self.setWindowTitle('Control %s' % self.devname) self.settingsBtn = self.buttonBox.button( QDialogButtonBox.RestoreDefaults) self.settingsBtn.clicked.connect(self.on_settingsBtn_clicked) # trigger parameter poll self.client.eval('%s.pollParams()' % self.devname, None) # now get all cache keys pertaining to the device and set the # properties we want params = self.client.getDeviceParams(self.devname) self.paraminfo = self.client.getDeviceParamInfo(self.devname) self.paramvalues = dict(params) # put parameter values in the list widget self.paramItems.clear() self.paramList.clear() for key, value in sorted(iteritems(params)): if self.paraminfo.get(key): # normally, show only userparams, except in expert mode is_userparam = self.paraminfo[key]['userparam'] if is_userparam or self.device_panel._show_lowlevel: self.paramItems[key] = item = \ QTreeWidgetItem(self.paramList, [key, str(value)]) # display non-userparams in grey italics, like lowlevel # devices in the device list if not is_userparam: item.setFont(0, lowlevelFont[True]) item.setForeground(0, lowlevelBrush[True]) # set description label if params.get('description'): self.description.setText(params['description']) else: self.description.setVisible(False) # check how to refer to the device in commands: if it is lowlevel, # we need to use quotes self.devrepr = srepr(self.devname) if params.get('lowlevel', True) \ else self.devname # show "Set alias" group box if it is an alias device if 'alias' in params: if params['alias']: self.deviceName.setText(self.deviceName.text() + ' (alias for %s)' % params['alias']) alias_config = self.client.eval('session.alias_config', {}) self.aliasTarget = QComboBox(self) self.aliasTarget.setEditable(True) if self.devname in alias_config: items = [t[0] for t in alias_config[self.devname]] self.aliasTarget.addItems(items) if params['alias'] in items: self.aliasTarget.setCurrentIndex( items.index(params['alias'])) self.targetLayoutAlias.takeAt(1).widget().deleteLater() self.targetLayoutAlias.insertWidget(1, self.aliasTarget) if self.client.viewonly: self.setAliasBtn.setEnabled(False) else: self.aliasGroup.setVisible(False) historyBtn = self.buttonBox.button(QDialogButtonBox.Reset) # show current value/status if it is readable if 'nicos.core.device.Readable' not in classes: self.valueFrame.setVisible(False) self.buttonBox.removeButton(historyBtn) else: self.valuelabel.setText(self.devitem.text(1)) self.statuslabel.setText(self.devitem.text(2)) self.statusimage.setPixmap(self.devitem.icon(0).pixmap(16, 16)) setForegroundBrush(self.statuslabel, self.devitem.foreground(2)) setBackgroundBrush(self.statuslabel, self.devitem.background(2)) # modify history button: add icon and set text historyBtn.setIcon(QIcon(':/find')) historyBtn.setText('Plot history...') historyBtn.clicked.connect(self.on_historyBtn_clicked) if self.client.viewonly: self.limitFrame.setVisible(False) self.targetFrame.setVisible(False) return # add a menu for the "More" button self.moveBtns.clear() menu = QMenu(self) if 'nicos.core.mixins.HasLimits' in classes: menu.addAction(self.actionSetLimits) if 'nicos.core.mixins.HasOffset' in classes: menu.addAction(self.actionAdjustOffset) if 'nicos.devices.abstract.CanReference' in classes: menu.addAction(self.actionReference) if 'nicos.devices.abstract.Coder' in classes: menu.addAction(self.actionSetPosition) if 'nicos.core.device.Moveable' in classes: if not menu.isEmpty(): menu.addSeparator() menu.addAction(self.actionFix) menu.addAction(self.actionRelease) if 'nicos.core.mixins.CanDisable' in classes: if not menu.isEmpty(): menu.addSeparator() menu.addAction(self.actionEnable) menu.addAction(self.actionDisable) if not menu.isEmpty(): menuBtn = QPushButton('More', self) menuBtn.setMenu(menu) self.moveBtns.addButton(menuBtn, QDialogButtonBox.ResetRole) def reset(checked): self.device_panel.exec_command('reset(%s)' % self.devrepr) def stop(checked): self.device_panel.exec_command('stop(%s)' % self.devrepr, immediate=True) self.moveBtns.addButton('Reset', QDialogButtonBox.ResetRole)\ .clicked.connect(reset) if 'nicos.core.device.Moveable' in classes or \ 'nicos.core.device.Measurable' in classes: self.moveBtns.addButton('Stop', QDialogButtonBox.ResetRole)\ .clicked.connect(stop) # show target and limits if the device is Moveable if 'nicos.core.device.Moveable' not in classes: self.limitFrame.setVisible(False) self.targetFrame.setVisible(False) else: if 'nicos.core.mixins.HasLimits' not in classes: self.limitFrame.setVisible(False) else: self.limitMin.setText(str(params['userlimits'][0])) self.limitMax.setText(str(params['userlimits'][1])) # insert a widget to enter a new device value # allowEnter=False because we catch pressing Enter ourselves self.target = DeviceValueEdit(self, dev=self.devname, useButtons=True, allowEnter=False) self.target.setClient(self.client) def btn_callback(target): self.device_panel.exec_command('move(%s, %s)' % (self.devrepr, srepr(target))) self.target.valueChosen.connect(btn_callback) self.targetFrame.layout().takeAt(1).widget().deleteLater() self.targetFrame.layout().insertWidget(1, self.target) def move(checked): try: target = self.target.getValue() except ValueError: return self.device_panel.exec_command('move(%s, %s)' % (self.devrepr, srepr(target))) if self.target.getValue() is not Ellipsis: # (button widget) self.moveBtn = self.moveBtns.addButton( 'Move', QDialogButtonBox.AcceptRole) self.moveBtn.clicked.connect(move) else: self.moveBtn = None if params.get('fixed') and self.moveBtn: self.moveBtn.setEnabled(False) self.moveBtn.setText('(fixed)') def on_paramList_customContextMenuRequested(self, pos): item = self.paramList.itemAt(pos) if not item: return menu = QMenu(self) refreshAction = menu.addAction('Refresh') menu.addAction('Refresh all') # QCursor.pos is more reliable then the given pos action = menu.exec_(QCursor.pos()) if action: cmd = 'session.getDevice(%r).pollParams(volatile_only=False%s)' \ % (self.devname, ', param_list=[%r]' % item.text(0) if action == refreshAction else '') # poll even non volatile parameter as requested explicitely self.client.eval(cmd, None) @pyqtSlot() def on_settingsBtn_clicked(self): self._show_extension(self.extension.isHidden()) def _show_extension(self, show): if show: # make "settings shown" permanent self.settingsBtn.hide() self.extension.setVisible(show) self.settingsBtn.setText('Settings %s' % ('<<<' if show else '>>>')) sz = self.size() sz.setHeight(self.sizeHint().height()) self.resize(sz) @pyqtSlot() def on_actionSetLimits_triggered(self): dlg = dialogFromUi(self, 'panels/devices_limits.ui') dlg.descLabel.setText('Adjust user limits of %s:' % self.devname) dlg.limitMin.setText(self.limitMin.text()) dlg.limitMax.setText(self.limitMax.text()) abslimits = self.client.getDeviceParam(self.devname, 'abslimits') offset = self.client.getDeviceParam(self.devname, 'offset') if offset is not None: abslimits = abslimits[0] - offset, abslimits[1] - offset dlg.limitMinAbs.setText(str(abslimits[0])) dlg.limitMaxAbs.setText(str(abslimits[1])) target = DeviceParamEdit(dlg, dev=self.devname, param='userlimits') target.setClient(self.client) btn = dlg.buttonBox.addButton('Reset to maximum range', QDialogButtonBox.ResetRole) def callback(): self.device_panel.exec_command('resetlimits(%s)' % self.devrepr) dlg.reject() btn.clicked.connect(callback) dlg.targetLayout.addWidget(target) res = dlg.exec_() if res != QDialog.Accepted: return newlimits = target.getValue() if newlimits[0] < abslimits[0] or newlimits[1] > abslimits[1]: QMessageBox.warning( self, 'Error', 'The entered limits are not ' 'within the absolute limits for the device.') # retry self.on_actionSetLimits_triggered() return self.device_panel.exec_command('set(%s, "userlimits", %s)' % (self.devrepr, newlimits)) def _get_new_value(self, window_title, desc): dlg = dialogFromUi(self, 'panels/devices_newpos.ui') dlg.setWindowTitle(window_title) dlg.descLabel.setText(desc) dlg.oldValue.setText(self.valuelabel.text()) target = DeviceValueEdit(dlg, dev=self.devname) target.setClient(self.client) dlg.targetLayout.addWidget(target) target.setFocus() res = dlg.exec_() if res != QDialog.Accepted: return None return target.getValue() @pyqtSlot() def on_actionAdjustOffset_triggered(self): val = self._get_new_value('Adjust NICOS offset', 'Adjust NICOS offset of %s:' % self.devname) if val is not None: self.device_panel.exec_command('adjust(%s, %r)' % (self.devrepr, val)) @pyqtSlot() def on_actionSetPosition_triggered(self): val = self._get_new_value( 'Set hardware position', 'Set hardware position of %s:' % self.devname) if val is not None: if self.devrepr != self.devname: cmd = 'CreateDevice(%s); %s.setPosition(%r)' % \ (self.devrepr, self.devname, val) else: cmd = '%s.setPosition(%r)' % (self.devname, val) self.device_panel.exec_command(cmd) @pyqtSlot() def on_actionReference_triggered(self): self.device_panel.exec_command('reference(%s)' % self.devrepr) @pyqtSlot() def on_actionFix_triggered(self): reason, ok = QInputDialog.getText( self, 'Fix', 'Please enter the reason for fixing %s:' % self.devname) if not ok: return self.device_panel.exec_command('fix(%s, %r)' % (self.devrepr, reason)) @pyqtSlot() def on_actionRelease_triggered(self): self.device_panel.exec_command('release(%s)' % self.devrepr) @pyqtSlot() def on_actionEnable_triggered(self): self.device_panel.exec_command('enable(%s)' % self.devrepr) @pyqtSlot() def on_actionDisable_triggered(self): self.device_panel.exec_command('disable(%s)' % self.devrepr) @pyqtSlot() def on_setAliasBtn_clicked(self): self.device_panel.exec_command( 'set(%s, "alias", %s)' % (self.devrepr, srepr(self.aliasTarget.currentText()))) def closeEvent(self, event): event.accept() self.closed.emit(self.devname.lower()) def on_cache(self, subkey, value): if subkey not in self.paramItems: return if not value: return value = cache_load(value) self.paramvalues[subkey] = value self.paramItems[subkey].setText(1, str(value)) def on_paramList_itemClicked(self, item): pname = item.text(0) self.editParam(pname) def editParam(self, pname): if not self.paraminfo[pname]['settable'] or self.client.viewonly: return mainunit = self.paramvalues.get('unit', 'main') punit = (self.paraminfo[pname]['unit'] or '').replace('main', mainunit) dlg = dialogFromUi(self, 'panels/devices_param.ui') dlg.target = DeviceParamEdit(self, dev=self.devname, param=pname) dlg.target.setClient(self.client) dlg.paramName.setText('Parameter: %s.%s' % (self.devname, pname)) dlg.paramDesc.setText(self.paraminfo[pname]['description']) dlg.paramValue.setText(str(self.paramvalues[pname]) + ' ' + punit) dlg.targetLayout.addWidget(dlg.target) dlg.resize(dlg.sizeHint()) dlg.target.setFocus() if dlg.exec_() != QDialog.Accepted: return try: new_value = dlg.target.getValue() except ValueError: self.log.exception('invalid value for typed value') # shouldn't happen, but if it does, at least give an indication that # something went wrong QMessageBox.warning( self, 'Error', 'The entered value is invalid ' 'for this parameter.') return if self.devrepr == self.devname: self.device_panel.exec_command('%s.%s = %r' % (self.devname, pname, new_value)) else: self.device_panel.exec_command( 'set(%s, %s, %r)' % (self.devrepr, srepr(pname), new_value)) def on_historyBtn_clicked(self): self.device_panel.plot_history(self.devname)
class ToftofProfileWindow(DlgUtils, QMainWindow): def __init__(self, parent): QMainWindow.__init__(self, parent) DlgUtils.__init__(self, 'Live data') self.panel = parent layout1 = QVBoxLayout() self.plot = QwtPlot(self) layout1.addWidget(self.plot) self.curve = QwtPlotCurve() self.curve.setRenderHint(QwtPlotCurve.RenderAntialiased) self.curve.attach(self.plot) self.marker = QwtPlotMarker() self.marker.attach(self.plot) self.markerpen = QPen(Qt.red) self.marker.setSymbol( QwtSymbol(QwtSymbol.Ellipse, QBrush(), self.markerpen, QSize(7, 7))) self.zoomer = QwtPlotZoomer(self.plot.canvas()) self.zoomer.setMousePattern(QwtPlotZoomer.MouseSelect3, Qt.NoButton) self.picker = QwtPlotPicker(self.plot.canvas()) self.picker.setSelectionFlags(QwtPlotPicker.PointSelection | QwtPlotPicker.ClickSelection) self.picker.setMousePattern(QwtPlotPicker.MouseSelect1, Qt.MidButton) self.picker.selected.connect(self.pickerSelected) layout2 = QHBoxLayout() layout2.addWidget(QLabel('Scale:', self)) self.scale = QComboBox(self) self.scale.addItems([ 'Single detectors, sorted by angle', 'Scattering angle 2theta (deg)', 'Q value (A-1)' ]) self.scale.currentIndexChanged[int].connect(self.scaleChanged) layout2.addWidget(self.scale) layout2.addStretch() self.scaleframe = QFrame(self) self.scaleframe.setLayout(layout2) self.scaleframe.setVisible(False) layout1.addWidget(self.scaleframe) mainframe = QFrame(self) mainframe.setLayout(layout1) self.setCentralWidget(mainframe) self.setContentsMargins(6, 6, 6, 6) plotfont = scaledFont(self.font(), 0.7) self.plot.setAxisFont(QwtPlot.xBottom, plotfont) self.plot.setAxisFont(QwtPlot.yLeft, plotfont) self.plot.setCanvasBackground(Qt.white) self.resize(800, 200) self._detinfo = None self._anglemap = None self._infowindow = None self._infolabel = None self._xs = self._ys = None self._type = None def _retrieve_detinfo(self): if self._detinfo is None: info = self.panel.client.eval( 'det._detinfo_parsed, ' 'det._anglemap', None) if not info: return self.showError('Cannot retrieve detector info.') self._lambda = self.panel.client.eval('chWL()', None) if not self._lambda: return self.showError('Cannot retrieve wavelength.') self._detinfo, self._anglemap = info self._inverse_anglemap = 0 self._infowindow = QMainWindow(self) self._infolabel = QLabel(self._infowindow) self._infolabel.setTextFormat(Qt.RichText) self._infowindow.setCentralWidget(self._infolabel) self._infowindow.setContentsMargins(10, 10, 10, 10) self._inv_anglemap = [[ entry for entry in self._detinfo[1:] if entry[12] == self._anglemap[detnr] + 1 ][0] for detnr in range(len(self._xs))] def scaleChanged(self, scale): self.update(self._type, self._orig_nbins, self._orig_x, self._orig_y) def update(self, proftype, nbins, x, y): self._orig_x = x self._orig_y = y self._orig_nbins = nbins x.setsize(8 * nbins) y.setsize(8 * nbins) xs = struct.unpack('d' * nbins, x) ys = struct.unpack('d' * nbins, y) if proftype == 0: if self.scale.currentIndex() == 0: xs = xs elif self.scale.currentIndex() == 1: self._retrieve_detinfo() xs = [self._inv_anglemap[int(xi)][5] for xi in xs] else: self._retrieve_detinfo() if self._lambda is None: self.showError('Could not determine wavelength.') self.scale.setCurrentIndex(1) return xs = [ 4 * pi / self._lambda * sin(radians(self._inv_anglemap[int(xi)][5] / 2.)) for xi in xs ] self._xs = xs self._ys = ys self.curve.setData(xs, ys) self.plot.setAxisAutoScale(QwtPlot.xBottom) self.plot.setAxisAutoScale(QwtPlot.yLeft) self.marker.setVisible(False) self.zoomer.setZoomBase(True) self._type = proftype if proftype == 0: self.setWindowTitle( 'Single detector view (time-channel integrated)') self.scaleframe.setVisible(True) elif proftype == 1: self.setWindowTitle('Time channel view (detector integrated)') self.scaleframe.setVisible(False) else: self.scaleframe.setVisible(False) def pickerSelected(self, point): if self._type != 0: return self._retrieve_detinfo() index = self.curve.closestPoint(self.picker.transform(point))[0] detentry = self._inv_anglemap[index][:] detentry.append(self._xs[index]) detentry.append(self._ys[index]) self.marker.setXValue(self._xs[index]) self.marker.setYValue(self._ys[index]) self.marker.setVisible(True) self.plot.replot() self._infowindow.show() entrynames = [ 'EntryNr', 'Rack', 'Plate', 'Pos', 'RPos', '2Theta', 'CableNr', 'CableType', 'CableLen', 'CableEmpty', 'Card', 'Chan', 'Total', 'DetName', 'BoxNr', 'BoxChan', 'XValue', 'Counts' ] formats = [ '%s', '%d', '%d', '%d', '%d', '%.3f', '%d', '%d', '%.2f', '%d', '%d', '%d', '%d', '%r', '%d', '%d', '%s', '%d' ] empties = [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0] self._infolabel.setText('Detector info:<br><table>' + ''.join( '<tr><td>%s</td><td></td><td>%s</td></tr>%s' % (name, format % value, '<tr></tr>' if empty else '') for (name, format, empty, value) in zip(entrynames, formats, empties, detentry)) + '</table>') def closeEvent(self, event): if self._infowindow: self._infowindow.close()