Esempio n. 1
0
    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)
Esempio n. 2
0
 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()
     self._reinit()
Esempio n. 3
0
class GenericPanel(Panel):
    """Provides a generic implementation of Panel to display any ``.ui`` file.

    The ``.ui`` file may also use NICOS GUI widgets (see :ref:`gui-widgets`).

    Options:

    * ``uifile`` -- the path to the UI file to display
    * ``showmsg`` -- if set to `True` a dialog window pops up in case of an
      error or warning inside the daemon and displays the corresponding
      message. If the dialog is already open, a new line with the error or
      warning message will be added to the open dialog.
    """

    panelName = 'Generic'  # XXX this is not unique

    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        self._error_window = None
        if 'uifile' not in options:
            raise ConfigurationError('GenericPanels require at least an'
                                     ' `uifile` option.')
        loadUi(self, findResource(options['uifile']))
        if options.get('showmsg'):
            self.client.message.connect(self.on_client_message)
        if client.isconnected:
            self.on_client_connected()
        client.connected.connect(self.on_client_connected)

    def on_client_connected(self):
        for ch in self.findChildren(NicosWidget):
            ch.setClient(self.client)

    def on_client_message(self, message):
        # show warnings and errors emitted by the current command in a window
        if len(message) < 6 or message[5] != self.client.last_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()
Esempio n. 4
0
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)
Esempio n. 5
0
class AmorControlPanel(GenericPanel):
    def __init__(self, parent, client, options):
        GenericPanel.__init__(self, parent, client, options)
        for ch in self.findChildren(NicosWidget):
            ch.setClient(client)

        # 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

        self.motor_widgets = {
            'som': self.somEdit,
            's2t': self.s2tEdit,
            'soz': self.sozEdit,
            'stz': self.stzEdit
        }

        self.slit_widgets = {
            'slit1_opening': self.slit1Edit,
            'slit2_opening': self.slit2Edit,
            'slit3_opening': self.slit3Edit,
            'slit4_opening': self.slit4Edit,
            'd5v': self.slit5Edit,
        }

        self.magnet_widgets = {'hsy': self.hsyEdit}

        # Initialise the widgets
        self._reinit()

        # Set the validator for monitor preset edit
        # Regular expression for scientific notation: [\d.]+(?:e\d+)?
        self.monitorPresetBox.setValidator(
            QRegExpValidator(QRegExp(r"[\d.]+(?:e\d+)?"), self))

        self.setMonitorPreset(True)
        self.setSampleMove(True)

        self.opMonitor.toggled.connect(self.setMonitorPreset)
        self.opTime.toggled.connect(self.setTimePreset)
        self.opSampleMove.toggled.connect(self.setSampleMove)
        self.opSampleSetPosition.toggled.connect(self.setSampleSetPosition)
        client.message.connect(self.on_client_message)
        client.setup.connect(self.on_client_setup)

    def updateStatus(self, status, exception=False):
        self._current_status = status

    def _reinit(self):
        widgets_dict = {}
        widgets_dict.update(self.motor_widgets)
        widgets_dict.update(self.slit_widgets)
        widgets_dict.update(self.magnet_widgets)

        for n, w in widgets_dict.items():
            currval = self.client.getDeviceParam(n, 'value')
            w._reinit(currval)

    def on_client_setup(self):
        self._reinit()

    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()
        self._reinit()

    def setMonitorPreset(self, checked):
        self.timePresetWidget.setVisible(not checked)
        self.monitorPresetWidget.setVisible(checked)

    def setTimePreset(self, checked):
        self.monitorPresetWidget.setVisible(not checked)
        self.timePresetWidget.setVisible(checked)

    def setSampleMove(self, checked):
        self.sampleSetPositionButtonWidget.setVisible(not checked)
        self.motor_widgets['s2t'].setEnabled(checked)
        self.sampleMoveButtonWidget.setVisible(checked)
        self._reinit()

    def setSampleSetPosition(self, checked):
        self.motor_widgets['s2t'].setEnabled(not checked)
        self.sampleMoveButtonWidget.setVisible(not checked)
        self.sampleSetPositionButtonWidget.setVisible(checked)
        self._reinit()

    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)

    @pyqtSlot()
    def on_countStartButton_clicked(self):
        if self.opTime.isChecked():
            value = float(self.timePresetBox.text())
            preset = 't'
        else:
            try:
                value = float(self.monitorPresetBox.text())
                preset = 'm'
            except ValueError:
                self.log.exception('invalid value for monitor preset')
                QMessageBox.warning(self, 'Error', 'Invalid monitor preset')
                return
        args = '%s=%r' % (preset, int(value))
        code = 'count(%s)' % args
        self.exec_command(code)

    @pyqtSlot()
    def on_sampleMoveButton_clicked(self):
        self._devsMoveButton(self.motor_widgets, 'move')

    @pyqtSlot()
    def on_sampleMoveAndWaitButton_clicked(self):
        self._devsMoveButton(self.motor_widgets, 'maw')

    @pyqtSlot()
    def on_sampleSetPositionButton_clicked(self):
        self._devsMoveButton(self.motor_widgets, 'adjust', True)

    @pyqtSlot()
    def on_slitMoveButton_clicked(self):
        self._devsMoveButton(self.slit_widgets, 'move')

    @pyqtSlot()
    def on_slitMoveAndWaitButton_clicked(self):
        self._devsMoveButton(self.slit_widgets, 'maw')

    @pyqtSlot()
    def on_hsyToggleButton_clicked(self):
        switchedOn = self.client.eval('hsy_switch.isEnabled', None)
        if switchedOn is None:
            self.showError('Cannot check the status of magnets!')
        newstate = 'off' if switchedOn else 'on'
        code = 'hsy_switch.move(%r)' % newstate
        self.exec_command(code)

    @pyqtSlot()
    def on_hsyMoveButton_clicked(self):
        self._devsMoveButton(self.magnet_widgets, 'move')

    def _devsMoveButton(self, dic, cmd='move', issue_separate=False):
        dev_to_widget = {
            n: w
            for n, w in dic.items() if not isinstance(w._inner, MissingWidget)
        }
        targets = []
        try:
            targets = [edit.getValue() for edit in dev_to_widget.values()]
        except ValueError:
            self.log.exception('invalid value for typed value')
            # shouldn't happen, but if it does, at least gives indication that
            # something went wrong
            QMessageBox.warning(self, 'Error', 'Some entered value is invalid')
            return

        # Check which motors to move
        expr = ', '.join([
            n + '.isAtTarget(target=%r)' % v
            for n, v in zip(dev_to_widget, targets)
        ])
        on_targ = self.client.eval(expr, None)
        if on_targ is None:
            self.showError('Cannot check the status! Cannot move!')
            return

        if not isinstance(on_targ, tuple):
            on_targ = [on_targ]

        move = {
            n: v
            for n, v, t in zip(dev_to_widget, targets, on_targ) if not t
        }

        if not move:
            return

        if issue_separate:
            code = '\n'.join(
                ('%s(%s, %r)' % (cmd, n, v) for n, v in move.items()))
        else:
            code = '%s(%s)' % (cmd, ', '.join(
                ('%s, %r' % (n, v) for n, v in move.items())))
        self.exec_command(code)
Esempio n. 6
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