Esempio n. 1
0
class ScriptQueue(object):
    def __init__(self, frame, view):
        self._id2item = {}  # mapping from request ID to list widget item
        self._frame = frame
        self._view = view
        self._timer = QTimer(singleShot=True, timeout=self._timeout)

    def _format_item(self, request):
        script = request['script']
        if len(script) > 100:
            return script[:100] + '...'
        return script

    def _timeout(self):
        self._frame.show()

    def append(self, request):
        item = QListWidgetItem(self._format_item(request))
        item.setData(Qt.UserRole, request['reqid'])
        self._id2item[request['reqid']] = item
        self._view.addItem(item)
        # delay showing the frame for 20 msecs, so that it doesn't flicker in
        # and out if the script is immediately taken out of the queue again
        self._timer.start(20)

    def update(self, request):
        item = self._id2item.get(request['reqid'])
        if item:
            text = self._format_item(request)
            item.setText(text)

    def remove(self, reqid):
        item = self._id2item.pop(reqid, None)
        if item is None:
            return
        item = self._view.takeItem(self._view.row(item))
        if not self._id2item:
            self._timer.stop()
            self._frame.hide()
        return item

    def rearrange(self, reqids):
        selected = self._view.currentItem()
        for i in range(self._view.count()-1, -1, -1):
            self._view.takeItem(i)
        for reqid in reqids:
            self._view.addItem(self._id2item[reqid])
        if selected:
            self._view.setCurrentItem(selected)

    def clear(self):
        self._frame.hide()
        self._view.clear()
        self._id2item.clear()

    # pylint: disable=nonzero-method
    def __nonzero__(self):
        return bool(self._id2item)

    __bool__ = __nonzero__
Esempio n. 2
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/expinfo.ui')
        for ch in self.findChildren(NicosWidget):
            ch.setClient(client)
        client.setup.connect(self.on_client_setup)
        client.initstatus.connect(self.on_client_initstatus)

        self.detLabel.setFormatCallback(
            lambda value, strvalue: ', '.join(sorted(value)))
        self.envLabel.setFormatCallback(
            lambda value, strvalue: ', '.join(sorted(value)))

        self._sample_panel = options.get('sample_panel', GenericSamplePanel)
        self._new_exp_panel = options.get('new_exp_panel')
        self._finish_exp_panel = options.get('finish_exp_panel')
        self._timeout = options.get('popup_proposal_after', 0)
        if self._timeout:
            self._proposal_popup_timer = QTimer(interval=self._timeout *
                                                3600000)
            self._proposal_popup_timer.setSingleShot(True)
            self._proposal_popup_timer.timeout.connect(
                self.on_proposal_popup_timer_timeout)
        else:
            self._proposal_popup_timer = None
Esempio n. 3
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/elog.ui')
        self.preview = QWebView(self)
        self.frame.layout().addWidget(self.preview)

        self.timer = QTimer(self,
                            singleShot=True,
                            timeout=self.on_timer_timeout)
        self.propdir = None

        self.menus = None
        self.bar = None

        if client.isconnected:
            self.on_client_connected()
        client.connected.connect(self.on_client_connected)
        client.setup.connect(self.on_client_connected)
        client.experiment.connect(self.on_client_experiment)

        self.activeGroup = QActionGroup(self)
        self.activeGroup.addAction(self.actionAddComment)
        self.activeGroup.addAction(self.actionAddRemark)
        self.activeGroup.addAction(self.actionAttachFile)
        self.activeGroup.addAction(self.actionNewSample)

        page = self.preview.page()
        if hasattr(page, 'setForwardUnsupportedContent'):  # QWebKit only
            page.setForwardUnsupportedContent(True)
            page.unsupportedContent.connect(self.on_page_unsupportedContent)
Esempio n. 4
0
def startStartupTools(window, config):
    """Start all tools that are set to *runatstartup* from *config*.

    Use *window* as the parent window for dialogs.
    """
    for tconfig in config:
        if isinstance(tconfig, menu):
            startStartupTools(window, tconfig.items)
        elif isinstance(tconfig, tool) and tconfig.options.get('runatstartup'):
            QTimer.singleShot(0, lambda tc=tconfig: runTool(window, tc))
Esempio n. 5
0
    def __init__(self, parent, _client, **_kwds):
        QMainWindow.__init__(self, parent)
        DlgUtils.__init__(self, 'Hexapod')

        # set during actions that will call signal handlers
        self.recursive = False

        loadUi(self, findResource('nicos_mlz/kws1/gui/tools/hexapod.ui'))

        for but in (self.butStart, self.butSetWorkspace, self.butSetFrame,
                    self.butSaveVel):
            but.setEnabled(False)

        self.axes = {}
        self.axeslist = []

        try:
            self._controller = PyTango.DeviceProxy(TANGO_DEV_BASE +
                                                   'controller')
            # make sure the server is running and create remaining proxies
            try:
                self._controller.State()
            except AttributeError:
                raise Exception('server appears to be not running')
            for axis in AXES:
                self.axes[axis] = PyTango.DeviceProxy(TANGO_DEV_BASE + axis)
                self.axeslist.append(self.axes[axis])
        except Exception as err:
            self.showError('could not connect to tango server: %s' % err)
            self.deleteLater()
            return

        self.on_cbsWorkspace_activated(0)
        self.on_cbsFrame_activated(self.cbsFrame.currentText())

        tx_speed = self.query_attr(self.axes['tx'], 'speed')
        self.inpVelTrans.setValue(tx_speed)
        self.lblVelTrans.setText(self.inpVelTrans.text())
        self.inpVelRot.setValue(self.query_attr(self.axes['rx'], 'speed'))
        self.lblVelRot.setText(self.inpVelRot.text())
        self.inpVelOmega.setValue(self.query_attr(self.axes['omega'], 'speed'))
        self.lblVelOmega.setText(self.inpVelOmega.text())

        # ramp time = speed / acceleration
        self.inpRampUp.setValue(tx_speed /
                                self.query_attr(self.axes['tx'], 'accel'))
        self.lblRampUp.setText(self.inpRampUp.text())
        self.inpRampDown.setValue(tx_speed /
                                  self.query_attr(self.axes['tx'], 'decel'))
        self.lblRampDown.setText(self.inpRampDown.text())

        self.updTimer = QTimer()
        self.updTimer.timeout.connect(self.updateTimer)
        self.updTimer.start(1000)
