def addLogo(self): logoLabel = QLabel() pxr = decolor_logo(QPixmap("resources/logo-icon.png"), Qt.white) logoLabel.setPixmap( pxr.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) self.toolBarMain.insertWidget(self.toolBarMain.actions()[0], logoLabel) nicosLabel = QLabel() pxr = decolor_logo(QPixmap("resources/nicos-logo-high.svg"), Qt.white) nicosLabel.setPixmap( pxr.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) self.toolBarMain.insertWidget(self.toolBarMain.actions()[1], nicosLabel)
def __init__(self, parent, client, options): CustomButtonPanel.__init__(self, parent, client, options) # our content is a simple widget ... self._tableWidget = TableWidget(self) self._tableWidget.setColumnCount(1) self._tableWidget.setHorizontalHeaderLabels(['Sample name']) self._tableWidget.horizontalHeaderItem(0).setTextAlignment( Qt.AlignLeft | Qt.AlignVCenter) self._tableWidget.setSortingEnabled(False) self._tableWidget.setCornerLabel('Position') self.vBoxLayout.insertWidget(0, self._tableWidget) client.connected.connect(self.on_client_connected) client.setup.connect(self.on_client_connected) image = options.get('image', None) # insert the optional image at top... if image: l = QLabel(self) l.setText(image) # insert above scrollArea self.vBoxLayout.insertWidget(0, l, alignment=Qt.AlignHCenter) p = QPixmap() if p.load(findResource(image)): l.setPixmap(p) else: msg = 'Loading of Image %r failed:' % image msg += '\n\nCheck GUI config file for %r' % __file__ self.showError(msg) self._numSamples = int(options.get('positions', 11)) self._tableWidget.setRowCount(self._numSamples) # fill in widgets into grid for pos in range(self._numSamples): self._tableWidget.setCellWidget(pos, 0, QLineEdit('')) self._tableWidget.horizontalHeader().setStretchLastSection(100) # now fill in data self._update_sample_info()
def add_logo(self): logo_label = QLabel() pxr = decolor_logo( QPixmap(path.join(root_path, 'resources', 'logo-icon.png')), Qt.white) logo_label.setPixmap( pxr.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) logo_label.setMargin(5) self.toolBarMain.insertWidget(self.toolBarMain.actions()[0], logo_label) nicos_label = QLabel() pxr = decolor_logo( QPixmap(path.join(root_path, 'resources', 'nicos-logo-high.svg')), Qt.white) nicos_label.setPixmap( pxr.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) self.toolBarMain.insertWidget(self.toolBarMain.actions()[1], nicos_label)
def addInstrument(self): textLabel = QLabel('Instrument:') textLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) textLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter) instrumentLabel = QLabel('Unknown') instrumentLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.toolBarMain.addWidget(textLabel) self.toolBarMain.addWidget(instrumentLabel) instrument = os.getenv('INSTRUMENT') if instrument: instrument = instrument.split('.')[-1] logo = decolor_logo(QPixmap('resources/%s-logo.svg' % instrument), Qt.white) if logo.isNull(): instrumentLabel.setText(instrument.upper()) return instrumentLabel.setPixmap( logo.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation))
class MainWindow(DefaultMainWindow): ui = '%s/main.ui' % uipath 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 _init_toolbar(self): self.statusLabel = QLabel('', self, pixmap=QPixmap(':/disconnected'), margin=5, minimumSize=QSize(30, 10)) self.statusLabel.setStyleSheet('color: white') self.toolbar = self.toolBarRight self.toolbar.addWidget(self.statusLabel) self.setStatus('disconnected') def _init_experiment_name(self): self.experiment_text = QLabel() self.experiment_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.experiment_text.setStyleSheet( 'font-size: 17pt; font-weight: bold') self.toolBarMain.addWidget(self.experiment_text) self.experiment_label = QLabel() self.experiment_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.experiment_label.setStyleSheet('font-size: 17pt') self.toolBarMain.addWidget(self.experiment_label) def _init_instrument_name(self): self.instrument_text = QLabel() self.instrument_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.instrument_text.setStyleSheet( 'font-size: 17pt; font-weight: bold') self.toolBarMain.addWidget(self.instrument_text) self.instrument_label = QLabel() self.instrument_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.instrument_label.setStyleSheet('font-size: 17pt') self.toolBarMain.addWidget(self.instrument_label) def set_icons(self): self.actionUser.setIcon(get_icon('settings_applications-24px.svg')) self.actionEmergencyStop.setIcon( get_icon('emergency_stop_cross-24px.svg')) self.actionConnect.setIcon(get_icon('power-24px.svg')) self.actionExit.setIcon(get_icon('exit_to_app-24px.svg')) self.actionViewOnly.setIcon(get_icon('lock-24px.svg')) self.actionPreferences.setIcon(get_icon('tune-24px.svg')) self.actionExpert.setIcon(get_icon('fingerprint-24px.svg')) def add_logo(self): logo_label = QLabel() pxr = decolor_logo( QPixmap(path.join(root_path, 'resources', 'logo-icon.png')), Qt.white) logo_label.setPixmap( pxr.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) logo_label.setMargin(5) self.toolBarMain.insertWidget(self.toolBarMain.actions()[0], logo_label) nicos_label = QLabel() pxr = decolor_logo( QPixmap(path.join(root_path, 'resources', 'nicos-logo-high.svg')), Qt.white) nicos_label.setPixmap( pxr.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) self.toolBarMain.insertWidget(self.toolBarMain.actions()[1], nicos_label) def update_instrument_text(self): instrument = self.client.eval('session.instrument', None) self.instrument_text.setText('Instrument:') if instrument: logo = decolor_logo( QPixmap( path.join(root_path, 'resources', f'{instrument}-logo.svg')), Qt.white) if logo.isNull(): self.instrument_label.setText(instrument.upper()) return self.instrument_label.setPixmap( logo.scaledToHeight(self.toolBarMain.height(), Qt.SmoothTransformation)) else: self.instrument_label.setText('UNKNOWN') def update_experiment_text(self): max_text_length = 50 experiment = self.client.eval('session.experiment.title', None) if experiment is not None: self.experiment_text.setText(" Experiment:") self.experiment_label.setText(experiment[0:max_text_length]) def remove_experiment_and_instrument(self): self.experiment_label.clear() self.experiment_text.clear() self.instrument_label.clear() self.instrument_text.clear() def reloadQSS(self): self.setQSS(self.stylefile) def selectQSS(self): style_file = QFileDialog.getOpenFileName( self, filter="Qt Stylesheet Files (*.qss)")[0] if style_file: self.style_file = style_file self.setQSS(self.style_file) @staticmethod def setQSS(style_file): with open(style_file, 'r', encoding='utf-8') as fd: try: QApplication.instance().setStyleSheet(fd.read()) except Exception as e: print(e) 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 \ current_time() - 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 is_connected = status != 'disconnected' if is_connected: self.actionConnect.setText('Disconnect') self.statusLabel.setText('\u2713 Connected') self.update_instrument_text() self.update_experiment_text() else: self.actionConnect.setText('Connect to server...') self.statusLabel.setText('Disconnected') self.setTitlebar(False) # new status icon pixmap = QPixmap(':/' + status + ('exc' if exception else '')) new_icon = QIcon() new_icon.addPixmap(pixmap, QIcon.Disabled) self.trayIcon.setIcon(new_icon) 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_connected(self): DefaultMainWindow.on_client_connected(self) self.actionConnect.setIcon(get_icon("power_off-24px.svg")) self.actionExpert.setEnabled(True) self.actionEmergencyStop.setEnabled(not self.client.viewonly) def on_client_disconnected(self): DefaultMainWindow.on_client_disconnected(self) self.remove_experiment_and_instrument() self.actionConnect.setIcon(get_icon("power-24px.svg")) self.actionExpert.setEnabled(False) self.actionExpert.setChecked(False) self.actionEmergencyStop.setEnabled(False) def on_actionViewOnly_toggled(self, on): DefaultMainWindow.on_actionViewOnly_toggled(self, on) if self.client.isconnected: self.actionEmergencyStop.setEnabled(not self.client.viewonly) else: self.actionEmergencyStop.setEnabled(False) @pyqtSlot(bool) def on_actionConnect_triggered(self, _): # connection or disconnection request? connection_req = self.current_status == "disconnected" super().on_actionConnect_triggered(connection_req) @pyqtSlot() def on_actionUser_triggered(self): w = self.toolBarRight.widgetForAction(self.actionUser) self.dropdown.popup(w.mapToGlobal(QPoint(0, w.height()))) @pyqtSlot() def on_actionEmergencyStop_triggered(self): self.client.tell_action('emergency')
class PictureDisplay(NicosWidget, QWidget): """A display widget to show a picture.""" designer_description = 'Widget to display a picture file' filepath = PropDef('filepath', str, '', 'Path to the picture that should ' 'be displayed') name = PropDef('name', str, '', 'Name (caption) to be displayed above ' 'the picture') refresh = PropDef('refresh', int, 0, 'Interval to check for updates ' 'in seconds') height = PropDef('height', int, 0) width = PropDef('width', int, 0) def __init__(self, parent=None, designMode=False, **kwds): QWidget.__init__(self, parent, **kwds) NicosWidget.__init__(self) self._last_mtime = None self.namelabel = QLabel(self) self.namelabel.setAlignment(Qt.AlignHCenter) self.piclabel = QLabel(self) self.piclabel.setScaledContents(True) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.piclabel, 1) self.setLayout(layout) def registerKeys(self): pass def setPicture(self): size = QSize(self.props['width'] * self._scale, self.props['height'] * self._scale) if isfile(self._filePath): pixmap = QPixmap(self._filePath) else: pixmap = QPixmap(size) pixmap.fill() if size.isEmpty(): self.piclabel.setPixmap(pixmap) else: self.piclabel.setPixmap(pixmap.scaled(size)) self.piclabel.resize(self.piclabel.sizeHint()) def updatePicture(self): if not isfile(self._filePath): return # on first iteration self._last_mtime is None -> always setPicture() mtime = getmtime(self._filePath) if self._last_mtime != mtime: self._last_mtime = mtime self.setPicture() def propertyUpdated(self, pname, value): NicosWidget.propertyUpdated(self, pname, value) if pname == 'filepath': self._filePath = findResource(value) self.setPicture() elif pname == 'name': layout = QVBoxLayout() if value: layout.addWidget(self.namelabel) layout.addSpacing(5) layout.addWidget(self.piclabel, 1) sip.delete(self.layout()) self.setLayout(layout) self.namelabel.setText(value) elif pname in ('width', 'height'): self.setPicture() elif pname == 'refresh': if value: self._refreshTimer = QTimer() self._refreshTimer.setInterval(value * 1000) self._refreshTimer.timeout.connect(self.updatePicture) self._refreshTimer.start()
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