class ExpInfoPanel(Panel): """Provides a panel with several labels displaying basic experiment info. This is for example the experiment title, sample name, and user name. It also provides several buttons with which the user can change proposal info, sample properties, scan environment and setups. Options: * ``sample_panel`` -- what to show when the user clicks on the "Sample" button. The value must be a panel configuration, e.g. ``panel('...')`` or ``tabbed(...)``. There are several panels that are useful for this: - ``nicos.clients.gui.panels.setup_panel.GenericSamplePanel`` -- a panel that only shows a single input box for the sample name. - ``nicos.clients.gui.panels.setup_panel.TasSamplePanel`` -- a panel that also shows input boxes for triple-axis sample properties (such as lattice constants). * ``popup_proposal_after`` -- if given, the proposal dialog will be opened when the daemon has been idle for more than the specified time interval (in hours). * ``new_exp_panel`` -- class name of the panel which should be opened after a new experiment has been started from the proposal info panel. * ``finish_exp_panel`` -- class name of the panel which should be opened before an experiment is finished from the proposal info panel. """ panelName = 'Experiment Info' _viewonly = False def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, 'panels/expinfo.ui') for ch in self.findChildren(NicosWidget): ch.setClient(client) client.setup.connect(self.on_client_setup) client.initstatus.connect(self.on_client_initstatus) self.detLabel.setFormatCallback( lambda value, strvalue: ', '.join(sorted(value))) self.envLabel.setFormatCallback( lambda value, strvalue: ', '.join(sorted(value))) self._sample_panel = options.get('sample_panel', GenericSamplePanel) self._new_exp_panel = options.get('new_exp_panel') self._finish_exp_panel = options.get('finish_exp_panel') self._timeout = options.get('popup_proposal_after', 0) if self._timeout: self._proposal_popup_timer = QTimer(interval=self._timeout * 3600000) self._proposal_popup_timer.setSingleShot(True) self._proposal_popup_timer.timeout.connect( self.on_proposal_popup_timer_timeout) else: self._proposal_popup_timer = None def hideTitle(self): self.titleLbl.setVisible(False) def setViewOnly(self, viewonly): self._viewonly = viewonly if not self._viewonly and self._timeout: # ask explicitly for status to restart timer if necessary self.client.ask('getstatus') def on_client_initstatus(self, initstatus): self.setupLabel.setText(', '.join(sorted(initstatus['setups'][1]))) def on_client_setup(self, data): self.setupLabel.setText(', '.join(sorted(data[1]))) def updateStatus(self, status, exception=False): if self._proposal_popup_timer: if status == 'idle': if not self._viewonly or \ (self.client.user_level is not None and self.client.user_level < ADMIN): self._proposal_popup_timer.start() else: self._proposal_popup_timer.stop() def on_proposal_popup_timer_timeout(self): if self._viewonly: return dlg = QMessageBox(self) dlg.setText('The experiment has been idle for more than %.1f hours.' % self._timeout) contButton = QPushButton('Continue current experiment') finishAndNewButton = QPushButton('Finish and start new experiment') dlg.addButton(contButton, QMessageBox.RejectRole) dlg.addButton(finishAndNewButton, QMessageBox.ActionRole) dlg.exec_() if dlg.clickedButton() == finishAndNewButton: self.on_proposalBtn_clicked() elif dlg.clickedButton() == contButton: self._proposal_popup_timer.start() @pyqtSlot() def on_proposalBtn_clicked(self): dlg = PanelDialog(self, self.client, ExpPanel, 'Proposal info', new_exp_panel=self._new_exp_panel, finish_exp_panel=self._finish_exp_panel) dlg.exec_() @pyqtSlot() def on_setupBtn_clicked(self): dlg = PanelDialog(self, self.client, SetupsPanel, 'Setups') dlg.exec_() @pyqtSlot() def on_sampleBtn_clicked(self): dlg = PanelDialog(self, self.client, self._sample_panel, 'Sample information') dlg.exec_() @pyqtSlot() def on_detenvBtn_clicked(self): dlg = PanelDialog(self, self.client, DetEnvPanel, 'Detectors and environment') dlg.exec_() @pyqtSlot() def on_remarkBtn_clicked(self): dlg = dialogFromUi(self, 'panels/expinfo_remark.ui') def callback(): self.showInfo('The remark will be added to the logbook as a ' 'heading and will also appear in the data files.') dlg.buttonBox.helpRequested.connect(callback) for ch in dlg.findChildren(NicosWidget): ch.setClient(self.client) dlg.remarkEdit.setFocus() if not dlg.exec_(): return self.client.run('Remark(%r)' % dlg.remarkEdit.getValue())
class EntryWidget(base_class, ui_class): def __init__(self, client, watcher, entry, shortKey, showTimeStamp, showTTL, parent=None): base_class.__init__(self, parent) self.setupUi(self) self.updateTimer = QTimer(self) self.updateTimer.setSingleShot(True) self.watcher = watcher self.client = client self.entry = entry self.widgetValue = None self.setupEvents() self.setupWidgetUi(shortKey, showTimeStamp, showTTL) def setupEvents(self): """Sets up all events.""" self.buttonSet.clicked.connect(self.setKey) self.buttonDel.clicked.connect(self.delKey) self.buttonWatch.clicked.connect(self.watchKey) self.client.signals.keyUpdated.connect(self.keyUpdated) self.updateTimer.timeout.connect(self.updateTimerEvent) def setupWidgetUi(self, shortKey, showTimeStamp, showTTL): """ Sets up and generate a UI according to the data type of the value and whether or not time to live or time stamp should be shown. """ entry = self.entry fm = self.labelTime.fontMetrics() margins = self.labelTime.getContentsMargins() self.labelTime.setMinimumWidth( fm.width(entry.convertTime(1.0)) + margins[0] + margins[2] + self.labelTime.sizeHint().width()) if self.watcher is None: # widget is already in watcher self.buttonWatch.hide() if shortKey: self.labelKey.setText(entry.key.rpartition('/')[2]) self.labelKey.setToolTip(entry.key) else: self.labelKey.setText(entry.key) if entry.value in ('True', 'False'): self.widgetValue = ReadOnlyCheckBox() self.layoutWidget.insertWidget(4, self.widgetValue) self.layoutWidget.insertSpacerItem( 5, QSpacerItem(56, 20, QSizePolicy.Expanding)) else: self.widgetValue = QLineEdit() self.layoutWidget.insertWidget(4, self.widgetValue) self.widgetValue.setReadOnly(True) self.widgetValue.setToolTip(entry.key) if not showTTL: self.labelTTL.hide() if not showTimeStamp: self.labelTime.hide() self.updateValues() def updateValues(self): entry = self.entry if entry.expired: setBackgroundColor(self, expiredColor) elif entry.ttl: setBackgroundColor(self, ttlColor) if isinstance(self.widgetValue, ReadOnlyCheckBox): self.widgetValue.setChecked(entry.value == 'True') else: self.widgetValue.setText(entry.value) self.labelTTL.setText(str(entry.ttl or '')) self.labelTime.setText(entry.convertTime()) if entry.ttl: # automatically refresh the value if the entry has a ttl (we don't # get timestamp updates from the server unless the value changes) time_to_update = max((entry.time + entry.ttl) - time.time(), 0) self.updateTimer.start(time_to_update * 1000) def setKey(self): """Sets the key locally and on the server.""" dlg = EntryEditDialog(self) dlg.fillEntry(self.entry) dlg.valueTime.setText('') # we want current timestamp by default dlg.valueKey.setReadOnly(True) if dlg.exec_() != QDialog.Accepted: return entry = dlg.getEntry() self.client.put(entry.key, entry) def delKey(self): if QMessageBox.question(self, 'Delete', 'Really delete?', QMessageBox.Yes | QMessageBox.No) == QMessageBox.No: return self.client.delete(self.entry.key) def watchKey(self): """Adds our key to the watcher window.""" if not self.watcher: return widget = EntryWidget(self.client, None, self.entry, False, True, True, self.watcher) self.watcher.addWidgetKey(widget) self.watcher.show() def keyUpdated(self, key, entry): if key != self.entry.key: return self.entry = entry self.updateValues() def updateTimerEvent(self): self.client.update(self.entry.key)