Esempio n. 6
0
 def __init__(self,
              client,
              watcher,
              entry,
              shortKey,
              showTimeStamp,
              showTTL,
              parent=None):
     base_class.__init__(self, parent)
     self.setupUi(self)
     self.updateTimer = QTimer(self)
     self.updateTimer.setSingleShot(True)
     self.watcher = watcher
     self.client = client
     self.entry = entry
     self.widgetValue = None
     self.setupEvents()
     self.setupWidgetUi(shortKey, showTimeStamp, showTTL)
Esempio n. 7
0
 def _label_entered(self, widget, event, from_mouse=True):
     infotext = '%s = %s' % (self.props['name'] or self.props['dev']
                             or self.props['key'], self.valuelabel.text())
     if self.props['unit'].strip():
         infotext += ' %s' % self.props['unit']
     if self.props['statuskey']:
         try:
             const, msg = self._laststatus
         except ValueError:
             const, msg = self._laststatus, ''
         infotext += ', status is %s: %s' % (statuses.get(const, '?'), msg)
     infotext += ', changed %s ago' % (
         nicedelta(currenttime() - self._lastchange))
     self.widgetInfo.emit(infotext)
     if from_mouse:
         self._mousetimer = QTimer(self, timeout=lambda:
                                   self._label_entered(widget, event, False)
                                   )
         self._mousetimer.start(1000)
Esempio n. 8
0
 def on_devValueChange(self, dev, value, strvalue, unitvalue, expired):
     # check expired values
     self._expired = expired
     self._lastvalue = value
     self._lastchange = currenttime()
     if self.props['maxlen'] > -1:
         self.valuelabel.setText(strvalue[:self.props['maxlen']])
     else:
         self.valuelabel.setText(strvalue)
     if self._expired:
         setBothColors(self.valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                         self._colorscheme['expired']))
         if self.props['showExpiration']:
             self.valuelabel.setText(NOT_AVAILABLE)
     elif not self.props['istext']:
         setBothColors(self.valuelabel, (self._colorscheme['fore'][BUSY],
                                         self._colorscheme['back'][BUSY]))
         QTimer.singleShot(1000, self._applystatuscolor)
     else:
         self._applystatuscolor()
Esempio n. 9
0
 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()
Esempio n. 10
0
    def addcurve(self, key, index, title, scale, offset):
        series = TimeSeries(key, self.props['plotinterval'], scale, offset,
                            self.props['plotwindow'], self)
        series.init_empty()
        curve = PlotCurve([currenttime()], [0], legend=title)
        self.plotcurves[series] = curve
        self.ncurves += 1
        self.curves.append(curve)
        self.axes.addCurves(curve)
        self.series[key, index] = series
        self.widget.update()

        # record the current value at least every 5 seconds, to avoid curves
        # not updating if the value doesn't change
        def update():
            series.synthesize_value()
        self.ctimers[curve] = QTimer(singleShot=True)
        self.ctimers[curve].timeout.connect(update)
Esempio n. 11
0
class EntryWidget(base_class, ui_class):
    def __init__(self,
                 client,
                 watcher,
                 entry,
                 shortKey,
                 showTimeStamp,
                 showTTL,
                 parent=None):
        base_class.__init__(self, parent)
        self.setupUi(self)
        self.updateTimer = QTimer(self)
        self.updateTimer.setSingleShot(True)
        self.watcher = watcher
        self.client = client
        self.entry = entry
        self.widgetValue = None
        self.setupEvents()
        self.setupWidgetUi(shortKey, showTimeStamp, showTTL)

    def setupEvents(self):
        """Sets up all events."""
        self.buttonSet.clicked.connect(self.setKey)
        self.buttonDel.clicked.connect(self.delKey)
        self.buttonWatch.clicked.connect(self.watchKey)
        self.client.signals.keyUpdated.connect(self.keyUpdated)
        self.updateTimer.timeout.connect(self.updateTimerEvent)

    def setupWidgetUi(self, shortKey, showTimeStamp, showTTL):
        """
        Sets up and generate a UI according to the data type of the value
        and whether or not time to live or time stamp should be shown.
        """
        entry = self.entry

        fm = self.labelTime.fontMetrics()
        margins = self.labelTime.getContentsMargins()
        self.labelTime.setMinimumWidth(
            fm.width(entry.convertTime(1.0)) + margins[0] + margins[2] +
            self.labelTime.sizeHint().width())

        if self.watcher is None:  # widget is already in watcher
            self.buttonWatch.hide()

        if shortKey:
            self.labelKey.setText(entry.key.rpartition('/')[2])
            self.labelKey.setToolTip(entry.key)
        else:
            self.labelKey.setText(entry.key)

        if entry.value in ('True', 'False'):
            self.widgetValue = ReadOnlyCheckBox()
            self.layoutWidget.insertWidget(4, self.widgetValue)
            self.layoutWidget.insertSpacerItem(
                5, QSpacerItem(56, 20, QSizePolicy.Expanding))
        else:
            self.widgetValue = QLineEdit()
            self.layoutWidget.insertWidget(4, self.widgetValue)
        self.widgetValue.setReadOnly(True)
        self.widgetValue.setToolTip(entry.key)

        if not showTTL:
            self.labelTTL.hide()
        if not showTimeStamp:
            self.labelTime.hide()

        self.updateValues()

    def updateValues(self):
        entry = self.entry

        if entry.expired:
            setBackgroundColor(self, expiredColor)
        elif entry.ttl:
            setBackgroundColor(self, ttlColor)

        if isinstance(self.widgetValue, ReadOnlyCheckBox):
            self.widgetValue.setChecked(entry.value == 'True')
        else:
            self.widgetValue.setText(entry.value)

        self.labelTTL.setText(str(entry.ttl or ''))
        self.labelTime.setText(entry.convertTime())

        if entry.ttl:
            # automatically refresh the value if the entry has a ttl (we don't
            # get timestamp updates from the server unless the value changes)
            time_to_update = max((entry.time + entry.ttl) - time.time(), 0)
            self.updateTimer.start(time_to_update * 1000)

    def setKey(self):
        """Sets the key locally and on the server."""
        dlg = EntryEditDialog(self)
        dlg.fillEntry(self.entry)
        dlg.valueTime.setText('')  # we want current timestamp by default
        dlg.valueKey.setReadOnly(True)
        if dlg.exec_() != QDialog.Accepted:
            return
        entry = dlg.getEntry()
        self.client.put(entry.key, entry)

    def delKey(self):
        if QMessageBox.question(self, 'Delete', 'Really delete?',
                                QMessageBox.Yes
                                | QMessageBox.No) == QMessageBox.No:
            return
        self.client.delete(self.entry.key)

    def watchKey(self):
        """Adds our key to the watcher window."""
        if not self.watcher:
            return
        widget = EntryWidget(self.client, None, self.entry, False, True, True,
                             self.watcher)
        self.watcher.addWidgetKey(widget)
        self.watcher.show()

    def keyUpdated(self, key, entry):
        if key != self.entry.key:
            return
        self.entry = entry
        self.updateValues()

    def updateTimerEvent(self):
        self.client.update(self.entry.key)
