Пример #1
0
    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)
Пример #2
0
    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()
Пример #3
0
    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)
Пример #4
0
    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))
Пример #5
0
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')
Пример #6
0
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()
Пример #7
0
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