def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, findResource('nicos_mlz/kws1/gui/sampleconf.ui')) self.sampleGroup.setEnabled(False) self.frame.setLayout(QVBoxLayout()) menu = QMenu(self) menu.addAction(self.actionCopyAperture) menu.addAction(self.actionCopyDetOffset) menu.addAction(self.actionCopyThickness) menu.addAction(self.actionCopyTimeFactor) menu.addSeparator() menu.addAction(self.actionCopyAll) self.copyBtn.setMenu(menu) menu = QMenu(self) menu.addAction(self.actionEmpty) menu.addAction(self.actionGenerate) self.createBtn.setMenu(menu) self.configs = [] self.dirty = False self.filename = None self.holder_info = options.get('holder_info', []) self.instrument = options.get('instrument', 'kws1')
def getMenus(self): if not self.menu: menu = QMenu('&Live data', self) menu.addAction(self.actionPrint) menu.addSeparator() menu.addAction(self.actionUnzoom) menu.addAction(self.actionLogScale) self.menu = menu return [self.menu]
def getMenus(self): if not self.menu: menu = QMenu('&Live data', self) menu.addAction(self.actionOpen) menu.addAction(self.actionPrint) menu.addSeparator() menu.addAction(self.actionKeepRatio) menu.addAction(self.actionUnzoom) menu.addAction(self.actionLogScale) menu.addAction(self.actionColormap) menu.addAction(self.actionMarkCenter) menu.addAction(self.actionROI) self.menu = menu return [self.menu]
def getMenus(self): menu = QMenu('&History viewer', self) menu.addAction(self.actionNew) menu.addSeparator() menu.addAction(self.actionSavePlot) menu.addAction(self.actionPrint) menu.addAction(self.actionAttachElog) menu.addAction(self.actionSaveData) menu.addSeparator() menu.addAction(self.actionEditView) menu.addAction(self.actionCloseView) menu.addAction(self.actionDeleteView) menu.addAction(self.actionResetView) menu.addSeparator() menu.addAction(self.actionLogScale) menu.addAction(self.actionAutoScale) menu.addAction(self.actionScaleX) menu.addAction(self.actionScaleY) menu.addAction(self.actionUnzoom) menu.addAction(self.actionLegend) menu.addAction(self.actionSymbols) menu.addAction(self.actionLines) ag = QActionGroup(menu) ag.addAction(self.actionFitPeakGaussian) ag.addAction(self.actionFitPeakLorentzian) ag.addAction(self.actionFitPeakPV) ag.addAction(self.actionFitPeakPVII) ag.addAction(self.actionFitTc) ag.addAction(self.actionFitCosine) ag.addAction(self.actionFitSigmoid) ag.addAction(self.actionFitLinear) ag.addAction(self.actionFitExponential) menu.addAction(self.actionFitPeak) menu.addAction(self.actionPickInitial) menu.addAction(self.actionFitPeakGaussian) menu.addAction(self.actionFitPeakLorentzian) menu.addAction(self.actionFitPeakPV) menu.addAction(self.actionFitPeakPVII) menu.addAction(self.actionFitTc) menu.addAction(self.actionFitCosine) menu.addAction(self.actionFitSigmoid) menu.addAction(self.actionFitLinear) menu.addAction(self.actionFitExponential) menu.addSeparator() menu.addAction(self.actionFitArby) menu.addSeparator() menu.addAction(self.actionClose) self._refresh_presets() return [menu, self.presetmenu]
def __init__(self, log, gui_conf, viewonly=False, tunnel=''): DefaultMainWindow.__init__(self, log, gui_conf, viewonly, tunnel) self.addLogo() self.addInstrument() self.addExperiment() self.set_icons() self.stylefile = gui_conf.stylefile # Cheesburger menu dropdown = QMenu('') dropdown.addAction(self.actionConnect) dropdown.addAction(self.actionViewOnly) dropdown.addAction(self.actionPreferences) dropdown.addAction(self.actionExpert) dropdown.addSeparator() dropdown.addAction(self.actionExit) self.actionUser.setMenu(dropdown) self.actionUser.setIconVisibleInMenu(True) self.dropdown = dropdown
def getMenus(self): if self._liveOnlyIndex is not None: return [] if not self.menu: menu = QMenu('&Live data', self) menu.addAction(self.actionOpen) menu.addAction(self.actionPrint) menu.addAction(self.actionSavePlot) menu.addSeparator() menu.addAction(self.actionKeepRatio) menu.addAction(self.actionUnzoom) menu.addAction(self.actionLogScale) menu.addAction(self.actionColormap) menu.addAction(self.actionMarkCenter) menu.addAction(self.actionROI) menu.addAction(self.actionSymbols) menu.addAction(self.actionLines) self.menu = menu return [self.menu]
def __init__(self, log, gui_conf, viewonly=False, tunnel=''): DefaultMainWindow.__init__(self, log, gui_conf, viewonly, tunnel) self.add_logo() self.set_icons() self.style_file = gui_conf.stylefile # Cheeseburger menu dropdown = QMenu('') dropdown.addAction(self.actionConnect) dropdown.addAction(self.actionViewOnly) dropdown.addAction(self.actionPreferences) dropdown.addAction(self.actionExpert) dropdown.addSeparator() dropdown.addAction(self.actionExit) self.actionUser.setMenu(dropdown) self.actionUser.setIconVisibleInMenu(True) self.dropdown = dropdown self.actionExpert.setEnabled(self.client.isconnected) self.actionEmergencyStop.setEnabled(self.client.isconnected) self._init_instrument_name() self._init_experiment_name()
def getMenus(self): if not self.menus: menu1 = QMenu('&Browser', self) menu1.addAction(self.actionBack) menu1.addAction(self.actionForward) menu1.addSeparator() menu1.addAction(self.actionRefresh) menu1.addAction(self.actionPrint) menu2 = QMenu('&Logbook', self) menu2.addAction(self.actionAddComment) menu2.addAction(self.actionAddRemark) menu2.addSeparator() menu2.addAction(self.actionAttachFile) menu2.addSeparator() menu2.addAction(self.actionNewSample) self.menus = [menu1, menu2] return self.menus
def getMenus(self): if not self.menus: menu1 = QMenu('&Data plot', self) menu1.addAction(self.actionSavePlot) menu1.addAction(self.actionPrint) menu1.addAction(self.actionAttachElog) menu1.addSeparator() menu1.addAction(self.actionResetPlot) menu1.addAction(self.actionAutoDisplay) menu1.addAction(self.actionCombine) menu1.addAction(self.actionClosePlot) menu1.addAction(self.actionDeletePlot) menu1.addSeparator() menu1.addAction(self.actionXAxis) menu1.addAction(self.actionYAxis) menu1.addAction(self.actionNormalized) menu1.addSeparator() menu1.addAction(self.actionUnzoom) menu1.addAction(self.actionLogXScale) menu1.addAction(self.actionLogScale) menu1.addAction(self.actionAutoScale) menu1.addAction(self.actionScaleX) menu1.addAction(self.actionScaleY) menu1.addAction(self.actionLegend) menu1.addAction(self.actionErrors) menu1.addSeparator() menu2 = QMenu('Data &manipulation', self) menu2.addAction(self.actionModifyData) menu2.addSeparator() ag = QActionGroup(menu2) ag.addAction(self.actionFitPeakGaussian) ag.addAction(self.actionFitPeakLorentzian) ag.addAction(self.actionFitPeakPV) ag.addAction(self.actionFitPeakPVII) ag.addAction(self.actionFitTc) ag.addAction(self.actionFitCosine) ag.addAction(self.actionFitSigmoid) ag.addAction(self.actionFitLinear) ag.addAction(self.actionFitExponential) menu2.addAction(self.actionFitPeak) menu2.addAction(self.actionPickInitial) menu2.addAction(self.actionFitPeakGaussian) menu2.addAction(self.actionFitPeakLorentzian) menu2.addAction(self.actionFitPeakPV) menu2.addAction(self.actionFitPeakPVII) menu2.addAction(self.actionFitTc) menu2.addAction(self.actionFitCosine) menu2.addAction(self.actionFitSigmoid) menu2.addAction(self.actionFitLinear) menu2.addAction(self.actionFitExponential) menu2.addSeparator() menu2.addAction(self.actionFitArby) self.menus = [menu1, menu2] return self.menus
class DevicesPanel1(DevicesPanel): def on_tree_customContextMenuRequested(self, point): item = self.tree.itemAt(point) if item is None: return if item.type() == DEVICE_TYPE: self._menu_dev = item.text(0) ldevname = self._menu_dev.lower() if charmpowersupply in self._devinfo[ldevname].classes: params = self.client.getDeviceParams(ldevname) self.cpsmenu = QMenu() self.cps_actions = [] i = 0 for menuItem in params['transitions']: self.cps_actions.append(QAction(menuItem)) self.cpsmenu.addAction(self.cps_actions[i]) self.cps_actions[i].triggered.connect( partial(self.on_actionApply_triggered, i)) i = i + 1 self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionMove) self.cpsmenu.addAction(self.actionReset) self.cpsmenu.addSeparator() if self.mainwindow.history_wintype is not None: self.cpsmenu.addAction(self.actionPlotHistory) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionShutDown) self.cpsmenu.addAction(self.actionHelp) self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point)) return if roimanager in self._devinfo[ldevname].classes: params = self.client.getDeviceParams(ldevname) self.cpsmenu = QMenu() self.cps_actions = [] self.cps_actions.append(QAction('Edit...')) self.cpsmenu.addAction(self.cps_actions[0]) self.cps_actions[0].triggered.connect( partial(self.on_roimanagerEdit, ldevname)) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionMove) self.cpsmenu.addAction(self.actionReset) self.cpsmenu.addSeparator() if self.mainwindow.history_wintype is not None: self.cpsmenu.addAction(self.actionPlotHistory) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionShutDown) self.cpsmenu.addAction(self.actionHelp) self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point)) return if compareimage in self._devinfo[ldevname].classes: params = self.client.getDeviceParams(ldevname) self.cpsmenu = QMenu() self.cps_actions = [] self.cps_actions.append(QAction('Show &Compare...')) self.cpsmenu.addAction(self.cps_actions[0]) self.cps_actions[0].triggered.connect( partial(self.on_images_compare, ldevname)) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionMove) self.cpsmenu.addAction(self.actionReset) self.cpsmenu.addSeparator() if self.mainwindow.history_wintype is not None: self.cpsmenu.addAction(self.actionPlotHistory) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionShutDown) self.cpsmenu.addAction(self.actionHelp) self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point)) return if playlistmanager in self._devinfo[ldevname].classes: params = self.client.getDeviceParams(ldevname) self.cpsmenu = QMenu() self.cps_actions = [] self.cps_actions.append(QAction('Edit...')) self.cpsmenu.addAction(self.cps_actions[0]) self.cps_actions[0].triggered.connect( partial(self.on_playlist_edit, ldevname)) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionMove) self.cpsmenu.addAction(self.actionReset) self.cpsmenu.addSeparator() if self.mainwindow.history_wintype is not None: self.cpsmenu.addAction(self.actionPlotHistory) self.cpsmenu.addSeparator() self.cpsmenu.addAction(self.actionShutDown) self.cpsmenu.addAction(self.actionHelp) self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point)) return return super().on_tree_customContextMenuRequested(point) @pyqtSlot() def on_playlist_edit(self, ldevname): if not playlisteditor.win: playlisteditor.win = playlisteditor.Window(self.client, ldevname) playlisteditor.win.show() @pyqtSlot() def on_images_compare(self, ldevname): if not imagecompare.win: imagecompare.win = imagecompare.Window(self.client, ldevname) imagecompare.win.show() @pyqtSlot() def on_roimanagerEdit(self, ldevname): if not roieditor.win: roieditor.win = roieditor.Window(self.client, ldevname) roieditor.win.show() @pyqtSlot() def on_actionApply_triggered(self, index): if self._menu_dev: self.client.eval(self._menu_dev + '.apply(' + str(index) + ')') def on_client_cache(self, data): rv = super().on_client_cache(data) # here we truncate the long status message for charmpowersupply devices (time, key, op, value) = data if '/' not in key: return ldevname, subkey = key.rsplit('/', 1) if ldevname not in self._devinfo: return devitem = self._devitems[ldevname] devinfo = self._devinfo[ldevname] if charmpowersupply in self._devinfo[ldevname].classes: if subkey == 'status': t = devitem.text(2) i = t.rfind('[') if i >= 0: t = t[:i - 1] devitem.setText(2, str(t)) if ldevname in self._control_dialogs: dlg = self._control_dialogs[ldevname] ct = dlg.statuslabel.text() i = ct.rfind('[') if i >= 0: ct = ct[:i - 1] dlg.statuslabel.setText(ct) return rv
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)')
class DevicesPanel(Panel): """Provides a graphical list of NICOS devices and their current values. The user can operate basic device functions (move, stop, reset) by selecting an item from the list, which opens a control dialog. Options: * ``useicons`` (default True) -- if set to False, the list widget does not display status icons for the devices. * ``param_display`` (default {}) -- a dictionary containing the device name as key and a parameter name or a list of the parameter names which should be displayed in the device tree as subitems of the device item, for example:: param_display = { 'tas': 'scanmode', 'Exp': ['lastpoint', 'lastscan'] } * ``filters`` (default []) -- a list of tuples containing the name of the filter and the regular expression to filter out the devices. example:: filters = [ ('All', ''), ('Default', 'T|UBahn'), ('Foo', 'bar$'), ] """ panelName = 'Devices' ui = 'panels/devices.ui' @classmethod def _createIcons(cls): # hack to make non-Qt usage as in checksetups work if not hasattr(cls, 'statusIcon'): cls.statusIcon = { OK: QIcon(':/leds/status_green'), WARN: QIcon(':/leds/status_warn'), BUSY: QIcon(':/leds/status_yellow'), NOTREACHED: QIcon(':/leds/status_red'), DISABLED: QIcon(':/leds/status_white'), ERROR: QIcon(':/leds/status_red'), UNKNOWN: QIcon(':/leds/status_unknown'), } @property def groupIcon(self): return QIcon(':/setup') def __init__(self, parent, client, options): DevicesPanel._createIcons() Panel.__init__(self, parent, client, options) loadUi(self, self.ui) self.useicons = bool(options.get('icons', True)) self.param_display = {} param_display = options.get('param_display', {}) for (key, value) in param_display.items(): value = [value] if isinstance(value, string_types) else list(value) self.param_display[key.lower()] = value self.tree.header().restoreState(self._headerstate) self.clear() self.devmenu = QMenu(self) self.devmenu.addAction(self.actionMove) self.devmenu.addAction(self.actionStop) self.devmenu.addAction(self.actionReset) self.devmenu.addSeparator() self.devmenu.addAction(self.actionFix) self.devmenu.addAction(self.actionRelease) self.devmenu.addSeparator() if self.mainwindow.history_wintype is not None: self.devmenu.addAction(self.actionPlotHistory) self.devmenu.addSeparator() self.devmenu.addAction(self.actionShutDown) self.devmenu.addAction(self.actionHelp) self.devmenu_ro = QMenu(self) self.devmenu_ro.addAction(self.actionMove) self.devmenu_ro.addAction(self.actionReset) self.devmenu_ro.addSeparator() if self.mainwindow.history_wintype is not None: self.devmenu_ro.addAction(self.actionPlotHistory) self.devmenu_ro.addSeparator() self.devmenu_ro.addAction(self.actionShutDown) self.devmenu_ro.addAction(self.actionHelp) self._menu_dev = None # device for which context menu is shown self._dev2setup = {} self._setupinfo = {} self._control_dialogs = {} self._show_lowlevel = self.mainwindow.expertmode # daemon request ID of last command executed from this panel # (used to display messages from this command) self._current_status = 'idle' self._exec_reqid = None self._error_window = None client.connected.connect(self.on_client_connected) client.disconnected.connect(self.on_client_disconnected) client.cache.connect(self.on_client_cache) client.device.connect(self.on_client_device) client.setup.connect(self.on_client_setup) client.message.connect(self.on_client_message) self.filters = options.get('filters', []) self.filter.addItem('') for text, rx in self.filters: self.filter.addItem('Filter: %s' % text, rx) self.filter.lineEdit().setPlaceholderText('Enter search expression') def updateStatus(self, status, exception=False): self._current_status = status def saveSettings(self, settings): settings.setValue('headers', self.tree.header().saveState()) def loadSettings(self, settings): self._headerstate = settings.value('headers', '', QByteArray) def _update_view(self): with self.sgroup as settings: for i in range(self.tree.topLevelItemCount()): v = settings.value( '%s/expanded' % self.tree.topLevelItem(i).text(0), True, bool) self.tree.topLevelItem(i).setExpanded(v) def _store_view(self): with self.sgroup as settings: for i in range(self.tree.topLevelItemCount()): settings.setValue( '%s/expanded' % self.tree.topLevelItem(i).text(0), self.tree.topLevelItem(i).isExpanded()) def hideTitle(self): self.titleLbl.setVisible(False) def setExpertMode(self, expert): self._show_lowlevel = expert self.on_client_connected() def clear(self): if self.tree: self._store_view() self._catitems = {} # map lowercased devname -> tree widget item self._devitems = {} self._devparamitems = {} # map lowercased devname -> DevInfo instance self._devinfo = {} self.tree.clear() def on_client_connected(self): self.clear() state = self.client.ask('getstatus') if not state: return devlist = state['devices'] self._read_setup_info(state['setups']) for devname in devlist: self._create_device_item(devname) # close all control dialogs for now nonexisting devices for ldevname in list(self._control_dialogs): if ldevname not in self._devitems: self._control_dialogs[ldevname].close() # add all toplevel items to the tree, sorted for cat in self._catitems: self.tree.addTopLevelItem(self._catitems[cat]) self._catitems[cat].setExpanded(True) for devitem in itervalues(self._devitems): devitem.setExpanded(True) self.tree.sortItems(0, Qt.AscendingOrder) self._update_view() def on_client_disconnected(self): self.clear() def on_client_message(self, message): # show warnings and errors emitted by the current command in a window if message[5] != self._exec_reqid or message[2] < WARNING: return msg = '%s: %s' % (message[0], message[3].strip()) if self._error_window is None: def reset_errorwindow(): self._error_window = None self._error_window = ErrorDialog(self) self._error_window.accepted.connect(reset_errorwindow) self._error_window.addMessage(msg) self._error_window.show() else: self._error_window.addMessage(msg) self._error_window.activateWindow() def _read_setup_info(self, setuplists=None): if setuplists is None: allstatus = self.client.ask('getstatus') if allstatus is None: return setuplists = allstatus['setups'] loaded_setups = set(setuplists[0]) self._dev2setup = {} self._setupinfo = self.client.eval('session.getSetupInfo()', {}) if self._setupinfo is None: self.log.warning('session.getSetupInfo() returned None instead ' 'of {}') return for setupname, info in iteritems(self._setupinfo): if info is None: continue if setupname not in loaded_setups: continue for devname in info['devices']: self._dev2setup[devname] = setupname def _create_device_item(self, devname, add_cat=False): ldevname = devname.lower() # get all cache keys pertaining to the device params = self.client.getDeviceParams(devname) if not params: return lowlevel_device = params.get('lowlevel') or False if lowlevel_device and not self._show_lowlevel: return if 'nicos.core.data.sink.DataSink' in params.get('classes', []) and \ not self._show_lowlevel: return # remove still-existing previous item for the same device name if ldevname in self._devitems: self.on_client_device(('destroy', [devname])) cat = self._dev2setup.get(devname) if cat is None: # device is not in any setup? reread setup info self._read_setup_info() cat = self._dev2setup.get(devname) if cat is None: # still not there -> give up return if cat not in self._catitems: display_order = self._setupinfo[cat].get('display_order', 50) representative = self._setupinfo[cat].get('extended', {}).get( 'representative', '').lower() catitem = SetupTreeWidgetItem(cat, display_order, representative) catitem.setToolTip(0, self._setupinfo[cat].get('description', '')) f = catitem.font(0) f.setBold(True) catitem.setFont(0, f) catitem.setIcon(0, self.groupIcon) self._catitems[cat] = catitem if add_cat: self.tree.addTopLevelItem(catitem) catitem.setExpanded(True) else: catitem = self._catitems[cat] # create a tree node for the device devitem = QTreeWidgetItem(catitem, [devname, '', ''], DEVICE_TYPE) devitem.setForeground(0, lowlevelBrush[lowlevel_device]) devitem.setFont(0, lowlevelFont[lowlevel_device]) if self.useicons: devitem.setIcon(0, self.statusIcon[OK]) devitem.setToolTip(0, params.get('description', '')) self._devitems[ldevname] = devitem # fill the device info with dummy values, will be populated below self._devinfo[ldevname] = DevInfo(devname) # let the cache handler process all properties for key, value in iteritems(params): self.on_client_cache( (0, ldevname + '/' + key, OP_TELL, cache_dump(value))) def on_client_setup(self, setuplists): # update setup tooltips self._read_setup_info(setuplists) for i in range(self.tree.topLevelItemCount()): catitem = self.tree.topLevelItem(i) cat = catitem.text(0) catitem.setToolTip(0, self._setupinfo[cat].get('description', '')) def on_client_device(self, data): (action, devlist) = data if not devlist: return if action == 'create': for devname in devlist: self._create_device_item(devname, add_cat=True) self.tree.sortItems(0, Qt.AscendingOrder) self._update_view() elif action == 'destroy': self._store_view() for devname in devlist: ldevname = devname.lower() if ldevname in self._devitems: # remove device item and cached info... item = self._devitems[ldevname] del self._devitems[ldevname] del self._devinfo[ldevname] self._devparamitems.pop(ldevname, None) try: catitem = item.parent() except RuntimeError: # Qt object has already been destroyed pass else: catitem.removeChild(item) # remove category item if it has no further children if catitem.childCount() == 0: self.tree.takeTopLevelItem( self.tree.indexOfTopLevelItem(catitem)) del self._catitems[catitem.text(0)] self._update_view() def on_client_cache(self, data): (time, key, op, value) = data if '/' not in key: return ldevname, subkey = key.rsplit('/', 1) if ldevname not in self._devinfo: return if ldevname in self._control_dialogs: self._control_dialogs[ldevname].on_cache(subkey, value) devitem = self._devitems[ldevname] devinfo = self._devinfo[ldevname] if subkey == 'value': if time < devinfo.valtime: return if not value: fvalue = '' else: fvalue = cache_load(value) if isinstance(fvalue, list): fvalue = tuple(fvalue) devinfo.value = fvalue devinfo.expired = op != OP_TELL devinfo.valtime = time fmted = devinfo.fmtValUnit() devitem.setText(1, fmted) if ldevname in self._control_dialogs: self._control_dialogs[ldevname].valuelabel.setText(fmted) devitem.setForeground(1, valueBrush[devinfo.expired, devinfo.fixed]) if not devitem.parent().isExpanded(): if ldevname == devitem.parent().representative: devitem.parent().setText(1, fmted) elif subkey == 'status': if time < devinfo.stattime: return if not value: status = (UNKNOWN, '?') else: status = cache_load(value) devinfo.status = status devinfo.stattime = time devitem.setText(2, str(status[1])) if status[0] not in self.statusIcon: # old or wrong status constant return if self.useicons: devitem.setIcon(0, self.statusIcon[status[0]]) devitem.setForeground(2, foregroundBrush[status[0]]) devitem.setBackground(2, backgroundBrush[status[0]]) else: devitem.setForeground(0, foregroundBrush[BUSY]) devitem.setBackground(0, backgroundBrush[status[0]]) if not devitem.parent().isExpanded(): item = devitem.parent() item.setBackground( 0, backgroundBrush[self._getHighestStatus(item)]) else: devitem.parent().setBackground(0, backgroundBrush[OK]) if ldevname in self._control_dialogs: dlg = self._control_dialogs[ldevname] dlg.statuslabel.setText(status[1]) dlg.statusimage.setPixmap(self.statusIcon[status[0]].pixmap( 16, 16)) setForegroundBrush(dlg.statuslabel, foregroundBrush[status[0]]) setBackgroundBrush(dlg.statuslabel, backgroundBrush[status[0]]) elif subkey == 'fmtstr': if not value: return devinfo.fmtstr = cache_load(value) devitem.setText(1, devinfo.fmtValUnit()) elif subkey == 'unit': if not value: value = "''" devinfo.unit = cache_load(value) devitem.setText(1, devinfo.fmtValUnit()) elif subkey == 'fixed': if not value: value = "''" devinfo.fixed = bool(cache_load(value)) devitem.setForeground(1, valueBrush[devinfo.expired, devinfo.fixed]) if ldevname in self._control_dialogs: dlg = self._control_dialogs[ldevname] if dlg.moveBtn: dlg.moveBtn.setEnabled(not devinfo.fixed) dlg.moveBtn.setText(devinfo.fixed and '(fixed)' or 'Move') elif subkey == 'userlimits': if not value: return value = cache_load(value) if ldevname in self._control_dialogs: dlg = self._control_dialogs[ldevname] dlg.limitMin.setText(str(value[0])) dlg.limitMax.setText(str(value[1])) elif subkey == 'classes': if not value: value = "[]" devinfo.classes = set(cache_load(value)) elif subkey == 'alias': if not value: return if ldevname in self._control_dialogs: dlg = self._control_dialogs[ldevname] dlg._reinit() elif subkey == 'description': devitem.setToolTip(0, cache_load(value or "''")) if subkey in self.param_display.get(ldevname, ()): if not devinfo.params: devinfo.params = self.client.getDeviceParamInfo(devinfo.name) value = devinfo.fmtParam(subkey, cache_load(value)) if subkey not in self._devparamitems.setdefault(ldevname, {}): devitem = self._devitems[ldevname] self._devparamitems[ldevname][subkey] = \ QTreeWidgetItem(devitem, [subkey, value, ''], PARAM_TYPE) devitem.setExpanded(True) else: self._devparamitems[ldevname][subkey].setText(1, value) def on_tree_itemExpanded(self, item): if item.type() == SETUP_TYPE: item.setText(1, '') item.setBackground(0, backgroundBrush[OK]) def _getHighestStatus(self, item): retval = OK for i in range(item.childCount()): lstatus = self._devinfo[item.child(i).text(0).lower()].status[0] if retval < lstatus: retval = lstatus return retval def on_tree_itemCollapsed(self, item): if item.type() == SETUP_TYPE: item.setBackground(0, backgroundBrush[self._getHighestStatus(item)]) if item.representative: item.setText(1, self._devitems[item.representative].text(1)) def on_tree_customContextMenuRequested(self, point): item = self.tree.itemAt(point) if item is None: return if item.type() == DEVICE_TYPE: self._menu_dev = item.text(0) ldevname = self._menu_dev.lower() if 'nicos.core.device.Moveable' in self._devinfo[ldevname].classes and \ not self.client.viewonly: self.devmenu.popup(self.tree.viewport().mapToGlobal(point)) elif 'nicos.core.device.Readable' in self._devinfo[ ldevname].classes: self.devmenu_ro.popup(self.tree.viewport().mapToGlobal(point)) def on_filter_editTextChanged(self, text): for i in range(self.filter.count()): if text == self.filter.itemText(i): rx = QRegExp(self.filter.itemData(i)) break else: rx = QRegExp(text) for i in range(self.tree.topLevelItemCount()): setupitem = self.tree.topLevelItem(i) all_children_hidden = True for j in range(setupitem.childCount()): devitem = setupitem.child(j) if rx.indexIn(devitem.text(0)) == -1: devitem.setHidden(True) else: devitem.setHidden(False) all_children_hidden = False setupitem.setHidden(all_children_hidden) @pyqtSlot() def on_actionShutDown_triggered(self): if self._menu_dev: if self.askQuestion('This will unload the device until the setup ' 'is loaded again. Proceed?'): self.exec_command('RemoveDevice(%s)' % srepr(self._menu_dev), ask_queue=False) @pyqtSlot() def on_actionReset_triggered(self): if self._menu_dev: self.exec_command('reset(%s)' % srepr(self._menu_dev)) @pyqtSlot() def on_actionFix_triggered(self): if self._menu_dev: reason, ok = QInputDialog.getText( self, 'Fix', 'Please enter the reason for fixing %s:' % self._menu_dev) if not ok: return self.exec_command('fix(%s, %r)' % (srepr(self._menu_dev), reason)) @pyqtSlot() def on_actionRelease_triggered(self): if self._menu_dev: self.exec_command('release(%s)' % srepr(self._menu_dev)) @pyqtSlot() def on_actionStop_triggered(self): if self._menu_dev: self.exec_command('stop(%s)' % srepr(self._menu_dev), immediate=True) @pyqtSlot() def on_actionMove_triggered(self): if self._menu_dev: self._open_control_dialog(self._menu_dev) @pyqtSlot() def on_actionHelp_triggered(self): if self._menu_dev: self.client.eval('session.showHelp(session.devices[%r])' % self._menu_dev) @pyqtSlot() def on_actionPlotHistory_triggered(self): if self._menu_dev: self.plot_history(self._menu_dev) def on_tree_itemActivated(self, item, column): if item.type() == DEVICE_TYPE: devname = item.text(0) self._open_control_dialog(devname) elif item.type() == PARAM_TYPE: devname = item.parent().text(0) dlg = self._open_control_dialog(devname) dlg.editParam(item.text(0)) def _open_control_dialog(self, devname): ldevname = devname.lower() if ldevname in self._control_dialogs: dlg = self._control_dialogs[ldevname] if dlg.isVisible(): dlg.activateWindow() return dlg devinfo = self._devinfo[ldevname] item = self._devitems[ldevname] dlg = ControlDialog(self, devname, devinfo, item, self.log, self._show_lowlevel) dlg.closed.connect(self._control_dialog_closed) dlg.rejected.connect(dlg.close) self._control_dialogs[ldevname] = dlg dlg.show() return dlg def _control_dialog_closed(self, ldevname): dlg = self._control_dialogs.pop(ldevname, None) if dlg: dlg.deleteLater() # API shared with ControlDialog def exec_command(self, command, ask_queue=True, immediate=False): if ask_queue and not immediate and self._current_status != 'idle': qwindow = ScriptExecQuestion() result = qwindow.exec_() if result == QMessageBox.Cancel: return elif result == QMessageBox.Apply: immediate = True if immediate: self.client.tell('exec', command) self._exec_reqid = None # no request assigned to this command else: self._exec_reqid = self.client.run(command) def plot_history(self, dev): if self.mainwindow.history_wintype is not None: win = self.mainwindow.createWindow(self.mainwindow.history_wintype) if win: panel = win.getPanel('History viewer') panel.newView(dev) showPanel(panel)
def getMenus(self): menuFile = QMenu('&File', self) menuFile.addAction(self.actionNew) menuFile.addAction(self.actionOpen) menuFile.addAction(self.menuRecent.menuAction()) menuFile.addAction(self.actionSave) menuFile.addAction(self.actionSaveAs) menuFile.addAction(self.actionReload) menuFile.addSeparator() menuFile.addAction(self.actionPrint) menuView = QMenu('&View', self) menuView.addAction(self.actionShowScripts) menuEdit = QMenu('&Edit', self) menuEdit.addAction(self.actionUndo) menuEdit.addAction(self.actionRedo) menuEdit.addSeparator() menuEdit.addAction(self.actionCut) menuEdit.addAction(self.actionCopy) menuEdit.addAction(self.actionPaste) menuEdit.addSeparator() menuEdit.addAction(self.actionComment) menuEdit.addSeparator() menuEdit.addAction(self.actionFind) menuScript = QMenu('&Script', self) menuScript.addSeparator() menuScript.addAction(self.actionRun) menuScript.addAction(self.actionSimulate) menuScript.addAction(self.actionUpdate) menuScript.addSeparator() menuScript.addAction(self.actionGet) if self.toolconfig: menuTools = QMenu('Editor t&ools', self) createToolMenu(self, self.toolconfig, menuTools) menus = [menuFile, menuView, menuEdit, menuScript, menuTools] else: menus = [menuFile, menuView, menuEdit, menuScript] self.menus = menus return self.menus
def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, 'panels/status.ui') self.stopcounting = False self.menus = None self.bar = None self.queueFrame.hide() self.statusLabel.hide() self.pause_color = QColor('#ffdddd') self.idle_color = parent.user_color self.script_queue = ScriptQueue(self.queueFrame, self.queueView) self.current_line = -1 self.current_request = {} self.curlineicon = QIcon(':/currentline') self.errlineicon = QIcon(':/errorline') empty = QPixmap(16, 16) empty.fill(Qt.transparent) self.otherlineicon = QIcon(empty) self.traceView.setItemDelegate(LineDelegate(24, self.traceView)) self.stopcounting = bool(options.get('stopcounting', False)) if self.stopcounting: tooltip = 'Aborts the current executed script' self.actionStop.setToolTip(tooltip) self.actionStop.setText('Abort current script') self.actionStop2.setToolTip(tooltip) self.showETA = bool(options.get('eta', False)) self.etaWidget.hide() client.request.connect(self.on_client_request) client.processing.connect(self.on_client_processing) client.blocked.connect(self.on_client_blocked) client.status.connect(self.on_client_status) client.initstatus.connect(self.on_client_initstatus) client.disconnected.connect(self.on_client_disconnected) client.rearranged.connect(self.on_client_rearranged) client.updated.connect(self.on_client_updated) client.eta.connect(self.on_client_eta) bar = QToolBar('Script control') bar.setObjectName(bar.windowTitle()) # unfortunately it is not wise to put a menu in its own dropdown menu, # so we have to duplicate the actionBreak and actionStop... dropdown1 = QMenu('', self) dropdown1.addAction(self.actionBreak) dropdown1.addAction(self.actionBreakCount) dropdown1.addAction(self.actionFinishEarly) self.actionBreak2.setMenu(dropdown1) dropdown2 = QMenu('', self) dropdown2.addAction(self.actionStop) dropdown2.addAction(self.actionFinish) dropdown2.addAction(self.actionFinishEarlyAndStop) self.actionStop2.setMenu(dropdown2) bar.addAction(self.actionBreak2) bar.addAction(self.actionContinue) bar.addAction(self.actionStop2) bar.addAction(self.actionEmergencyStop) self.bar = bar # self.mainwindow.addToolBar(bar) menu = QMenu('&Script control', self) menu.addAction(self.actionBreak) menu.addAction(self.actionBreakCount) menu.addAction(self.actionContinue) menu.addAction(self.actionFinishEarly) menu.addSeparator() menu.addAction(self.actionStop) menu.addAction(self.actionFinish) menu.addAction(self.actionFinishEarlyAndStop) menu.addSeparator() menu.addAction(self.actionEmergencyStop) self.mainwindow.menuBar().insertMenu( self.mainwindow.menuWindows.menuAction(), menu) self.activeGroup = QActionGroup(self) self.activeGroup.addAction(self.actionBreak) self.activeGroup.addAction(self.actionBreak2) self.activeGroup.addAction(self.actionBreakCount) self.activeGroup.addAction(self.actionContinue) self.activeGroup.addAction(self.actionStop) self.activeGroup.addAction(self.actionStop2) self.activeGroup.addAction(self.actionFinish) self.activeGroup.addAction(self.actionFinishEarly) self.activeGroup.addAction(self.actionFinishEarlyAndStop) self._status = 'idle'
class MainWindow(DlgUtils, QMainWindow): name = 'MainWindow' # Emitted when a panel generates code that an editor panel should add. codeGenerated = pyqtSignal(object) # Interval (in ms) to make "keepalive" queries to the daemon. keepaliveInterval = 12 * 3600 * 1000 ui = 'main.ui' def __init__(self, log, gui_conf, viewonly=False, tunnel=''): QMainWindow.__init__(self) DlgUtils.__init__(self, 'NICOS') loadUi(self, self.ui) # set app icon in multiple sizes icon = QIcon() icon.addFile(':/appicon') icon.addFile(':/appicon-16') icon.addFile(':/appicon-48') self.setWindowIcon(icon) if tunnel and SSHTunnelForwarder is None: self.showError('You want to establish a connection to NICOS via ' "a SSH tunnel, but the 'sshtunnel' module is not " 'installed. The tunneling feature will disabled.') self.tunnel = tunnel if SSHTunnelForwarder is not None else '' self.tunnelServer = None # hide admin label until we are connected as admin self.adminLabel.hide() # our logger instance self.log = log # window for displaying errors self.errorWindow = None # window for "prompt" event confirmation self.promptWindow = None # debug console window, if opened self.debugConsole = None # log messages sent by the server self.messages = [] # are we in expert mode? (always false on startup) self.expertmode = False # no wrapping at startup self.allowoutputlinewrap = False # set-up the initial connection data self.conndata = ConnectionData(host='localhost', port=1301, user='******', password=None, viewonly=viewonly) # state members self.current_status = None # connect the client's events self.client = NicosGuiClient(self, self.log) self.client.error.connect(self.on_client_error) self.client.broken.connect(self.on_client_broken) self.client.failed.connect(self.on_client_failed) self.client.connected.connect(self.on_client_connected) self.client.disconnected.connect(self.on_client_disconnected) self.client.status.connect(self.on_client_status) self.client.showhelp.connect(self.on_client_showhelp) self.client.clientexec.connect(self.on_client_clientexec) self.client.plugplay.connect(self.on_client_plugplay) self.client.watchdog.connect(self.on_client_watchdog) self.client.prompt.connect(self.on_client_prompt) # data handling setup self.data = DataHandler(self.client) # panel configuration self.gui_conf = gui_conf self.initDataReaders() self.mainwindow = self # determine if there is an editor window type, because we would like to # have a way to open files from a console panel later self.editor_wintype = self.gui_conf.find_panel( ('editor.EditorPanel', 'nicos.clients.gui.panels.editor.EditorPanel')) self.history_wintype = self.gui_conf.find_panel( ('history.HistoryPanel', 'nicos.clients.gui.panels.history.HistoryPanel')) # additional panels self.panels = [] self.splitters = [] self.windowtypes = [] self.windows = {} # setting presets self.instrument = self.gui_conf.name self.createWindowContent() # timer for reconnecting self.reconnectTimer = QTimer(singleShot=True, timeout=self._reconnect) self._reconnect_count = 0 self._reconnect_time = 0 # timer for session keepalive, every 12 hours self.keepaliveTimer = QTimer(singleShot=False, timeout=self._keepalive) self.keepaliveTimer.start(self.keepaliveInterval) # setup tray icon self.trayIcon = QSystemTrayIcon(self) self.trayIcon.activated.connect(self.on_trayIcon_activated) self.trayMenu = QMenu(self) nameAction = self.trayMenu.addAction(self.instrument) nameAction.setEnabled(False) self.trayMenu.addSeparator() toggleAction = self.trayMenu.addAction('Hide main window') toggleAction.setCheckable(True) toggleAction.triggered[bool].connect( lambda hide: self.setVisible(not hide)) self.trayIcon.setContextMenu(self.trayMenu) # help window self.helpWindow = None # watchdog window self.watchdogWindow = None # plug-n-play notification windows self.pnpWindows = {} # create initial state self._init_toolbar() def _init_toolbar(self): self.statusLabel = QLabel('', self, pixmap=QPixmap(':/disconnected'), margin=5, minimumSize=QSize(30, 10)) self.toolbar = self.toolBarMain self.toolbar.addWidget(self.statusLabel) self.setStatus('disconnected') def addPanel(self, panel, always=True): if always or panel not in self.panels: self.panels.append(panel) def createWindowContent(self): self.sgroup = SettingGroup('MainWindow') with self.sgroup as settings: loadUserStyle(self, settings) # load saved settings and stored layout for panel config self.loadSettings(settings) # create panels in the main window widget = createWindowItem(self.gui_conf.main_window, self, self, self, self.log) if widget: self.centralLayout.addWidget(widget) self.centralLayout.setContentsMargins(0, 0, 0, 0) # call postInit after creation of all panels for panel in self.panels: panel.postInit() with self.sgroup as settings: # geometry and window appearance loadBasicWindowSettings(self, settings) self.update() # load auxiliary windows state self.loadAuxWindows(settings) if len(self.splitstate) == len(self.splitters): for sp, st in zip(self.splitters, self.splitstate): sp.restoreState(st) if not self.gui_conf.windows: self.menuBar().removeAction(self.menuWindows.menuAction()) for i, wconfig in enumerate(self.gui_conf.windows): action = ToolAction(self.client, QIcon(':/' + wconfig.icon), wconfig.name, wconfig.options, self) self.toolBarWindows.addAction(action) self.menuWindows.addAction(action) def window_callback(on, i=i): self.createWindow(i) action.triggered[bool].connect(window_callback) if not self.gui_conf.windows: self.toolBarWindows.hide() else: self.toolBarWindows.show() createToolMenu(self, self.gui_conf.tools, self.menuTools) if isinstance(self.gui_conf.main_window, tabbed) and widget: widget.tabChangedTab(0) def createWindow(self, wtype): # for the history_wintype or editor_wintype if wtype == -1: return self try: wconfig = self.gui_conf.windows[wtype] except IndexError: # config outdated, window type doesn't exist return if wtype in self.windows: window = self.windows[wtype] window.activateWindow() return window window = AuxiliaryWindow(self, wtype, wconfig) if window.centralLayout.count(): window.setWindowIcon(QIcon(':/' + wconfig.icon)) self.windows[wtype] = window window.closed.connect(self.on_auxWindow_closed) for panel in window.panels: panel.updateStatus(self.current_status) window.show() return window else: del window return None def getPanel(self, panelName): for panelobj in self.panels: if panelobj.panelName == panelName: return panelobj def initDataReaders(self): try: # just import to register all default readers # pylint: disable=unused-import import nicos.devices.datasinks except ImportError: pass classes = self.gui_conf.options.get('reader_classes', []) for clsname in classes: try: importString(clsname) except ImportError: pass def on_auxWindow_closed(self, window): del self.windows[window.type] window.deleteLater() def setConnData(self, data): self.conndata = data def _reconnect(self): if self._reconnect_count and self.conndata.password is not None: self._reconnect_count -= 1 if self._reconnect_count <= self.client.RECONNECT_TRIES_LONG: self._reconnect_time = self.client.RECONNECT_INTERVAL_LONG self.client.connect(self.conndata) def _keepalive(self): if self.client.isconnected: self.client.ask('keepalive') def show(self): QMainWindow.show(self) if self.autoconnect and not self.client.isconnected: self.on_actionConnect_triggered(True) if sys.platform == 'darwin': # on Mac OS loadBasicWindowSettings seems not to work before show() # so we do it here again with self.sgroup as settings: loadBasicWindowSettings(self, settings) def startup(self): self.show() startStartupTools(self, self.gui_conf.tools) def loadSettings(self, settings): self.autoconnect = settings.value('autoconnect', True, bool) self.connpresets = {} # new setting key, with dictionary values for (k, v) in settings.value('connpresets_new', {}).items(): self.connpresets[k] = ConnectionData(**v) # if it was empty, try old setting key with list values if not self.connpresets: for (k, v) in settings.value('connpresets', {}).items(): self.connpresets[k] = ConnectionData(host=v[0], port=int(v[1]), user=v[2], password=None) self.lastpreset = settings.value('lastpreset', '') if self.lastpreset in self.connpresets: self.conndata = self.connpresets[self.lastpreset].copy() self.instrument = settings.value('instrument', self.gui_conf.name) self.confirmexit = settings.value('confirmexit', True, bool) self.warnwhenadmin = settings.value('warnwhenadmin', True, bool) self.showtrayicon = settings.value('showtrayicon', True, bool) self.autoreconnect = settings.value('autoreconnect', True, bool) self.autosavelayout = settings.value('autosavelayout', True, bool) self.allowoutputlinewrap = settings.value('allowoutputlinewrap', False, bool) self.update() def loadAuxWindows(self, settings): open_wintypes = settings.value('auxwindows') or [] if isinstance(open_wintypes, str): open_wintypes = [int(w) for w in open_wintypes.split(',')] for wtype in open_wintypes: if isinstance(wtype, str): wtype = int(wtype) self.createWindow(wtype) def saveWindowLayout(self): with self.sgroup as settings: settings.setValue('geometry', self.saveGeometry()) settings.setValue('windowstate', self.saveState()) settings.setValue('splitstate', [sp.saveState() for sp in self.splitters]) open_wintypes = list(self.windows) settings.setValue('auxwindows', open_wintypes) def saveSettings(self, settings): settings.setValue('autoconnect', self.client.isconnected) settings.setValue( 'connpresets_new', {k: v.serialize() for (k, v) in self.connpresets.items()}) settings.setValue('lastpreset', self.lastpreset) settings.setValue('font', self.user_font) settings.setValue('color', self.user_color) def closeEvent(self, event): if self.confirmexit and QMessageBox.question( self, 'Quit', 'Do you really want to quit?', QMessageBox.Yes | QMessageBox.No) == QMessageBox.No: event.ignore() return for panel in self.panels: if not panel.requestClose(): event.ignore() return if self.autosavelayout: self.saveWindowLayout() with self.sgroup as settings: self.saveSettings(settings) for panel in self.panels: with panel.sgroup as settings: panel.saveSettings(settings) for window in list(self.windows.values()): if not window.close(): event.ignore() return if self.helpWindow: self.helpWindow.close() if self.client.isconnected: self.on_actionConnect_triggered(False) event.accept() QApplication.instance().quit() def setTitlebar(self, connected): inststr = str(self.instrument) or 'NICOS' if connected: hoststr = '%s at %s:%s' % (self.client.login, self.client.host, self.client.port) self.setWindowTitle('%s - %s' % (inststr, hoststr)) else: self.setWindowTitle('%s - disconnected' % inststr) def setStatus(self, status, exception=False): if status == self.current_status: return if self.client.last_action_at and \ self.current_status == 'running' and \ status in ('idle', 'paused') and \ currenttime() - self.client.last_action_at > 20: # show a visual indication of what happened if status == 'paused': msg = 'Script is now paused.' elif exception: msg = 'Script has exited with an error.' else: msg = 'Script has finished.' self.trayIcon.showMessage(self.instrument, msg) self.client.last_action_at = 0 self.current_status = status isconnected = status != 'disconnected' self.actionConnect.setChecked(isconnected) if isconnected: self.actionConnect.setText('Disconnect') else: self.actionConnect.setText('Connect to server...') self.setTitlebar(False) # new status icon pixmap = QPixmap(':/' + status + ('exc' if exception else '')) self.statusLabel.setPixmap(pixmap) self.statusLabel.setToolTip('Script status: %s' % status) newicon = QIcon() newicon.addPixmap(pixmap, QIcon.Disabled) self.trayIcon.setIcon(newicon) self.trayIcon.setToolTip('%s status: %s' % (self.instrument, status)) if self.showtrayicon: self.trayIcon.show() if self.promptWindow and status != 'paused': self.promptWindow.close() # propagate to panels for panel in self.panels: panel.updateStatus(status, exception) for window in self.windows.values(): for panel in window.panels: panel.updateStatus(status, exception) def on_client_error(self, problem, exc=None): if exc is not None: self.log.error('Error from daemon', exc=exc) problem = strftime('[%m-%d %H:%M:%S] ') + problem if self.errorWindow is None: def reset_errorWindow(): self.errorWindow = None self.errorWindow = ErrorDialog(self, windowTitle='Daemon error') self.errorWindow.accepted.connect(reset_errorWindow) self.errorWindow.addMessage(problem) self.errorWindow.show() else: self.errorWindow.addMessage(problem) def on_client_broken(self, problem): self.on_client_error(problem) if self.autoreconnect: self._reconnect_count = self.client.RECONNECT_TRIES self._reconnect_time = self.client.RECONNECT_INTERVAL_SHORT self.reconnectTimer.start(self._reconnect_time) def on_client_failed(self, problem): if self._reconnect_count: self.reconnectTimer.start(self._reconnect_time) else: self.on_client_error(problem) def on_client_connected(self): self.setStatus('idle') self._reconnect_count = 0 self.setTitlebar(True) # get all server status info initstatus = self.client.ask('getstatus') if initstatus: # handle initial status self.on_client_status(initstatus['status']) # propagate info to all components self.client.signal('initstatus', initstatus) # show warning label for admin users self.adminLabel.setVisible(self.warnwhenadmin and self.client.user_level is not None and self.client.user_level >= ADMIN) self.actionViewOnly.setChecked(self.client.viewonly) # set focus to command input, if present for panel in self.panels: if isinstance(panel, ConsolePanel) and panel.hasinput: panel.commandInput.setFocus() def on_client_status(self, data): status = data[0] if status == STATUS_IDLE: self.setStatus('idle') elif status == STATUS_IDLEEXC: self.setStatus('idle', exception=True) elif status != STATUS_INBREAK: self.setStatus('running') else: self.setStatus('paused') def on_client_disconnected(self): self.adminLabel.setVisible(False) self.setStatus('disconnected') def on_client_showhelp(self, data): if not HelpWindow: return if self.helpWindow is None: self.helpWindow = HelpWindow(self, self.client) self.helpWindow.showHelp(data) self.helpWindow.activateWindow() def on_client_clientexec(self, data): # currently used for client-side plot using matplotlib; data is # (funcname, args, ...) plot_func_path = data[0] try: modname, funcname = plot_func_path.rsplit('.', 1) func = getattr(__import__(modname, None, None, [funcname]), funcname) func(*data[1:]) except Exception: self.log.exception( 'Error during clientexec:\n%s', '\n'.join(traceback.format_tb(sys.exc_info()[2]))) def on_client_plugplay(self, data): windowkey = data[0:2] # (mode, setupname) if windowkey in self.pnpWindows: self.pnpWindows[windowkey].activateWindow() else: window = PnPSetupQuestion(self, self.client, data) self.pnpWindows[windowkey] = window window.closed.connect(self.on_pnpWindow_closed) window.show() def on_pnpWindow_closed(self, window): self.pnpWindows.pop(window.data[0:2], None) def on_client_watchdog(self, data): if self.watchdogWindow is None: self.watchdogWindow = WatchdogDialog(self) self.watchdogWindow.addEvent(data) if data[0] != 'resolved': self.watchdogWindow.show() def on_client_prompt(self, data): if self.promptWindow: self.promptWindow.close() # show non-modal dialog box that prompts the user to continue or abort prompt_text = data[0] dlg = self.promptWindow = QMessageBox( QMessageBox.Information, 'Confirmation required', prompt_text, QMessageBox.Ok | QMessageBox.Cancel, self) dlg.setWindowModality(Qt.NonModal) # give the buttons better descriptions btn = dlg.button(QMessageBox.Cancel) btn.setText('Abort script') btn.clicked.connect(lambda: self.client.tell_action('stop', BREAK_NOW)) btn = dlg.button(QMessageBox.Ok) btn.setText('Continue script') btn.clicked.connect(lambda: self.client.tell_action('continue')) btn.setFocus() dlg.show() def on_trayIcon_activated(self, reason): if reason == QSystemTrayIcon.Trigger: self.activateWindow() def on_actionExpert_toggled(self, on): self.expertmode = on for panel in self.panels: panel.setExpertMode(on) for window in self.windows.values(): for panel in window.panels: panel.setExpertMode(on) def on_actionViewOnly_toggled(self, on): # also triggered when the action is checked by on_client_connected self.client.viewonly = on for panel in self.panels: panel.setViewOnly(on) for window in self.windows.values(): for panel in window.panels: panel.setViewOnly(on) @pyqtSlot() def on_actionNicosHelp_triggered(self): if not HelpWindow: self.showError('Cannot open help window: Qt web extension is not ' 'available on your system.') return if not self.client.isconnected: self.showError('Cannot open online help: you are not connected ' 'to a daemon.') return self.client.eval('session.showHelp("index")') @pyqtSlot() def on_actionNicosDocu_triggered(self): if not QWebView: self.showError('Cannot open documentation window: Qt web extension' ' is not available on your system.') return from nicos.clients.gui.tools.website import WebsiteTool # XXX: change URL to current release version dlg = WebsiteTool(self, self.client, url='http://www.nicos-controls.org') dlg.setWindowModality(Qt.NonModal) dlg.show() @pyqtSlot() def on_actionDebugConsole_triggered(self): if self.debugConsole is None: self.debugConsole = DebugConsole(self) self.debugConsole.show() @pyqtSlot() def on_actionAbout_triggered(self): import nicos.authors if self.client.isconnected: dinfo = self.client.daemon_info.copy() dinfo['server_host'] = self.client.host else: dinfo = {} dlg = dialogFromUi(self, 'dialogs/about.ui') dlg.clientVersion.setText(nicos_version) dlg.pyVersion.setText( '%s/%s/%s' % (sys.version.split()[0], QT_VERSION_STR, PYQT_VERSION_STR)) dlg.serverHost.setText(dinfo.get('server_host', 'not connected')) dlg.nicosRoot.setText(dinfo.get('nicos_root', '')) dlg.serverVersion.setText(dinfo.get('daemon_version', '')) dlg.customPath.setText(dinfo.get('custom_path', '')) dlg.customVersion.setText(dinfo.get('custom_version', '')) dlg.contributors.setPlainText(nicos.authors.authors_list) dlg.adjustSize() dlg.exec_() @pyqtSlot(bool) def on_actionConnect_triggered(self, on): # connection or disconnection request? if not on: self.client.disconnect() if self.tunnelServer: self.tunnelServer.stop() self.tunnelServer = None return self.actionConnect.setChecked(False) # gets set by connection event ret = ConnectionDialog.getConnectionData(self, self.connpresets, self.lastpreset, self.conndata, self.tunnel) new_name, new_data, save, tunnel = ret if new_data is None: return if save: self.lastpreset = save self.connpresets[save] = new_data else: self.lastpreset = new_name self.conndata = new_data if tunnel: try: host, username, password = splitTunnelString(tunnel) self.tunnelServer = SSHTunnelForwarder( host, ssh_username=username, ssh_password=password, remote_bind_address=(self.conndata.host, self.conndata.port), compression=True) self.tunnelServer.start() tunnel_port = self.tunnelServer.local_bind_port # corresponding ssh command line (debug) # print 'ssh -f %s -L %d:%s:%d -N' % (host, tunnel_port, # self.conndata.host, # self.conndata.port) # store the established tunnel information host, user, and # password for the next connection try to avoid typing password # for every (re)connection via the GUI self.tunnel = tunnel self.conndata.host = 'localhost' self.conndata.port = tunnel_port except ValueError as e: self.showError(str(e)) self.tunnelServer = None except BaseSSHTunnelForwarderError as e: self.showError(str(e)) self.tunnelServer = None self.client.connect(self.conndata) @pyqtSlot() def on_actionPreferences_triggered(self): dlg = SettingsDialog(self) ret = dlg.exec_() if ret == QDialog.Accepted: dlg.saveSettings() @pyqtSlot() def on_actionFont_triggered(self): font, ok = QFontDialog.getFont(self.user_font, self) if not ok: return for panel in self.panels: panel.setCustomStyle(font, self.user_color) self.user_font = font @pyqtSlot() def on_actionColor_triggered(self): color = QColorDialog.getColor(self.user_color, self) if not color.isValid(): return for panel in self.panels: panel.setCustomStyle(self.user_font, color) self.user_color = color
class ConsolePanel(Panel): """Provides a console-like interface. The commands can be entered and the output from the NICOS daemon is displayed. Options: * ``hasinput`` (default True) -- if set to False, the input box is hidden and the console is just an output view. * ``hasmenu`` (default True) -- if set to False, the console does not provide its menu (containing actions for the output view such as Save or Print). * ``fulltime`` (default False) -- if set to True, the console shows the full (date + time) timestamp for every line, instead of only for errors and warnings. * ``watermark`` (default empty) -- the path to an image file that should be used as a watermark in the console window. """ panelName = 'Console' ui = 'panels/console.ui' def __init__(self, parent, client, options): Panel.__init__(self, parent, client, options) loadUi(self, self.ui) self.commandInput.scrollWidget = self.outView self.grepPanel.hide() self.grepText.scrollWidget = self.outView self.actionLabel.hide() self.outView.setActionLabel(self.actionLabel) self.commandInput.history = self.cmdhistory self.commandInput.completion_callback = self.completeInput self.grepNoMatch.setVisible(False) self.actionAllowLineWrap.setChecked(self.mainwindow.allowoutputlinewrap) client.connected.connect(self.on_client_connected) client.message.connect(self.on_client_message) client.simmessage.connect(self.on_client_simmessage) client.initstatus.connect(self.on_client_initstatus) client.mode.connect(self.on_client_mode) client.experiment.connect(self.on_client_experiment) self.outView.setContextMenuPolicy(Qt.CustomContextMenu) self.menu = QMenu('&Output', self) self.menu.addAction(self.actionCopy) self.menu.addAction(self.actionGrep) self.menu.addSeparator() self.menu.addAction(self.actionSave) self.menu.addAction(self.actionPrint) self.menu.addSeparator() self.menu.addAction(self.actionAllowLineWrap) self.on_actionAllowLineWrap_triggered( self.mainwindow.allowoutputlinewrap) self.hasinput = bool(options.get('hasinput', True)) self.inputFrame.setVisible(self.hasinput) self.hasmenu = bool(options.get('hasmenu', True)) if options.get('fulltime', False): self.outView.setFullTimestamps(True) watermark = options.get('watermark', '') if watermark: watermark = findResource(watermark) if path.isfile(watermark): self.outView.setBackgroundImage(watermark) def on_outView_customContextMenuRequested(self, point): self.menu.popup(self.outView.mapToGlobal(point)) def setExpertMode(self, expert): if not self.hasinput: self.inputFrame.setVisible(expert) def setViewOnly(self, viewonly): self.commandInput.setVisible(not viewonly) self.promptLabel.setVisible(not viewonly) def loadSettings(self, settings): self.cmdhistory = settings.value('cmdhistory') or [] def saveSettings(self, settings): # only save 100 entries of the history cmdhistory = self.commandInput.history[-100:] settings.setValue('cmdhistory', cmdhistory) def getMenus(self): if self.hasmenu: return [self.menu] return [] def setCustomStyle(self, font, back): self.commandInput.idle_color = back for widget in (self.outView, self.commandInput): widget.setFont(font) setBackgroundColor(widget, back) self.promptLabel.setFont(font) def updateStatus(self, status, exception=False): self.commandInput.setStatus(status) def completeInput(self, fullstring, lastword): try: return self.client.ask('complete', fullstring, lastword, default=[]) except Exception: return [] def on_client_connected(self): self.actionLabel.hide() self.outView._currentuser = self.client.login def on_client_mode(self, mode): self.promptLabel.setText(modePrompt(mode)) def on_client_initstatus(self, state): self.on_client_mode(state['mode']) self.outView.clear() messages = self.client.ask('getmessages', '10000', default=[]) total = len(messages) // 2500 + 1 for _, batch in enumerateWithProgress(chunks(messages, 2500), text='Synchronizing...', parent=self, total=total): self.outView.addMessages(batch) self.outView.scrollToBottom() def on_client_message(self, message): self.outView.addMessage(message) def on_client_simmessage(self, simmessage): if simmessage[-1] == '0': self.outView.addMessage(simmessage) def on_client_experiment(self, data): (_, proptype) = data if proptype == 'user': # only clear history and output when switching TO a user experiment self.commandInput.history = [] # clear everything except the last command with output self.outView.clearAlmostEverything() def on_outView_anchorClicked(self, url): """Called when the user clicks a link in the out view.""" scheme = url.scheme() if scheme == 'exec': # Direct execution is too dangerous. Just insert it in the editor. if self.inputFrame.isVisible(): self.commandInput.setText(url.path()) self.commandInput.setFocus() elif scheme == 'edit': if self.mainwindow.editor_wintype is None: return win = self.mainwindow.createWindow(self.mainwindow.editor_wintype) panel = win.getPanel('User editor') panel.openFile(url.path()) showPanel(panel) elif scheme == 'trace': TracebackDialog(self, self.outView, url.path()).show() else: self.log.warning('Strange anchor in outView: %s', url) @pyqtSlot() def on_actionPrint_triggered(self): printer = QPrinter() printdlg = QPrintDialog(printer, self) printdlg.setOption(QAbstractPrintDialog.PrintSelection) if printdlg.exec_() == QDialog.Accepted: self.outView.print_(printer) @pyqtSlot() def on_actionSave_triggered(self): fn = QFileDialog.getSaveFileName(self, 'Save', '', 'All files (*.*)')[0] if not fn: return try: fn = fn.encode(sys.getfilesystemencoding()) with open(fn, 'w') as f: f.write(self.outView.getOutputString()) except Exception as err: QMessageBox.warning(self, 'Error', 'Writing file failed: %s' % err) @pyqtSlot() def on_actionCopy_triggered(self): self.outView.copy() @pyqtSlot() def on_actionGrep_triggered(self): self.grepPanel.setVisible(True) self.grepText.setFocus() @pyqtSlot() def on_grepClose_clicked(self): self.grepPanel.setVisible(False) self.commandInput.setFocus() self.outView.scrollToBottom() def on_grepText_returnPressed(self): self.on_grepSearch_clicked() def on_grepText_escapePressed(self): self.on_grepClose_clicked() @pyqtSlot() def on_grepSearch_clicked(self): st = self.grepText.text() if not st: return found = self.outView.findNext(st, self.grepRegex.isChecked()) self.grepNoMatch.setVisible(not found) @pyqtSlot() def on_grepOccur_clicked(self): st = self.grepText.text() if not st: return self.outView.occur(st, self.grepRegex.isChecked()) @pyqtSlot(bool) def on_actionAllowLineWrap_triggered(self, checked): self.mainwindow.allowoutputlinewrap = checked if self.mainwindow.allowoutputlinewrap: self.outView.setLineWrapMode(QTextEdit.WidgetWidth) else: self.outView.setLineWrapMode(QTextEdit.NoWrap) def on_commandInput_execRequested(self, script, action): if action == 'queue': self.client.run(script) else: self.client.tell('exec', script) self.commandInput.setText('')