Esempio n. 12
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()
Esempio n. 13
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
Esempio n. 14
0
    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()
Esempio n. 15
0
class ValueDisplay(NicosWidget, QWidget):
    """Value display widget with two labels."""

    designer_description = 'A widget with name/value labels'
    designer_icon = ':/table'

    widgetInfo = pyqtSignal(str)

    dev = PropDef('dev', str, '', 'NICOS device name, if set, display '
                  'value of this device')
    key = PropDef('key', str, '', 'Cache key to display (without "nicos/"'
                  ' prefix), set either "dev" or this')
    statuskey = PropDef('statuskey', str, '', 'Cache key to extract status '
                        'information  for coloring value, if "dev" is '
                        'given this is set automatically')
    name = PropDef('name', str, '', 'Name of the value to display above/'
                   'left of the value; if "dev" is given this '
                   'defaults to the device name')
    unit = PropDef('unit', str, '', 'Unit of the value to display next to '
                   'the name; if "dev" is given this defaults to '
                   'the unit set in NICOS')
    format = PropDef('format', str, '', 'Python format string to use for the '
                     'value; if "dev" is given this defaults to the '
                     '"fmtstr" set in NICOS')
    maxlen = PropDef('maxlen', int, -1, 'Maximum length of the value string to '
                     'allow; defaults to no limit')
    width = PropDef('width', int, 8, 'Width of the widget in units of the '
                    'width of one character')
    istext = PropDef('istext', bool, False, 'If given, a "text" font will be '
                     'used for the value instead of the monospaced '
                     'font used for numeric values')
    showName = PropDef('showName', bool, True, 'If false, do not display the '
                       'label for the value name')
    showStatus = PropDef('showStatus', bool, True, 'If false, do not display '
                         'the device status as a color of the value text')
    showExpiration = PropDef('showExpiration', bool, True, 'If true, display '
                             'expired cache values as "n/a"')
    horizontal = PropDef('horizontal', bool, False, 'If true, display name '
                         'label left of the value instead of above it')

    def __init__(self, parent, designMode=False, colorScheme=None, **kwds):
        # keys being watched
        self._mainkeyid = None
        self._statuskeyid = None

        # other current values
        self._isfixed = ''
        # XXX could be taken from devinfo
        self._lastvalue = designMode and '1.4' or None
        self._laststatus = (OK, '')
        self._lastchange = 0
        self._mouseover = False
        self._mousetimer = None
        self._expired = True

        self._colorscheme = colorScheme or defaultColorScheme

        QWidget.__init__(self, parent, **kwds)
        NicosWidget.__init__(self)
        self._statuscolors = self._colorscheme['fore'][UNKNOWN], \
            self._colorscheme['back'][UNKNOWN]
        self._labelcolor = None

    def propertyUpdated(self, pname, value):
        if pname == 'dev':
            if value:
                self.key = value + '.value'
                self.statuskey = value + '.status'
        elif pname == 'width':
            if value < 0:
                self.reinitLayout()
            else:
                onechar = QFontMetrics(self.valueFont).width('0')
                self.valuelabel.setMinimumSize(QSize(onechar * (value + .5), 0))
        elif pname == 'istext':
            self.valuelabel.setFont(value and self.font() or self.valueFont)
            self.width = self.width
        elif pname == 'valueFont':
            self.valuelabel.setFont(self.valueFont)
            self.width = self.width  # update char width calculation
        elif pname == 'showName':
            self.namelabel.setVisible(value)
        elif pname == 'showStatus':
            if not value:
                setBothColors(self.valuelabel,
                              (self._colorscheme['fore'][UNKNOWN],
                               self._colorscheme['back'][UNKNOWN]))
        elif pname == 'horizontal':
            self.reinitLayout()
        if pname in ('dev', 'name', 'unit'):
            self.update_namelabel()
        NicosWidget.propertyUpdated(self, pname, value)

    def initUi(self):
        self.namelabel = QLabel(' ', self, textFormat=Qt.RichText)
        self.update_namelabel()

        valuelabel = SensitiveSMLabel('----', self, self._label_entered,
                                      self._label_left)
        valuelabel.setFrameShape(QFrame.Panel)
        valuelabel.setAlignment(Qt.AlignHCenter)
        valuelabel.setFrameShadow(QFrame.Sunken)
        valuelabel.setAutoFillBackground(True)
        setBothColors(valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                   self._colorscheme['back'][UNKNOWN]))
        valuelabel.setLineWidth(2)
        self.valuelabel = valuelabel
        self.width = 8

        self.reinitLayout()

    def reinitLayout(self):
        # reinitialize UI after switching horizontal/vertical layout
        if self.props['horizontal']:
            new_layout = QHBoxLayout()
            new_layout.addWidget(self.namelabel)
            new_layout.addStretch()
            new_layout.addWidget(self.valuelabel)
            self.namelabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        else:
            new_layout = QVBoxLayout()
            new_layout.addWidget(self.namelabel)
            tmplayout = QHBoxLayout()
            if self.width >= 0:
                tmplayout.addStretch()
            tmplayout.addWidget(self.valuelabel)
            if self.width >= 0:
                tmplayout.addStretch()
            new_layout.addLayout(tmplayout)
            self.namelabel.setAlignment(Qt.AlignHCenter)
        if self.layout():
            sip.delete(self.layout())
        new_layout.setContentsMargins(1, 1, 1, 1)  # save space
        self.setLayout(new_layout)

    def registerKeys(self):
        if self.props['dev']:
            self.registerDevice(self.props['dev'],
                                self.props['unit'], self.props['format'])
        else:
            self.registerKey(self.props['key'], self.props['statuskey'],
                             self.props['unit'], self.props['format'])

    def on_devValueChange(self, dev, value, strvalue, unitvalue, expired):
        # check expired values
        self._expired = expired
        self._lastvalue = value
        self._lastchange = currenttime()
        if self.props['maxlen'] > -1:
            self.valuelabel.setText(strvalue[:self.props['maxlen']])
        else:
            self.valuelabel.setText(strvalue)
        if self._expired:
            setBothColors(self.valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                            self._colorscheme['expired']))
            if self.props['showExpiration']:
                self.valuelabel.setText(NOT_AVAILABLE)
        elif not self.props['istext']:
            setBothColors(self.valuelabel, (self._colorscheme['fore'][BUSY],
                                            self._colorscheme['back'][BUSY]))
            QTimer.singleShot(1000, self._applystatuscolor)
        else:
            self._applystatuscolor()

    def _applystatuscolor(self):
        if self._expired:
            setBothColors(self.valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                            self._colorscheme['expired']))
        else:
            setBothColors(self.valuelabel, self._statuscolors)
            if self._labelcolor:
                self.namelabel.setAutoFillBackground(True)
                setBackgroundColor(self.namelabel, self._labelcolor)
            else:
                self.namelabel.setAutoFillBackground(False)

    def on_devStatusChange(self, dev, code, status, expired):
        if self.props['showStatus']:
            self._statuscolors = self._colorscheme['fore'][code], \
                self._colorscheme['back'][code]
            self._labelcolor = self._colorscheme['label'][code]
            self._laststatus = code, status
            self._applystatuscolor()

    def on_devMetaChange(self, dev, fmtstr, unit, fixed):
        self._isfixed = fixed and ' (F)'
        self.format = fmtstr
        self.unit = unit or ''

    def update_namelabel(self):
        name = self.props['name'] or self.props['dev'] or self.props['key']
        self.namelabel.setText(
            html.escape(str(name)) +
            ' <font color="#888888">%s</font><font color="#0000ff">%s</font> '
            % (html.escape(self.props['unit'].strip()), self._isfixed))

    def _label_entered(self, widget, event, from_mouse=True):
        infotext = '%s = %s' % (self.props['name'] or self.props['dev']
                                or self.props['key'], self.valuelabel.text())
        if self.props['unit'].strip():
            infotext += ' %s' % self.props['unit']
        if self.props['statuskey']:
            try:
                const, msg = self._laststatus
            except ValueError:
                const, msg = self._laststatus, ''
            infotext += ', status is %s: %s' % (statuses.get(const, '?'), msg)
        infotext += ', changed %s ago' % (
            nicedelta(currenttime() - self._lastchange))
        self.widgetInfo.emit(infotext)
        if from_mouse:
            self._mousetimer = QTimer(self, timeout=lambda:
                                      self._label_entered(widget, event, False)
                                      )
            self._mousetimer.start(1000)

    def _label_left(self, widget, event):
        if self._mousetimer:
            self._mousetimer.stop()
            self._mousetimer = None
            self.widgetInfo.emit('')
Esempio n. 16
0
 def __init__(self, frame, view):
     self._id2item = {}  # mapping from request ID to list widget item
     self._frame = frame
     self._view = view
     self._timer = QTimer(singleShot=True, timeout=self._timeout)
Esempio n. 17
0
class ELogPanel(Panel):
    """Provides a HTML widget for the electronic logbook."""

    panelName = 'Electronic logbook'

    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/elog.ui')
        self.preview = QWebView(self)
        self.frame.layout().addWidget(self.preview)

        self.timer = QTimer(self,
                            singleShot=True,
                            timeout=self.on_timer_timeout)
        self.propdir = None

        self.menus = None
        self.bar = None

        if client.isconnected:
            self.on_client_connected()
        client.connected.connect(self.on_client_connected)
        client.setup.connect(self.on_client_connected)
        client.experiment.connect(self.on_client_experiment)

        self.activeGroup = QActionGroup(self)
        self.activeGroup.addAction(self.actionAddComment)
        self.activeGroup.addAction(self.actionAddRemark)
        self.activeGroup.addAction(self.actionAttachFile)
        self.activeGroup.addAction(self.actionNewSample)

        page = self.preview.page()
        if hasattr(page, 'setForwardUnsupportedContent'):  # QWebKit only
            page.setForwardUnsupportedContent(True)
            page.unsupportedContent.connect(self.on_page_unsupportedContent)

    def getMenus(self):
        if not self.menus:
            menu1 = QMenu('&Browser', self)
            menu1.addAction(self.actionBack)
            menu1.addAction(self.actionForward)
            menu1.addSeparator()
            menu1.addAction(self.actionRefresh)
            menu1.addAction(self.actionPrint)
            menu2 = QMenu('&Logbook', self)
            menu2.addAction(self.actionAddComment)
            menu2.addAction(self.actionAddRemark)
            menu2.addSeparator()
            menu2.addAction(self.actionAttachFile)
            menu2.addSeparator()
            menu2.addAction(self.actionNewSample)
            self.menus = [menu1, menu2]

        return self.menus

    def getToolbars(self):
        if not self.bar:
            bar = QToolBar('Logbook')
            bar.addAction(self.actionBack)
            bar.addAction(self.actionForward)
            bar.addSeparator()
            bar.addAction(self.actionRefresh)
            bar.addAction(self.actionPrint)
            bar.addSeparator()
            bar.addAction(self.actionAddComment)
            bar.addAction(self.actionAddRemark)
            bar.addSeparator()
            bar.addAction(self.actionNewSample)
            bar.addAction(self.actionAttachFile)
            bar.addSeparator()
            box = QLineEdit(self)
            btn = QPushButton('Search', self)
            bar.addWidget(box)
            bar.addWidget(btn)

            def callback():
                if hasattr(QWebPage, 'FindWrapsAroundDocument'):  # WebKit
                    self.preview.findText(box.text(),
                                          QWebPage.FindWrapsAroundDocument)
                else:
                    # WebEngine wraps automatically
                    self.preview.findText(box.text())

            box.returnPressed.connect(callback)
            btn.clicked.connect(callback)
            self.bar = bar

        return [self.bar]

    def setViewOnly(self, viewonly):
        self.activeGroup.setEnabled(not viewonly)

    def on_timer_timeout(self):
        if hasattr(self.preview.page(), 'mainFrame'):  # QWebKit only
            try:
                frame = self.preview.page().mainFrame().childFrames()[1]
            except IndexError:
                self.log.error('No logbook seems to be loaded.')
                self.on_client_connected()
                return
            scrollval = frame.scrollBarValue(Qt.Vertical)
            was_at_bottom = scrollval == frame.scrollBarMaximum(Qt.Vertical)

            # restore current scrolling position in document on reload
            def callback(new_size):
                nframe = self.preview.page().mainFrame().childFrames()[1]
                if was_at_bottom:
                    nframe.setScrollBarValue(
                        Qt.Vertical, nframe.scrollBarMaximum(Qt.Vertical))
                else:
                    nframe.setScrollBarValue(Qt.Vertical, scrollval)
                self.preview.loadFinished.disconnect(callback)

            self.preview.loadFinished.connect(callback)
        self.preview.reload()

    def on_client_connected(self):
        self._update_content()

    def on_client_experiment(self, data):
        self._update_content()

    def _update_content(self):
        self.propdir = self.client.eval('session.experiment.proposalpath', '')
        if not self.propdir:
            return
        logfile = path.abspath(
            path.join(self.propdir, 'logbook', 'logbook.html'))
        if path.isfile(logfile):
            self.preview.load(QUrl('file://' + logfile))
        else:
            self.preview.setHtml(
                '<style>body { font-family: sans-serif; }</style>'
                '<p><b>The logbook HTML file does not seem to exist.</b></p>'
                '<p>Please check that the file is created and accessible on '
                '<b>your local computer</b> at %s.  Then click '
                '"refresh" above.' % html.escape(path.normpath(logfile)))

    def on_page_unsupportedContent(self, reply):
        if reply.url().scheme() != 'file':
            return
        filename = reply.url().path()
        if filename.endswith('.dat'):
            content = open(filename, encoding='utf-8', errors='replace').read()
            window = QMainWindow(self)
            window.resize(600, 800)
            window.setWindowTitle(filename)
            widget = QTextEdit(window)
            widget.setFontFamily('monospace')
            window.setCentralWidget(widget)
            widget.setText(content)
            window.show()
        else:
            # try to open the link with host computer default application
            try:
                QDesktopServices.openUrl(reply.url())
            except Exception:
                pass

    def on_refreshLabel_linkActivated(self, link):
        if link == 'refresh':
            self.on_timer_timeout()
        elif link == 'back':
            self.preview.back()
        elif link == 'forward':
            self.preview.forward()

    @pyqtSlot()
    def on_actionRefresh_triggered(self):
        # if for some reason, we have the wrong proposal path, update here
        propdir = self.client.eval('session.experiment.proposalpath', '')
        if propdir and propdir != self.propdir:
            self._update_content()
        else:
            self.on_timer_timeout()

    @pyqtSlot()
    def on_actionBack_triggered(self):
        self.preview.back()

    @pyqtSlot()
    def on_actionForward_triggered(self):
        self.preview.forward()

    @pyqtSlot()
    def on_actionNewSample_triggered(self):
        name, ok = QInputDialog.getText(self, 'New sample',
                                        'Please enter the new sample name:')
        if not ok or not name:
            return
        self.client.eval('NewSample(%r)' % name)
        self.timer.start(750)

    @pyqtSlot()
    def on_actionAddRemark_triggered(self):
        remark, ok = QInputDialog.getText(
            self, 'New remark',
            'Please enter the remark.  The remark will be added to the logbook '
            'as a heading and will also appear in the data files.')
        if not ok or not remark:
            return
        self.client.eval('Remark(%r)' % remark)
        self.timer.start(750)

    @pyqtSlot()
    def on_actionAddComment_triggered(self):
        dlg = dialogFromUi(self, 'panels/elog_comment.ui')
        dlg.helpFrame.setVisible(False)
        dlg.mdLabel.linkActivated.connect(
            lambda link: dlg.helpFrame.setVisible(True))
        if dlg.exec_() != QDialog.Accepted:
            return
        text = dlg.freeFormText.toPlainText()
        if not text:
            return
        self.client.eval('LogEntry(%r)' % text)
        self.timer.start(750)

    @pyqtSlot()
    def on_actionAttachFile_triggered(self):
        dlg = dialogFromUi(self, 'panels/elog_attach.ui')

        def on_fileSelect_clicked():
            self.selectInputFile(dlg.fileName, 'Choose a file to attach')
            dlg.fileRename.setFocus()

        dlg.fileSelect.clicked.connect(on_fileSelect_clicked)
        if dlg.exec_() != QDialog.Accepted:
            return
        fname = dlg.fileName.text()
        if not path.isfile(fname):
            return self.showError('The given file name is not a valid file.')
        newname = dlg.fileRename.text()
        if not newname:
            newname = path.basename(fname)
        desc = dlg.fileDesc.text()
        filecontent = open(fname, 'rb').read()
        remotefn = self.client.ask('transfer', filecontent)
        if remotefn is not None:
            self.client.eval('_LogAttach(%r, [%r], [%r])' %
                             (desc, remotefn, newname))
        self.timer.start(750)

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        # Let the user select the desired printer via the system printer list
        printer = QPrinter()
        dialog = QPrintDialog(printer)

        if not dialog.exec_():
            return

        mainFrame = self.preview.page().mainFrame()
        childFrames = mainFrame.childFrames()

        # Workaround for Qt versions < 4.8.0
        printWholeSite = True
        if hasattr(QWebView, 'selectedHtml'):
            if self.preview.hasSelection():
                printWholeSite = False

        # use whole frame if no content is selected or selecting html is not
        # supported
        if printWholeSite:
            # set 'content' frame active as printing an inactive web frame
            # doesn't work properly

            if len(childFrames) >= 2:
                childFrames[1].setFocus()

                # thanks to setFocus, we can get the print the frame
                # with evaluated javascript
                html = childFrames[1].toHtml()
        else:
            html = self.preview.selectedHtml()

            # construct head
            head = '<head>'

            # extract head from child frames
            for frame in childFrames:
                headEl = frame.findFirstElement('head')
                head += headEl.toInnerXml()

            head += '</head>'

            # concat new head and selection
            # the result may be invalid html; needs improvements!
            html = head + html

        # prepend a header to the log book
        html.replace('</head>', '</head><h1>NICOS Log book</h1>')

        # let qt layout the content
        doc = QTextDocument()
        doc.setHtml(html)

        doc.print_(printer)
Esempio n. 18
0
class ExpInfoPanel(Panel):
    """Provides a panel with several labels displaying basic experiment info.

    This is for example the experiment title, sample name, and user name.

    It also provides several buttons with which the user can change proposal
    info, sample properties, scan environment and setups.

    Options:

    * ``sample_panel`` -- what to show when the user clicks on the "Sample"
      button.  The value must be a panel configuration, e.g. ``panel('...')``
      or ``tabbed(...)``.

      There are several panels that are useful for this:

      - ``nicos.clients.gui.panels.setup_panel.GenericSamplePanel`` -- a panel
        that only shows a single input box for the sample name.
      - ``nicos.clients.gui.panels.setup_panel.TasSamplePanel`` -- a panel that
        also shows input boxes for triple-axis sample properties (such as
        lattice constants).

    * ``popup_proposal_after`` -- if given, the proposal dialog will be opened
      when the daemon has been idle for more than the specified time interval
      (in hours).
    * ``new_exp_panel`` -- class name of the panel which should be opened after
      a new experiment has been started from the proposal info panel.
    * ``finish_exp_panel`` -- class name of the panel which should be opened
      before an experiment is finished from the proposal info panel.
    """

    panelName = 'Experiment Info'
    _viewonly = False

    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/expinfo.ui')
        for ch in self.findChildren(NicosWidget):
            ch.setClient(client)
        client.setup.connect(self.on_client_setup)
        client.initstatus.connect(self.on_client_initstatus)

        self.detLabel.setFormatCallback(
            lambda value, strvalue: ', '.join(sorted(value)))
        self.envLabel.setFormatCallback(
            lambda value, strvalue: ', '.join(sorted(value)))

        self._sample_panel = options.get('sample_panel', GenericSamplePanel)
        self._new_exp_panel = options.get('new_exp_panel')
        self._finish_exp_panel = options.get('finish_exp_panel')
        self._timeout = options.get('popup_proposal_after', 0)
        if self._timeout:
            self._proposal_popup_timer = QTimer(interval=self._timeout * 3600000)
            self._proposal_popup_timer.setSingleShot(True)
            self._proposal_popup_timer.timeout.connect(
                self.on_proposal_popup_timer_timeout)
        else:
            self._proposal_popup_timer = None

    def hideTitle(self):
        self.titleLbl.setVisible(False)

    def setViewOnly(self, viewonly):
        self._viewonly = viewonly
        if not self._viewonly and self._timeout:
            # ask explicitly for status to restart timer if necessary
            self.client.ask('getstatus')

    def on_client_initstatus(self, initstatus):
        self.setupLabel.setText(', '.join(sorted(initstatus['setups'][1])))

    def on_client_setup(self, data):
        self.setupLabel.setText(', '.join(sorted(data[1])))

    def updateStatus(self, status, exception=False):
        if self._proposal_popup_timer:
            if status == 'idle':
                if not self._viewonly or \
                    (self.client.user_level is not None and
                     self.client.user_level < ADMIN):
                    self._proposal_popup_timer.start()
            else:
                self._proposal_popup_timer.stop()

    def on_proposal_popup_timer_timeout(self):
        if self._viewonly:
            return
        dlg = QMessageBox(self)
        dlg.setText('The experiment has been idle for more than %.1f hours.' %
                    self._timeout)
        contButton = QPushButton('Continue current experiment')
        finishAndNewButton = QPushButton('Finish and start new experiment')
        dlg.addButton(contButton, QMessageBox.RejectRole)
        dlg.addButton(finishAndNewButton, QMessageBox.ActionRole)
        dlg.exec_()
        if dlg.clickedButton() == finishAndNewButton:
            self.on_proposalBtn_clicked()
        elif dlg.clickedButton() == contButton:
            self._proposal_popup_timer.start()

    @pyqtSlot()
    def on_proposalBtn_clicked(self):
        dlg = PanelDialog(self, self.client, ExpPanel, 'Proposal info',
                          new_exp_panel=self._new_exp_panel,
                          finish_exp_panel=self._finish_exp_panel)
        dlg.exec_()

    @pyqtSlot()
    def on_setupBtn_clicked(self):
        dlg = PanelDialog(self, self.client, SetupsPanel, 'Setups')
        dlg.exec_()

    @pyqtSlot()
    def on_sampleBtn_clicked(self):
        dlg = PanelDialog(self, self.client, self._sample_panel,
                          'Sample information')
        dlg.exec_()

    @pyqtSlot()
    def on_detenvBtn_clicked(self):
        dlg = PanelDialog(self, self.client, DetEnvPanel,
                          'Detectors and environment')
        dlg.exec_()

    @pyqtSlot()
    def on_remarkBtn_clicked(self):
        dlg = dialogFromUi(self, 'panels/expinfo_remark.ui')

        def callback():
            self.showInfo('The remark will be added to the logbook as a '
                          'heading and will also appear in the data files.')
        dlg.buttonBox.helpRequested.connect(callback)

        for ch in dlg.findChildren(NicosWidget):
            ch.setClient(self.client)
        dlg.remarkEdit.setFocus()
        if not dlg.exec_():
            return
        self.client.run('Remark(%r)' % dlg.remarkEdit.getValue())
Esempio n. 19
0
    def __init__(self, widget, name, keys_indices, interval, fromtime, totime,
                 yfrom, yto, window, meta, dlginfo, query_func):
        QObject.__init__(self)
        self.name = name
        self.dlginfo = dlginfo

        self.fromtime = fromtime
        self.totime = totime
        self.yfrom = yfrom
        self.yto = yto
        self.window = window

        self._key_indices = {}
        self.uniq_keys = set()
        self.series = OrderedDict()
        self.timer = None

        # + 60 seconds: get all values, also those added while querying
        hist_totime = self.totime or currenttime() + 60
        hist_cache = {}

        iterator = enumerate(keys_indices)
        if fromtime is not None:
            iterator = enumerateWithProgress(keys_indices,
                                             'Querying history...',
                                             force_display=True)

        for _, (key, index, scale, offset) in iterator:
            real_indices = [index]
            history = None
            self.uniq_keys.add(key)

            if fromtime is not None:
                if key not in hist_cache:
                    history = query_func(key, self.fromtime, hist_totime)
                    if not history:
                        from nicos.clients.gui.main import log
                        if log is None:
                            from __main__ import log  # pylint: disable=no-name-in-module
                        log.error('Error getting history for %s.', key)
                        QMessageBox.warning(
                            widget, 'Error', 'Could not get history for %s, '
                            'there are no values to show.\n'
                            'Is it spelled correctly?' % key)
                        history = []
                    hist_cache[key] = history
                else:
                    history = hist_cache[key]
                # if the value is a list/tuple and we don't have an index
                # specified, add a plot for each item
                if history:
                    first_value = history[0][1]
                    if not index and isinstance(first_value, (list, tuple)):
                        real_indices = tuple(
                            (i, ) for i in range(len(first_value)))
            for index in real_indices:
                name = '%s[%s]' % (key,
                                   ','.join(map(str, index))) if index else key
                series = TimeSeries(name, interval, scale, offset, window,
                                    self, meta[0].get(key), meta[1].get(key))
                self.series[key, index] = series
                if history:
                    series.init_from_history(history, fromtime, totime
                                             or currenttime(), index)
                else:
                    series.init_empty()
            self._key_indices.setdefault(key, []).extend(real_indices)

        self.listitem = None
        self.plot = None
        if self.totime is None:
            # add another point with the same value every interval time (but
            # not more often than 11 seconds)
            self.timer = QTimer(self, interval=max(interval, 11) * 1000)
            self.timer.timeout.connect(self.on_timer_timeout)
            self.timer.start()

        self.timeSeriesUpdate.connect(self.on_timeSeriesUpdate)
Esempio n. 20
0
class HexapodTool(DlgUtils, QMainWindow):
    toolName = 'HexapodTool'

    def __init__(self, parent, _client, **_kwds):
        QMainWindow.__init__(self, parent)
        DlgUtils.__init__(self, 'Hexapod')

        # set during actions that will call signal handlers
        self.recursive = False

        loadUi(self, findResource('nicos_mlz/kws1/gui/tools/hexapod.ui'))

        for but in (self.butStart, self.butSetWorkspace, self.butSetFrame,
                    self.butSaveVel):
            but.setEnabled(False)

        self.axes = {}
        self.axeslist = []

        try:
            self._controller = PyTango.DeviceProxy(TANGO_DEV_BASE +
                                                   'controller')
            # make sure the server is running and create remaining proxies
            try:
                self._controller.State()
            except AttributeError:
                raise Exception('server appears to be not running')
            for axis in AXES:
                self.axes[axis] = PyTango.DeviceProxy(TANGO_DEV_BASE + axis)
                self.axeslist.append(self.axes[axis])
        except Exception as err:
            self.showError('could not connect to tango server: %s' % err)
            self.deleteLater()
            return

        self.on_cbsWorkspace_activated(0)
        self.on_cbsFrame_activated(self.cbsFrame.currentText())

        tx_speed = self.query_attr(self.axes['tx'], 'speed')
        self.inpVelTrans.setValue(tx_speed)
        self.lblVelTrans.setText(self.inpVelTrans.text())
        self.inpVelRot.setValue(self.query_attr(self.axes['rx'], 'speed'))
        self.lblVelRot.setText(self.inpVelRot.text())
        self.inpVelOmega.setValue(self.query_attr(self.axes['omega'], 'speed'))
        self.lblVelOmega.setText(self.inpVelOmega.text())

        # ramp time = speed / acceleration
        self.inpRampUp.setValue(tx_speed /
                                self.query_attr(self.axes['tx'], 'accel'))
        self.lblRampUp.setText(self.inpRampUp.text())
        self.inpRampDown.setValue(tx_speed /
                                  self.query_attr(self.axes['tx'], 'decel'))
        self.lblRampDown.setText(self.inpRampDown.text())

        self.updTimer = QTimer()
        self.updTimer.timeout.connect(self.updateTimer)
        self.updTimer.start(1000)

    def exec_cmd(self, dev, cmd, args=None):
        try:
            return dev.command_inout(cmd, args)
        except Exception as err:
            self.showError('could not execute %s on hexapod:\n%s' % (cmd, err))
            raise

    def query_attr(self, dev, attr):
        try:
            return getattr(dev, attr)
        except Exception as err:
            self.showError('could not query %s on hexapod:\n%s' % (attr, err))
            raise

    def set_attr(self, dev, attr, value):
        try:
            setattr(dev, attr, value)
        except Exception as err:
            self.showError('could not set %s on hexapod:\n%s' % (attr, err))
            raise

    @pyqtSlot()
    def on_butExit_clicked(self):
        self.close()
        self.deleteLater()

    def on_inpNewXX_valueChanged(self, v):
        self.butStart.setEnabled(True)

    @pyqtSlot()
    def on_butStart_clicked(self):
        self.exec_cmd(
            self._controller,
            'StartSynchronousMovement',
            [
                self.inpNewTX.value(),
                self.inpNewTY.value(),
                self.inpNewTZ.value(),
                self.inpNewRZ.value(),
                self.inpNewRY.value(),
                self.inpNewRX.value(),
                self.inpNewOmega.value(),
                0.0,  # detector arm dummy value
            ])
        self.butStart.setEnabled(False)

    @pyqtSlot()
    def on_butStop_clicked(self):
        try:
            self.exec_cmd(self._controller, 'Stop')
        except Exception as err:
            self.showInfo('exception raised after executing stop on hexapod:\n'
                          '%s' % err)

    def updateTimer(self):
        msg = '<font color=darkblue>' + self.exec_cmd(self._controller,
                                                      'Status')
        if 'not referenced' in self.exec_cmd(self.axes['omega'], 'Status'):
            msg += ' (omega not referenced)'
        msg += '</font>'
        self.lblStatus.setText(msg)

        pos = [self.query_attr(axis, 'value') for axis in self.axeslist]
        for value, widget in zip(pos, [
                self.lblCurTX, self.lblCurTY, self.lblCurTZ, self.lblCurRZ,
                self.lblCurRY, self.lblCurRX, self.lblCurOmega
        ]):
            widget.setText('%8.3f' % value)

    @pyqtSlot(str)
    def on_cbsFrame_activated(self, text):
        frame = text.split()[0].upper()
        values = self.query_attr(self._controller, 'frame' + frame)
        for value, widget in zip(values, [
                self.inpFrameTX, self.inpFrameTY, self.inpFrameTZ,
                self.inpFrameRZ, self.inpFrameRY, self.inpFrameRX
        ]):
            widget.setValue(value)
        self.butSetFrame.setEnabled(False)

    def on_inpFrameXX_valueChanged(self, _value):
        if self.cbsFrame.currentIndex() <= 3:
            self.butSetFrame.setEnabled(True)

    def on_inpWsXX_valueChanged(self, _value):
        if not self.recursive:
            self.butSetWorkspace.setEnabled(True)

    @pyqtSlot()
    def on_butSetWorkspace_clicked(self):
        self.recursive = True
        try:
            index = self.cbsWorkspace.currentIndex()
            group, index = ('juelich', index - 1) if index else ('hexamove', 0)
            workspaces = self.query_attr(self._controller,
                                         group + 'workspaceDefinitions')
            workspaces = [[i] + (ws if i != index else [
                self.inpWsTXmin.value(),
                self.inpWsTXmax.value(),
                self.inpWsTYmin.value(),
                self.inpWsTYmax.value(),
                self.inpWsTZmin.value(),
                self.inpWsTZmax.value(),
                self.inpWsRZmin.value(),
                self.inpWsRZmax.value(),
                self.inpWsRYmin.value(),
                self.inpWsRYmax.value(),
                self.inpWsRXmin.value(),
                self.inpWsRXmax.value(),
                self.inpWsTXref.value(),
                self.inpWsTYref.value(),
                self.inpWsTZref.value(),
                self.inpWsRZref.value(),
                self.inpWsRYref.value(),
                self.inpWsRXref.value(),
            ]) for i, ws in enumerate(workspaces)]
            self._controller.set_property(
                {'init{}workspaces'.format(group): workspaces})
        finally:
            self.recursive = False

    def on_togEnableWorkspace_toggled(self, checked):
        if self.recursive:
            return
        index = self.cbsWorkspace.currentIndex()
        group, index = ('juelich', index - 1) if index else ('hexamove', 0)
        self.exec_cmd(
            self._controller,
            ('Enable' if checked else 'Disable') + group + 'Workspace',
            index,
        )

    @pyqtSlot(int)
    def on_cbsWorkspace_activated(self, index):
        group, index = ('juelich', index - 1) if index else ('hexamove', 0)
        workspace = enabled = []
        for _ in range(5):
            workspace = self.query_attr(self._controller,
                                        group + 'WorkspaceDefinitions')[index]
            enabled = self.query_attr(self._controller,
                                      group + 'WorkspaceStatus')[index]
            # check reference frame values; if they are out of range then
            # do a few retries (the library sometimes returns dummy values)
            # TODO: check if this is still necessary with the new protocol
            reftx, refty, reftz, refrz, refry, refrx = workspace[-6:]
            if (-50 <= reftx <= +50) and (-50 <= refty <= +50) and \
               (-50 <= reftz <= 510) and (-5 <= refrx <= +5) and \
               (-5 <= refry <= +5) and (-180 <= refrz <= +180):
                break
        self.recursive = True
        try:
            for value, widget in zip(workspace, [
                    self.inpWsTXmin,
                    self.inpWsTXmax,
                    self.inpWsTYmin,
                    self.inpWsTYmax,
                    self.inpWsTZmin,
                    self.inpWsTZmax,
                    self.inpWsRZmin,
                    self.inpWsRZmax,
                    self.inpWsRYmin,
                    self.inpWsRYmax,
                    self.inpWsRXmin,
                    self.inpWsRXmax,
                    self.inpWsTXref,
                    self.inpWsTYref,
                    self.inpWsTZref,
                    self.inpWsRZref,
                    self.inpWsRYref,
                    self.inpWsRXref,
            ]):
                widget.setValue(value)
            self.togEnableWorkspace.setChecked(enabled)
        finally:
            self.recursive = False
        self.butSetWorkspace.setEnabled(False)

    def on_togBirne_toggled(self, checked):
        self.set_attr(self._controller, 'manualControl', checked)
        self.grpFrames.setEnabled(not checked)
        self.grpNewPos.setEnabled(not checked)
        self.grpWorkspaces.setEnabled(not checked)

    @pyqtSlot()
    def on_butResetSystem_clicked(self):
        self.exec_cmd(self._controller, 'Reset')

    @pyqtSlot()
    def on_butCopyCurrent_clicked(self):
        for inwidget, labelwidget in zip([
                self.inpNewTX, self.inpNewTY, self.inpNewTZ, self.inpNewRZ,
                self.inpNewRY, self.inpNewRX, self.inpNewOmega
        ], [
                self.lblCurTX, self.lblCurTY, self.lblCurTZ, self.lblCurRZ,
                self.lblCurRY, self.lblCurRX, self.lblCurOmega
        ]):
            inwidget.setValue(float(labelwidget.text().replace(',', '.')))

    @pyqtSlot()
    def on_actionReference_drive_triggered(self):
        self.exec_cmd(self.axes['omega'], 'Reference')

    @pyqtSlot()
    def on_actionSet_Zero_triggered(self):
        self.exec_cmd(self.axes['omega'], 'Adjust', 0)

    def on_inpVelXX_valueChanged(self, _v):
        self.butSaveVel.setEnabled(True)

    @pyqtSlot()
    def on_butSaveVel_clicked(self):
        self.set_attr(self.axes['tx'], 'speed', self.inpVelTrans.value())
        self.set_attr(self.axes['rx'], 'speed', self.inpVelRot.value())
        self.set_attr(self.axes['omega'], 'speed', self.inpVelOmega.value())
        # acceleration = speed / ramp time
        self.set_attr(self.axes['tx'], 'accel',
                      self.inpVelTrans.value() / self.inpRampUp.value())
        self.set_attr(self.axes['tx'], 'decel',
                      self.inpVelTrans.value() / self.inpRampDown.value())
        self.butSaveVel.setEnabled(False)

        self.lblVelTrans.setText(self.inpVelTrans.text())
        self.lblVelRot.setText(self.inpVelRot.text())
        self.lblVelOmega.setText(self.inpVelOmega.text())
        self.lblRampUp.setText(self.inpRampUp.text())
        self.lblRampDown.setText(self.inpRampDown.text())

    def on_inpFrameTX_valueChanged(self, v):
        self.on_inpFrameXX_valueChanged(v)

    def on_inpFrameTY_valueChanged(self, v):
        self.on_inpFrameXX_valueChanged(v)

    def on_inpFrameTZ_valueChanged(self, v):
        self.on_inpFrameXX_valueChanged(v)

    def on_inpFrameRX_valueChanged(self, v):
        self.on_inpFrameXX_valueChanged(v)

    def on_inpFrameRY_valueChanged(self, v):
        self.on_inpFrameXX_valueChanged(v)

    def on_inpFrameRZ_valueChanged(self, v):
        self.on_inpFrameXX_valueChanged(v)

    def on_inpWsTXmax_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTXmin_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTXref_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTYmax_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTYmin_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTYref_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTZmax_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTZmin_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsTZref_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRXmax_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRXmin_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRXref_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRYmax_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRYmin_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRYref_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRZmax_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRZmin_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpWsRZref_valueChanged(self, v):
        self.on_inpWsXX_valueChanged(v)

    def on_inpNewOmega_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpNewRZ_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpNewRY_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpNewRX_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpNewTZ_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpNewTY_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpNewTX_valueChanged(self, v):
        self.on_inpNewXX_valueChanged(v)

    def on_inpRampDown_valueChanged(self, v):
        self.on_inpVelXX_valueChanged(v)

    def on_inpRampUp_valueChanged(self, v):
        self.on_inpVelXX_valueChanged(v)

    def on_inpVelTrans_valueChanged(self, v):
        self.on_inpVelXX_valueChanged(v)

    def on_inpVelOmega_valueChanged(self, v):
        self.on_inpVelXX_valueChanged(v)

    def on_inpVelRot_valueChanged(self, v):
        self.on_inpVelXX_valueChanged(v)