コード例 #1
0
    def _refresh_presets(self):
        pmenu = self.presetmenu
        pmenu.clear()
        delmenu = QMenu('Delete', self)
        try:
            for preset, info in iteritems(self.presetdict):
                paction = QAction(preset, self)
                pdelaction = QAction(preset, self)
                info = info.copy()

                def launchpreset(on, info=info):
                    self._createViewFromDialog(info)

                def delpreset(on, name=preset, act=paction, delact=pdelaction):
                    pmenu.removeAction(act)
                    delmenu.removeAction(delact)
                    self.presetdict.pop(name, None)
                    self._refresh_presets()

                paction.triggered[bool].connect(launchpreset)
                pmenu.addAction(paction)
                pdelaction.triggered[bool].connect(delpreset)
                delmenu.addAction(pdelaction)
        except AttributeError:
            self.presetdict = {}
        if self.presetdict:
            pmenu.addSeparator()
            pmenu.addMenu(delmenu)
        else:
            pmenu.addAction('(no presets created)')
コード例 #2
0
    def on_tableWidget_customContextMenuRequested(self, point):
        """Show context menu for adding and deleting echotimes (rows)."""
        self._stop_edit()

        menu = QMenu(self)
        add = menu.addAction('Add echo time')
        delete = menu.addAction('Delete echo time')

        row = self.tableWidget.rowAt(point.y())

        # Disable the delete action if there is nothing to delete
        if row == -1:
            delete.setEnabled(False)

        sender = self.sender()
        # The Signal can be sent from the table widget itself or from the
        # vertical header. In case of the table widget, its viewport hast to be
        # used for correct placement of the context mneu
        if hasattr(sender, 'viewport'):
            sender = sender.viewport()

        action = menu.exec_(sender.mapToGlobal(point))

        if action == add:
            self._add_row(row)
        elif action == delete:
            self._delete_row(row)
コード例 #3
0
 def getMenus(self):
     if not self.menu:
         menu = QMenu('&Live data', self)
         menu.addAction(self.actionPrint)
         menu.addSeparator()
         menu.addAction(self.actionUnzoom)
         menu.addAction(self.actionLogScale)
         self.menu = menu
     return [self.menu]
コード例 #4
0
ファイル: live.py プロジェクト: ess-dmsc/nicos
    def initLiveWidget(self, widgetcls):
        if isinstance(self.widget, widgetcls):
            return

        # delete the old widget
        if self.widget:
            self.widgetLayout.removeWidget(self.widget)
            self.widget.deleteLater()

        # create a new one
        self.widget = widgetcls(self)

        # enable/disable controls and set defaults for new livewidget instances
        self.setControlsEnabled(True)
        if isinstance(self.widget, LiveWidget1D):
            self.set2DControlsEnabled(False)
        else:
            self.set2DControlsEnabled(True)

        # apply current global settings
        self.widget.setCenterMark(self.actionMarkCenter.isChecked())
        self.widget.logscale(self.actionLogScale.isChecked())
        if isinstance(self.widget, LiveWidget1D):
            self.widget.setSymbols(self.actionSymbols.isChecked())
            self.widget.setLines(self.actionLines.isChecked())
        # liveonly mode does not display a status bar
        if self._liveOnlyIndex is None:
            self.widget.gr.cbm.addHandler(MouseEvent.MOUSE_MOVE,
                                          self.on_mousemove_gr)

        # handle menus
        self.menuColormap = QMenu(self)
        self.actionsColormap = QActionGroup(self)
        activeMap = self.widget.getColormap()
        activeCaption = None
        for name, value in COLORMAPS.items():
            caption = name.title()
            action = self.menuColormap.addAction(caption)
            action.setData(caption)
            action.setCheckable(True)
            if activeMap == value:
                action.setChecked(True)
                # update toolButton text later otherwise this may fail
                # depending on the setup and qt versions in use
                activeCaption = caption
            self.actionsColormap.addAction(action)
            action.triggered.connect(self.on_colormap_triggered)
        self.actionColormap.setMenu(self.menuColormap)

        # finish initiation
        self.widgetLayout.addWidget(self.widget)
        if activeCaption:
            self.toolbar.widgetForAction(
                self.actionColormap).setText(activeCaption)
        detectors = self.client.eval('session.experiment.detectors', [])
        self._register_rois(detectors)
コード例 #5
0
    def contextMenuOnItem(self, item, pos):
        if item is None:
            return  # invoked context menu on whitespace

        topLevelItems = []
        currentIndex = 0
        while currentIndex < self.topLevelItemCount():
            topLevelItems.append(self.topLevelItem(currentIndex))
            currentIndex += 1
        if self.currentItem() in topLevelItems:
            if self.currentItem().text(0) in self.nonListItems:
                if self.currentItem().childCount() > 0:
                    return  # value is already set, can't add multiple values
            menu = QMenu(self)
            addValueAction = menu.addAction('Add value...')
            addValueAction.triggered.connect(self.addValue)
            menu.popup(pos)
コード例 #6
0
    def __init__(self, parent, client, options):
        LokiPanelBase.__init__(self, parent, client, options)
        loadUi(self,
               findResource('nicos_ess/loki/gui/ui_files/scriptbuilder.ui'))

        self.window = parent
        self.runBtn.setVisible(False)
        self.mapping = {}
        self.expertmode = self.mainwindow.expertmode

        self._cmdlet = self.sender()
        self._layout = self.frame.layout()
        self.index = self._layout.indexOf(self._cmdlet)

        self.initialise_connection_status_listeners()

        modules = options.get('modules', [])
        for module in modules:
            importString(module)  # should register cmdlets

        for cmdlet in all_cmdlets:

            def callback(on, cmdlet=cmdlet):
                inst = cmdlet(self, self.client)
                inst.cmdletUp.connect(self.on_cmdletUp)
                inst.cmdletDown.connect(self.on_cmdletDown)
                inst.cmdletRemove.connect(self.on_cmdletRemove)
                self.runBtn.setVisible(True)
                self.frame.layout().insertWidget(
                    self.frame.layout().count() - 2, inst)

            action = QAction(cmdlet.name, self)
            action.triggered.connect(callback)
            self.mapping.setdefault(cmdlet.category, []).append(action)

        for category in all_categories[::-1]:
            if category not in self.mapping:
                return
            toolbtn = QToolButton(self)
            toolbtn.setText(category)
            toolbtn.setPopupMode(QToolButton.InstantPopup)
            menu = QMenu(self)
            menu.addActions(self.mapping[category])
            toolbtn.setMenu(menu)
            self.btnLayout.insertWidget(1, toolbtn)
コード例 #7
0
 def on_roiItemClicked(self, event):
     if event.getButtons() & MouseEvent.RIGHT_BUTTON:
         if isinstance(event.roi.reference, FitResult):
             menu = QMenu(self)
             actionClipboard = QAction("Copy fit values to clipboard", menu)
             menu.addAction(actionClipboard)
             p0dc = event.getDC()
             selectedItem = menu.exec_(
                 self.mapToGlobal(QPoint(p0dc.x, p0dc.y)))
             if selectedItem == actionClipboard:
                 res = event.roi.reference
                 text = '\n'.join(
                     (n + '\t' if n else '\t') +
                     (v + '\t' if isinstance(v, string_types) else '%g\t' %
                      v) +
                     (dv if isinstance(dv, string_types) else '%g' % dv)
                     for (n, v, dv) in res.label_contents)
                 QApplication.clipboard().setText(text)
コード例 #8
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, self.ui)

        self.window = parent
        self.mapping = {}
        self.current_cmdlet = None
        self.expertmode = self.mainwindow.expertmode

        # collect values of all cmdlets that have been added
        # so that the common fields carry over to the next cmdlet
        self.value_collection = {}

        self.commandInput.history = self.cmdhistory
        self.commandInput.completion_callback = self.completeInput
        self.console = None

        client.initstatus.connect(self.on_client_initstatus)
        client.mode.connect(self.on_client_mode)
        client.simresult.connect(self.on_client_simresult)

        modules = options.get('modules', [])
        for module in modules:
            importString(module)  # should register cmdlets

        for cmdlet in all_cmdlets:
            action = QAction(cmdlet.name, self)

            def callback(on, cmdlet=cmdlet):
                self.selectCmdlet(cmdlet)

            action.triggered.connect(callback)
            self.mapping.setdefault(cmdlet.category, []).append(action)

        for category in all_categories[::-1]:
            if category not in self.mapping:
                continue
            toolbtn = QToolButton(self)
            toolbtn.setText(category)
            toolbtn.setPopupMode(QToolButton.InstantPopup)
            menu = QMenu(self)
            menu.addActions(self.mapping[category])
            toolbtn.setMenu(menu)
            self.btnLayout.insertWidget(1, toolbtn)
コード例 #9
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, self.ui)

        self.commandInput.scrollWidget = self.outView
        self.grepPanel.hide()
        self.grepText.scrollWidget = self.outView
        self.actionLabel.hide()
        self.outView.setActionLabel(self.actionLabel)
        self.commandInput.history = self.cmdhistory
        self.commandInput.completion_callback = self.completeInput
        self.grepNoMatch.setVisible(False)
        self.actionAllowLineWrap.setChecked(self.mainwindow.allowoutputlinewrap)

        client.connected.connect(self.on_client_connected)
        client.message.connect(self.on_client_message)
        client.simmessage.connect(self.on_client_simmessage)
        client.initstatus.connect(self.on_client_initstatus)
        client.mode.connect(self.on_client_mode)
        client.experiment.connect(self.on_client_experiment)

        self.outView.setContextMenuPolicy(Qt.CustomContextMenu)

        self.menu = QMenu('&Output', self)
        self.menu.addAction(self.actionCopy)
        self.menu.addAction(self.actionGrep)
        self.menu.addSeparator()
        self.menu.addAction(self.actionSave)
        self.menu.addAction(self.actionPrint)
        self.menu.addSeparator()
        self.menu.addAction(self.actionAllowLineWrap)
        self.on_actionAllowLineWrap_triggered(
            self.mainwindow.allowoutputlinewrap)

        self.hasinput = bool(options.get('hasinput', True))
        self.inputFrame.setVisible(self.hasinput)
        self.hasmenu = bool(options.get('hasmenu', True))
        if options.get('fulltime', False):
            self.outView.setFullTimestamps(True)
        watermark = options.get('watermark', '')
        if watermark:
            watermark = findResource(watermark)
            if path.isfile(watermark):
                self.outView.setBackgroundImage(watermark)
コード例 #10
0
    def initLiveWidget(self, widgetcls):
        if isinstance(self.widget, widgetcls):
            return

        if self.widget:
            self.widgetLayout.removeWidget(self.widget)
            self.widget.deleteLater()
        self.widget = widgetcls(self)
        # enable/disable controls and set defaults for new livewidget instances
        self.setControlsEnabled(True)
        if isinstance(self.widget, LiveWidget1D):
            self.set2DControlsEnabled(False)
        else:
            self.set2DControlsEnabled(True)
        # apply current settings
        self.widget.setCenterMark(self.actionMarkCenter.isChecked())
        self.widget.logscale(self.actionLogScale.isChecked())
        guiConn = GUIConnector(self.widget.gr)
        guiConn.connect(MouseEvent.MOUSE_MOVE, self.on_mousemove_gr)

        self.menuColormap = QMenu(self)
        self.actionsColormap = QActionGroup(self)
        activeMap = self.widget.getColormap()
        activeCaption = None
        for name, value in iteritems(COLORMAPS):
            caption = name.title()
            action = self.menuColormap.addAction(caption)
            action.setData(caption)
            action.setCheckable(True)
            if activeMap == value:
                action.setChecked(True)
                # update toolButton text later otherwise this may fail
                # depending on the setup and qt versions in use
                activeCaption = caption
            self.actionsColormap.addAction(action)
            action.triggered.connect(self.on_colormap_triggered)
        self.actionColormap.setMenu(self.menuColormap)
        self.widgetLayout.addWidget(self.widget)
        if activeCaption:
            self.toolbar.widgetForAction(
                self.actionColormap).setText(activeCaption)
        detectors = self.client.eval('session.experiment.detectors', [])
        self._register_rois(detectors)
コード例 #11
0
ファイル: sampleconf.py プロジェクト: ess-dmsc/nicos
    def __init__(self, parent, client, options):
        LokiPanelBase.__init__(self, parent, client, options)
        loadUi(self, findResource('nicos_ess/loki/gui/ui_files/sampleconf.ui'))
        self.sampleGroup.setEnabled(False)
        self.frame.setLayout(QVBoxLayout())

        self.sample_frame = QFrame(self)
        loadUi(
            self.sample_frame,
            findResource('nicos_ess/loki/gui/ui_files/sampleconf_summary.ui'))

        layout = self.frame.layout()
        layout.addWidget(self.sample_frame)
        self.sample_frame.hide()

        self.sample_frame.posTbl.setEnabled(False)

        for box in self.sample_frame.findChildren(QLineEdit):
            box.setEnabled(False)

        menu = QMenu(self)
        menu.addAction(self.actionEmpty)
        menu.addAction(self.actionGenerate)
        self.createBtn.setMenu(menu)

        self.configs = []
        self.holder_info = options.get('holder_info', [])
        self.instrument = options.get('instrument', 'loki')
        self.unapplied_changes = False
        self.applyBtn.setEnabled(False)
        self.initialise_connection_status_listeners()
コード例 #12
0
ファイル: newdevicedialog.py プロジェクト: umithardal/nicos
    def __init__(self, classesList, parent=None):
        QDialog.__init__(self, parent)
        uic.loadUi(
            path.abspath(
                path.join(path.dirname(__file__), '..', 'ui', 'dialogs',
                          'newdevicedialog.ui')), self)
        self.menu = QMenu('Select class')
        self.pushButtonSelectClass.setMenu(self.menu)
        for _class in sorted([str(__class)[14:-2] for __class in classesList]):
            self.recursiveMenu(_class, self.menu)

        self.menuCustom = QMenu('Select class')
        for _class in sorted([
                str(__class)[14:-2]
                for __class in classparser.getDeviceClasses(None)
        ]):
            self.recursiveMenu(_class, self.menuCustom)

        self.checkBoxCustomClasses.stateChanged.connect(
            self.stateChangedHandler)

        self.lineEditDeviceName.setValidator(
            QRegExpValidator(QRegExp('[A-Za-z0-9_]*')))
コード例 #13
0
 def getMenus(self):
     self.menu = menu = QMenu('&Live data', self)
     menu.addAction(self.actionLoadTOF)
     menu.addAction(self.actionLoadPAD)
     menu.addSeparator()
     menu.addAction(self.actionWriteXml)
     menu.addAction(self.actionPrint)
     menu.addSeparator()
     menu.addAction(self.actionSetAsROI)
     menu.addAction(self.actionUnzoom)
     menu.addAction(self.actionLogScale)
     menu.addAction(self.actionNormalized)
     menu.addAction(self.actionLegend)
     return [menu]
コード例 #14
0
 def _register_rois(self, detectors):
     self.rois.clear()
     self.actionROI.setVisible(False)
     self.menuROI = QMenu(self)
     self.actionsROI = QActionGroup(self)
     self.actionsROI.setExclusive(False)
     for detname in detectors:
         self.log.debug('checking rois for detector \'%s\'', detname)
         for tup in self.client.eval(detname + '.postprocess', ''):
             roi = tup[0]
             cachekey = roi + '/roi'
             # check whether or not this is a roi (cachekey exists).
             keyval = self.client.getCacheKey(cachekey)
             if keyval:
                 self.on_roiChange(cachekey, keyval[1])
                 self.log.debug('register roi: %s', roi)
                 # create roi menu
                 action = self.menuROI.addAction(roi)
                 action.setData(roi)
                 action.setCheckable(True)
                 self.actionsROI.addAction(action)
                 action.triggered.connect(self.on_roi_triggered)
                 self.actionROI.setMenu(self.menuROI)
                 self.actionROI.setVisible(True)
コード例 #15
0
ファイル: newdevicedialog.py プロジェクト: umithardal/nicos
    def recursiveMenu(self, partialString, parentMenu):
        if len(partialString.split('.')) == 1:
            action = parentMenu.addAction(partialString)
            action.triggered.connect(self.classSelectedSlot)
        else:
            uncombinedNextPartialString = partialString.split('.')
            menus = parentMenu.findChildren(QMenu)

            submenuIsPresent = False
            submenu = None
            for menu in menus:
                if menu.title() == uncombinedNextPartialString[0]:
                    submenu = menu
                    uncombinedNextPartialString.pop(0)
                    submenuIsPresent = True

            if not submenuIsPresent:
                submenu = QMenu(uncombinedNextPartialString.pop(0), parentMenu)
                parentMenu.addMenu(submenu)

            nextPartialString = '.'.join(uncombinedNextPartialString)
            self.recursiveMenu(nextPartialString, submenu)
コード例 #16
0
    def on_paramList_customContextMenuRequested(self, pos):
        item = self.paramList.itemAt(pos)
        if not item:
            return

        menu = QMenu(self)
        refreshAction = menu.addAction('Refresh')
        menu.addAction('Refresh all')

        # QCursor.pos is more reliable then the given pos
        action = menu.exec_(QCursor.pos())

        if action:
            cmd = 'session.getDevice(%r).pollParams(volatile_only=False%s)' \
                  % (self.devname, ', param_list=[%r]' % item.text(0)
                     if action == refreshAction else '')
            # poll even non volatile parameter as requested explicitely
            self.client.eval(cmd, None)
コード例 #17
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, findResource('nicos_mlz/kws1/gui/sampleconf.ui'))
        self.sampleGroup.setEnabled(False)
        self.frame.setLayout(QVBoxLayout())

        menu = QMenu(self)
        menu.addAction(self.actionCopyAperture)
        menu.addAction(self.actionCopyDetOffset)
        menu.addAction(self.actionCopyThickness)
        menu.addAction(self.actionCopyTimeFactor)
        menu.addSeparator()
        menu.addAction(self.actionCopyAll)
        self.copyBtn.setMenu(menu)

        menu = QMenu(self)
        menu.addAction(self.actionEmpty)
        menu.addAction(self.actionGenerate)
        self.createBtn.setMenu(menu)

        self.configs = []
        self.dirty = False
        self.filename = None
        self.holder_info = options.get('holder_info', [])
        self.instrument = options.get('instrument', 'kws1')
コード例 #18
0
class LiveDataPanel(Panel):
    """Provides a generic "detector live view".

    For most instruments, a specific panel must be implemented that takes care
    of the individual live display needs.

    Options:

    * ``instrument`` -- the instrument name that is passed on to the livewidget
      module.
    * ``filetypes`` default[] - List of filename extensions whose content should
      be displayed.
    * ``detectors`` (default [] - list of detector devices whose data should be
      displayed.  If not set data from all configured detectors will be shown.
    * ``cachesize`` (default 20) - Number of entries in the live data cache.
      The live data cache allows to display of previous taken data.
    """

    panelName = 'Live data view'

    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/live.ui')

        self._allowed_tags = set()
        self._allowed_detectors = set()
        self._ignore_livedata = False  # ignore livedata, e.g. wrong detector
        self._last_idx = 0
        self._last_tag = None
        self._last_fnames = None
        self._last_format = None
        self._runtime = 0
        self._range_active = False
        self._cachesize = 20
        self._livewidgets = {}  # livewidgets for rois: roi_key -> widget
        self._fileopen_filter = None
        self.widget = None
        self.menu = None

        self.statusBar = QStatusBar(self, sizeGripEnabled=False)
        policy = self.statusBar.sizePolicy()
        policy.setVerticalPolicy(QSizePolicy.Fixed)
        self.statusBar.setSizePolicy(policy)
        self.statusBar.setSizeGripEnabled(False)
        self.layout().addWidget(self.statusBar)

        self.toolbar = QToolBar('Live data')
        self.toolbar.addAction(self.actionOpen)
        self.toolbar.addAction(self.actionPrint)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actionLogScale)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.actionKeepRatio)
        self.toolbar.addAction(self.actionUnzoom)
        self.toolbar.addAction(self.actionColormap)
        self.toolbar.addAction(self.actionMarkCenter)
        self.toolbar.addAction(self.actionROI)

        self._actions2D = [self.actionROI, self.actionColormap]
        self.setControlsEnabled(False)
        self.set2DControlsEnabled(False)

        # self.widget.setControls(Logscale | MinimumMaximum | BrightnessContrast |
        #                         Integrate | Histogram)

        self.liveitems = []
        self.setLiveItems(1)
        self._livechannel = 0

        self.splitter.setSizes([20, 80])
        self.splitter.restoreState(self.splitterstate)

        if hasattr(self.window(), 'closed'):
            self.window().closed.connect(self.on_closed)
        client.livedata.connect(self.on_client_livedata)
        client.liveparams.connect(self.on_client_liveparams)
        client.connected.connect(self.on_client_connected)
        client.cache.connect(self.on_cache)

        self.rois = {}
        self.detectorskey = None
        # configure instrument specific behavior
        self._instrument = options.get('instrument', '')
        # self.widget.setInstrumentOption(self._instrument)
        # if self._instrument == 'toftof':
        #     self.widget.setAxisLabels('time channels', 'detectors')
        # elif self._instrument == 'imaging':
        #     self.widget.setControls(ShowGrid | Logscale | Grayscale |
        #                             Normalize | Darkfield | Despeckle |
        #                             CreateProfile | Histogram | MinimumMaximum)
        #     self.widget.setStandardColorMap(True, False)
        # configure allowed file types
        supported_filetypes = ReaderRegistry.filetypes()
        opt_filetypes = set(options.get('filetypes', supported_filetypes))
        self._allowed_tags = opt_filetypes & set(supported_filetypes)

        # configure allowed detector device names
        detectors = options.get('detectors')
        if detectors:
            self._allowed_detectors = set(detectors)

        # configure caching
        self._cachesize = options.get('cachesize', self._cachesize)
        if self._cachesize < 1:
            self._cachesize = 1  # always cache the last live image
        self._datacache = BoundedOrderedDict(maxlen=self._cachesize)

    def setLiveItems(self, n):
        nitems = len(self.liveitems)
        if n < nitems:
            nfiles = self.fileList.count()
            for i in range(nitems - 1, n - 1, -1):
                self.liveitems.pop(i)
                self.fileList.takeItem(nfiles - nitems + i)
            if self._livechannel > n:
                self._livechannel = 0 if n > 0 else None
        else:
            for i in range(nitems, n):
                item = QListWidgetItem('<Live #%d>' % (i + 1))
                item.setData(FILENAME, i)
                item.setData(FILEFORMAT, '')
                item.setData(FILETAG, 'live')
                self.fileList.insertItem(self.fileList.count(), item)
                self.liveitems.append(item)
        if n == 1:
            self.liveitems[0].setText('<Live>')
        else:
            self.liveitems[0].setText('<Live #1>')

    def set2DControlsEnabled(self, flag):
        if flag != self.actionKeepRatio.isChecked():
            self.actionKeepRatio.trigger()
        for action in self._actions2D:
            action.setVisible(flag)

    def setControlsEnabled(self, flag):
        for action in self.toolbar.actions():
            action.setEnabled(flag)
        self.actionOpen.setEnabled(True)  # File Open action always available

    def initLiveWidget(self, widgetcls):
        if isinstance(self.widget, widgetcls):
            return

        if self.widget:
            self.widgetLayout.removeWidget(self.widget)
            self.widget.deleteLater()
        self.widget = widgetcls(self)
        # enable/disable controls and set defaults for new livewidget instances
        self.setControlsEnabled(True)
        if isinstance(self.widget, LiveWidget1D):
            self.set2DControlsEnabled(False)
        else:
            self.set2DControlsEnabled(True)
        # apply current settings
        self.widget.setCenterMark(self.actionMarkCenter.isChecked())
        self.widget.logscale(self.actionLogScale.isChecked())
        guiConn = GUIConnector(self.widget.gr)
        guiConn.connect(MouseEvent.MOUSE_MOVE, self.on_mousemove_gr)

        self.menuColormap = QMenu(self)
        self.actionsColormap = QActionGroup(self)
        activeMap = self.widget.getColormap()
        activeCaption = None
        for name, value in iteritems(COLORMAPS):
            caption = name.title()
            action = self.menuColormap.addAction(caption)
            action.setData(caption)
            action.setCheckable(True)
            if activeMap == value:
                action.setChecked(True)
                # update toolButton text later otherwise this may fail
                # depending on the setup and qt versions in use
                activeCaption = caption
            self.actionsColormap.addAction(action)
            action.triggered.connect(self.on_colormap_triggered)
        self.actionColormap.setMenu(self.menuColormap)
        self.widgetLayout.addWidget(self.widget)
        if activeCaption:
            self.toolbar.widgetForAction(
                self.actionColormap).setText(activeCaption)
        detectors = self.client.eval('session.experiment.detectors', [])
        self._register_rois(detectors)

    def loadSettings(self, settings):
        self.splitterstate = settings.value('splitter', '', QByteArray)

    def saveSettings(self, settings):
        settings.setValue('splitter', self.splitter.saveState())
        settings.setValue('geometry', self.saveGeometry())

    def getMenus(self):
        if not self.menu:
            menu = QMenu('&Live data', self)
            menu.addAction(self.actionOpen)
            menu.addAction(self.actionPrint)
            menu.addSeparator()
            menu.addAction(self.actionKeepRatio)
            menu.addAction(self.actionUnzoom)
            menu.addAction(self.actionLogScale)
            menu.addAction(self.actionColormap)
            menu.addAction(self.actionMarkCenter)
            menu.addAction(self.actionROI)
            self.menu = menu
        return [self.menu]

    def _get_all_widgets(self):
        yield self.widget
        for w in itervalues(self._livewidgets):
            yield w

    def getToolbars(self):
        return [self.toolbar]

    def on_mousemove_gr(self, event):
        xyz = None
        if event.getWindow():  # inside plot
            xyz = self.widget.getZValue(event)
        if xyz:
            fmt = '(%g, %g)'  # x, y data 1D integral plots
            if len(xyz) == 3:
                fmt += ': %g'  # x, y, z data for 2D image plot
            self.statusBar.showMessage(fmt % xyz)
        else:
            self.statusBar.clearMessage()

    def on_actionColormap_triggered(self):
        w = self.toolbar.widgetForAction(self.actionColormap)
        m = self.actionColormap.menu()
        if m:
            m.popup(w.mapToGlobal(QPoint(0, w.height())))

    def on_colormap_triggered(self):
        action = self.actionsColormap.checkedAction()
        name = action.data()
        for widget in self._get_all_widgets():
            widget.setColormap(COLORMAPS[name.upper()])
        self.toolbar.widgetForAction(self.actionColormap).setText(name.title())

    def _getLiveWidget(self, roi):
        return self._livewidgets.get(roi + '/roi', None)

    def showRoiWindow(self, roikey):
        key = roikey + '/roi'
        widget = self._getLiveWidget(roikey)
        region = self.widget._rois[key]
        if not widget:
            widget = LiveWidget(None)
            widget.setWindowTitle(roikey)
            widget.setColormap(self.widget.getColormap())
            widget.setCenterMark(self.actionMarkCenter.isChecked())
            widget.logscale(self.actionLogScale.isChecked())
            widget.gr.setAdjustSelection(False)  # don't use adjust on ROIs
            for name, roi in iteritems(self.rois):
                widget.setROI(name, roi)
            width = max(region.x) - min(region.x)
            height = max(region.y) - min(region.y)
            if width > height:
                dwidth = 500
                dheight = 500 * height // width
            else:
                dheight = 500
                dwidth = 500 * width // height
            widget.resize(dwidth, dheight)
            widget.closed.connect(self.on_roiWindowClosed)
        widget.setWindowForRoi(region)
        widget.update()
        widget.show()
        widget.activateWindow()
        self._livewidgets[key] = widget

    def closeRoiWindow(self, roi):
        widget = self._getLiveWidget(roi)
        if widget:
            widget.close()

    def on_closed(self):
        for w in self._livewidgets.values():
            w.close()

    def _register_rois(self, detectors):
        self.rois.clear()
        self.actionROI.setVisible(False)
        self.menuROI = QMenu(self)
        self.actionsROI = QActionGroup(self)
        self.actionsROI.setExclusive(False)
        for detname in detectors:
            self.log.debug('checking rois for detector \'%s\'', detname)
            for tup in self.client.eval(detname + '.postprocess', ''):
                roi = tup[0]
                cachekey = roi + '/roi'
                # check whether or not this is a roi (cachekey exists).
                keyval = self.client.getCacheKey(cachekey)
                if keyval:
                    self.on_roiChange(cachekey, keyval[1])
                    self.log.debug('register roi: %s', roi)
                    # create roi menu
                    action = self.menuROI.addAction(roi)
                    action.setData(roi)
                    action.setCheckable(True)
                    self.actionsROI.addAction(action)
                    action.triggered.connect(self.on_roi_triggered)
                    self.actionROI.setMenu(self.menuROI)
                    self.actionROI.setVisible(True)

    def on_actionROI_triggered(self):
        w = self.toolbar.widgetForAction(self.actionROI)
        self.actionROI.menu().popup(w.mapToGlobal(QPoint(0, w.height())))

    def on_roi_triggered(self):
        action = self.sender()
        roi = action.data()
        if action.isChecked():
            self.showRoiWindow(roi)
        else:
            self.closeRoiWindow(roi)

    def on_roiWindowClosed(self):
        widget = self.sender()
        if widget:
            key = None
            for key, w in iteritems(self._livewidgets):
                if w == widget:
                    self.log.debug('delete roi: %s', key)
                    del self._livewidgets[key]
                    break
            if key:
                roi = key.rsplit('/', 1)[0]
                for action in self.actionsROI.actions():
                    if action.data() == roi:
                        action.setChecked(False)
                        self.log.debug('uncheck roi: %s', roi)

    def on_roiChange(self, key, value):
        self.log.debug('on_roiChange: %s %s', key, (value, ))
        self.rois[key] = value
        for widget in self._get_all_widgets():
            widget.setROI(key, value)
        widget = self._livewidgets.get(key, None)
        if widget:
            widget.setWindowForRoi(self.widget._rois[key])

    def on_cache(self, data):
        _time, key, _op, svalue = data
        try:
            value = cache_load(svalue)
        except ValueError:
            value = None
        if key in self.rois:
            self.on_roiChange(key, value)
        elif key == self.detectorskey and self.widget:
            self._register_rois(value)

    def on_client_connected(self):
        self.client.tell('eventunmask', ['livedata', 'liveparams'])
        datapath = self.client.eval('session.experiment.datapath', '')
        if not datapath or not path.isdir(datapath):
            return
        if self._instrument == 'imaging':
            for fn in sorted(os.listdir(datapath)):
                if fn.endswith('.fits'):
                    self.add_to_flist(path.join(datapath, fn), '', 'fits',
                                      False)
        self.detectorskey = (self.client.eval('session.experiment.name') +
                             '/detlist').lower()

    def on_client_liveparams(self, params):
        tag, uid, det, fname, dtype, nx, ny, nz, runtime = params
        # TODO: remove compatibility code
        if isinstance(fname, string_types):
            fname, nx, ny, nz = [fname], [nx], [ny], [nz]

        if self._allowed_detectors and det not in self._allowed_detectors:
            self._ignore_livedata = True
            return
        self._ignore_livedata = False
        self._runtime = runtime
        self._last_uid = uid
        if dtype:
            self.setLiveItems(len(fname))
            self._last_fnames = None
            normalized_type = numpy.dtype(dtype).str
            if normalized_type not in DATATYPES:
                self._last_format = None
                self.log.warning('Unsupported live data format: %s',
                                 (params, ))
                return
            self._last_format = normalized_type
        elif fname:
            self._last_fnames = fname
            self._last_format = None
        self._last_tag = tag.lower()
        self._nx = nx
        self._ny = ny
        self._nz = nz
        self._last_idx = 0

    def _initLiveWidget(self, array):
        """Initialize livewidget based on array's shape"""
        if len(array.shape) == 1:
            widgetcls = LiveWidget1D
        else:
            widgetcls = IntegralLiveWidget
        self.initLiveWidget(widgetcls)

    def setData(self, array, uid=None, display=True):
        """Dispatch data array to corresponding live widgets.
        Cache array based on uid parameter. No caching if uid is ``None``.
        """
        if uid:
            if uid not in self._datacache:
                self.log.debug('add to cache: %s', uid)
            self._datacache[uid] = array
        if display:
            self._initLiveWidget(array)
            for widget in self._get_all_widgets():
                widget.setData(array)

    def setDataFromFile(self, filename, tag, uid=None, display=True):
        """Load data array from file and dispatch to live widgets using
        ``setData``. Do not use caching if uid is ``None``.
        """
        try:
            array = ReaderRegistry.getReaderCls(tag).fromfile(filename)
        except KeyError:
            raise NicosError('Unsupported fileformat %r' % tag)
        if array is not None:
            self.setData(array, uid, display=display)
        else:
            raise NicosError('Cannot read file %r' % filename)

    def on_client_livedata(self, data):
        if self._ignore_livedata:  # ignore all live events
            return

        idx = self._last_idx  # 0 <= array number < n
        self._last_idx += 1
        # check for allowed tags but always allow live data
        if self._last_tag in self._allowed_tags or self._last_tag == 'live':
            # pylint: disable=len-as-condition
            if len(data) and self._last_format:
                # we got live data with a specified format
                uid = str(self._last_uid) + '-' + str(idx)
                array = numpy.frombuffer(data, self._last_format)
                if self._nz[idx] > 1:
                    array = array.reshape(
                        (self._nz[idx], self._ny[idx], self._nx[idx]))
                elif self._ny[idx] > 1:
                    array = array.reshape((self._ny[idx], self._nx[idx]))
                # update display for selected live channel, just cache
                # otherwise
                self.setData(array, uid, display=(idx == self._livechannel))
                self.liveitems[idx].setData(FILEUID, uid)
            else:
                # we got no live data, but a filename with the data
                # filename corresponds to full qualififed path here
                for i, filename in enumerate(self._last_fnames):
                    uid = str(self._last_uid) + '-' + str(i)
                    self.add_to_flist(filename, self._last_format,
                                      self._last_tag, uid)
                    try:
                        # update display for selected live channel, just cache
                        # otherwise
                        self.setDataFromFile(filename,
                                             self._last_tag,
                                             uid,
                                             display=(i == self._livechannel))
                    except Exception as e:
                        if uid in self._datacache:
                            # image is already cached
                            # suppress error message for cached image
                            self.log.debug(e)
                        else:
                            # image is not cached and could not be loaded
                            self.log.exception(e)

    def remove_obsolete_cached_files(self):
        """Removes outdated cached files from the file list or set cached flag
        to False if the file is still available on the filesystem.
        """
        cached_item_rows = []
        for row in range(self.fileList.count()):
            item = self.fileList.item(row)
            if item.data(FILEUID):
                cached_item_rows.append(row)
        if len(cached_item_rows) > self._cachesize:
            for row in cached_item_rows[0:-self._cachesize]:
                item = self.fileList.item(row)
                self.log.debug('remove from cache %s %s', item.data(FILEUID),
                               item.data(FILENAME))
                if path.isfile(item.data(FILENAME)):
                    item.setData(FILEUID, None)
                else:
                    self.fileList.takeItem(row)

    def add_to_flist(self, filename, fformat, ftag, uid=None, scroll=True):
        shortname = path.basename(filename)
        item = QListWidgetItem(shortname)
        item.setData(FILENAME, filename)
        item.setData(FILEFORMAT, fformat)
        item.setData(FILETAG, ftag)
        item.setData(FILEUID, uid)
        self.fileList.insertItem(self.fileList.count() - len(self.liveitems),
                                 item)
        if uid:
            self.remove_obsolete_cached_files()
        if scroll:
            self.fileList.scrollToBottom()
        return item

    def on_fileList_itemClicked(self, item):
        if item is None:
            return

        fname = item.data(FILENAME)
        ftag = item.data(FILETAG)
        if item in self.liveitems and ftag == 'live':  # show live image
            self._livechannel = int(fname)
            fname = None
            self.log.debug("set livechannel: %d", self._livechannel)
        else:
            self._livechannel = None
            self.log.debug("no direct display")

        uid = item.data(FILEUID)
        if uid:  # show image from cache
            array = self._datacache.get(uid, None)
            if array is not None and array.size:
                self.setData(array)
                return
        if fname:
            # show image from file
            self.setDataFromFile(fname, ftag)

    def on_fileList_currentItemChanged(self, item, previous):
        self.on_fileList_itemClicked(item)

    @pyqtSlot()
    def on_actionOpen_triggered(self):
        """Open image file using registered reader classes."""
        ftypes = {
            ffilter: ftype
            for ftype, ffilter in ReaderRegistry.filefilters()
        }
        fdialog = FileFilterDialog(self, "Open data files", "",
                                   ";;".join(ftypes.keys()))
        if self._fileopen_filter:
            fdialog.selectNameFilter(self._fileopen_filter)
        if fdialog.exec_() == fdialog.Accepted:
            self._fileopen_filter = fdialog.selectedNameFilter()
            tag = ftypes[self._fileopen_filter]
            files = fdialog.selectedFiles()
            if files:

                def _cacheFile(fn, tag):
                    uid = uuid4()
                    # setDataFromFile may raise an `NicosException`, e.g.
                    # if the file cannot be opened.
                    self.setDataFromFile(fn, tag, uid, display=False)
                    return self.add_to_flist(fn, None, tag, uid)

                # load and display first item
                f = files.pop(0)
                self.fileList.setCurrentItem(_cacheFile(f, tag))
                cachesize = self._cachesize - 1
                # add first `cachesize` files to cache
                for _, f in enumerateWithProgress(files[:cachesize],
                                                  "Loading data files...",
                                                  parent=fdialog):
                    _cacheFile(f, tag)
                # add further files to file list (open on request/itemClicked)
                for f in files[cachesize:]:
                    self.add_to_flist(f, None, tag)

    @pyqtSlot()
    def on_actionUnzoom_triggered(self):
        self.widget.unzoom()

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        self.widget.printDialog()

    @pyqtSlot()
    def on_actionLogScale_triggered(self):
        for widget in self._get_all_widgets():
            widget.logscale(self.actionLogScale.isChecked())

    @pyqtSlot()
    def on_actionMarkCenter_triggered(self):
        flag = self.actionMarkCenter.isChecked()
        for widget in self._get_all_widgets():
            widget.setCenterMark(flag)

    @pyqtSlot()
    def on_actionKeepRatio_triggered(self):
        self.widget.gr.setAdjustSelection(self.actionKeepRatio.isChecked())
コード例 #19
0
    def __init__(self, log, gui_conf, viewonly=False, tunnel=''):
        DefaultMainWindow.__init__(self, log, gui_conf, viewonly, tunnel)
        self.add_logo()
        self.set_icons()
        self.style_file = gui_conf.stylefile

        # Cheeseburger menu
        dropdown = QMenu('')
        dropdown.addAction(self.actionConnect)
        dropdown.addAction(self.actionViewOnly)
        dropdown.addAction(self.actionPreferences)
        dropdown.addAction(self.actionExpert)
        dropdown.addSeparator()
        dropdown.addAction(self.actionExit)
        self.actionUser.setMenu(dropdown)
        self.actionUser.setIconVisibleInMenu(True)
        self.dropdown = dropdown
        self.actionExpert.setEnabled(self.client.isconnected)
        self.actionEmergencyStop.setEnabled(self.client.isconnected)
        self._init_instrument_name()
        self._init_experiment_name()
コード例 #20
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/scans.ui')
        ArbitraryFitter.arby_functions.update(options.get('fit_functions', {}))

        self.statusBar = QStatusBar(self, sizeGripEnabled=False)
        policy = self.statusBar.sizePolicy()
        policy.setVerticalPolicy(QSizePolicy.Fixed)
        self.statusBar.setSizePolicy(policy)
        self.layout().addWidget(self.statusBar)

        self.x_menu = QMenu(self)
        self.x_menu.aboutToShow.connect(self.on_x_menu_aboutToShow)
        self.actionXAxis.setMenu(self.x_menu)

        self.y_menu = QMenu(self)
        self.y_menu.aboutToShow.connect(self.on_y_menu_aboutToShow)
        self.actionYAxis.setMenu(self.y_menu)

        self.actionAutoDisplay.setChecked(True)

        self.norm_menu = QMenu(self)
        self.norm_menu.aboutToShow.connect(self.on_norm_menu_aboutToShow)
        self.actionNormalized.setMenu(self.norm_menu)

        quickfit = QShortcut(QKeySequence("G"), self)
        quickfit.activated.connect(self.on_quickfit)

        self.user_color = Qt.white
        self.user_font = QFont('Monospace')

        self.bulk_adding = False
        self.no_openset = False
        self.last_norm_selection = None
        self.fitclass = GaussFitter
        self.fitfuncmap = {}

        self.menus = None
        self.bars = None

        self.data = self.mainwindow.data

        # maps set uid -> plot
        self.setplots = {}
        # maps set uid -> list item
        self.setitems = {}
        # current plot object
        self.currentPlot = None
        # stack of set uids
        self.setUidStack = []
        # uids of automatically combined datasets -> uid of combined one
        self.contSetUids = {}

        self.splitter.setSizes([20, 80])
        self.splitter.restoreState(self.splitterstate)
        if self.tablecolwidth0 > 0:
            self.metaTable.setColumnWidth(0, self.tablecolwidth0)
            self.metaTable.setColumnWidth(1, self.tablecolwidth1)

        self.data.datasetAdded.connect(self.on_data_datasetAdded)
        self.data.pointsAdded.connect(self.on_data_pointsAdded)
        self.data.fitAdded.connect(self.on_data_fitAdded)
        client.experiment.connect(self.on_client_experiment)

        self.setCurrentDataset(None)
        self.updateList()
コード例 #21
0
class ScansPanel(Panel):
    """Provides a display for the scans of the current experiment.

    Options:

    * ``fit_functions`` (default {}) -- dictionary for special fitting
      functions. The name of the fit function is followed by a set of
      a list of fit parameters and the string containing the fit function
      code::

          fit_functions = {
              'Resonance': (['Vmax = 0.1', 'R = 0.6', 'f = ', 'L = ', 'C = '],
                            'Vmax / sqrt(R**2 + (f*L-1/(f*C))**2)'),
          }

      The function can use the all mathematical functions of the
      `numpy <http://www.numpy.org/>`_ package.

    """
    panelName = 'Scans'

    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/scans.ui')
        ArbitraryFitter.arby_functions.update(options.get('fit_functions', {}))

        self.statusBar = QStatusBar(self, sizeGripEnabled=False)
        policy = self.statusBar.sizePolicy()
        policy.setVerticalPolicy(QSizePolicy.Fixed)
        self.statusBar.setSizePolicy(policy)
        self.layout().addWidget(self.statusBar)

        self.x_menu = QMenu(self)
        self.x_menu.aboutToShow.connect(self.on_x_menu_aboutToShow)
        self.actionXAxis.setMenu(self.x_menu)

        self.y_menu = QMenu(self)
        self.y_menu.aboutToShow.connect(self.on_y_menu_aboutToShow)
        self.actionYAxis.setMenu(self.y_menu)

        self.actionAutoDisplay.setChecked(True)

        self.norm_menu = QMenu(self)
        self.norm_menu.aboutToShow.connect(self.on_norm_menu_aboutToShow)
        self.actionNormalized.setMenu(self.norm_menu)

        quickfit = QShortcut(QKeySequence("G"), self)
        quickfit.activated.connect(self.on_quickfit)

        self.user_color = Qt.white
        self.user_font = QFont('Monospace')

        self.bulk_adding = False
        self.no_openset = False
        self.last_norm_selection = None
        self.fitclass = GaussFitter
        self.fitfuncmap = {}

        self.menus = None
        self.bars = None

        self.data = self.mainwindow.data

        # maps set uid -> plot
        self.setplots = {}
        # maps set uid -> list item
        self.setitems = {}
        # current plot object
        self.currentPlot = None
        # stack of set uids
        self.setUidStack = []
        # uids of automatically combined datasets -> uid of combined one
        self.contSetUids = {}

        self.splitter.setSizes([20, 80])
        self.splitter.restoreState(self.splitterstate)
        if self.tablecolwidth0 > 0:
            self.metaTable.setColumnWidth(0, self.tablecolwidth0)
            self.metaTable.setColumnWidth(1, self.tablecolwidth1)

        self.data.datasetAdded.connect(self.on_data_datasetAdded)
        self.data.pointsAdded.connect(self.on_data_pointsAdded)
        self.data.fitAdded.connect(self.on_data_fitAdded)
        client.experiment.connect(self.on_client_experiment)

        self.setCurrentDataset(None)
        self.updateList()

    def loadSettings(self, settings):
        self.splitterstate = settings.value('splitter', '', QByteArray)
        self.tablecolwidth0 = settings.value('tablecolwidth0', 0, int)
        self.tablecolwidth1 = settings.value('tablecolwidth1', 0, int)

    def saveSettings(self, settings):
        settings.setValue('splitter', self.splitter.saveState())
        settings.setValue('tablecolwidth0', self.metaTable.columnWidth(0))
        settings.setValue('tablecolwidth1', self.metaTable.columnWidth(1))

    def setCustomStyle(self, font, back):
        self.user_font = font
        self.user_color = back

        for plot in self.setplots.values():
            plot.setBackgroundColor(back)
            plot.update()

        bold = QFont(font)
        bold.setBold(True)
        larger = scaledFont(font, 1.6)
        for plot in self.setplots.values():
            plot.setFonts(font, bold, larger)

    def requestClose(self):
        # Always succeeds, but break up circular references so that the panel
        # object can be deleted properly.
        self.currentPlot = None
        self.setplots.clear()
        return True

    def _autoscale(self, x=None, y=None):
        xflag = x if x is not None else self.actionScaleX.isChecked()
        yflag = y if y is not None else self.actionScaleY.isChecked()
        if self.currentPlot:
            self.currentPlot.setAutoScaleFlags(xflag, yflag)
            self.actionAutoScale.setChecked(xflag or yflag)
            self.actionScaleX.setChecked(xflag)
            self.actionScaleY.setChecked(yflag)
            self.currentPlot.update()

    def enablePlotActions(self, on):
        for action in [
            self.actionSavePlot, self.actionPrint, self.actionResetPlot,
            self.actionAttachElog, self.actionCombine, self.actionClosePlot,
            self.actionDeletePlot, self.actionLogXScale, self.actionLogScale,
            self.actionAutoScale, self.actionScaleX, self.actionScaleY,
            self.actionXAxis, self.actionYAxis, self.actionNormalized,
            self.actionUnzoom, self.actionLegend, self.actionModifyData,
            self.actionFitPeak, self.actionFitPeakPV, self.actionFitPeakPVII,
            self.actionFitTc, self.actionFitCosine, self.actionFitSigmoid,
            self.actionFitArby, self.actionErrors,
        ]:
            action.setEnabled(on)

    def enableAutoScaleActions(self, on):
        for action in [self.actionAutoScale, self.actionScaleX,
                       self.actionScaleY]:
            action.setEnabled(on)

    def getMenus(self):
        if not self.menus:
            menu1 = QMenu('&Data plot', self)
            menu1.addAction(self.actionSavePlot)
            menu1.addAction(self.actionPrint)
            menu1.addAction(self.actionAttachElog)
            menu1.addSeparator()
            menu1.addAction(self.actionResetPlot)
            menu1.addAction(self.actionAutoDisplay)
            menu1.addAction(self.actionCombine)
            menu1.addAction(self.actionClosePlot)
            menu1.addAction(self.actionDeletePlot)
            menu1.addSeparator()
            menu1.addAction(self.actionXAxis)
            menu1.addAction(self.actionYAxis)
            menu1.addAction(self.actionNormalized)
            menu1.addSeparator()
            menu1.addAction(self.actionUnzoom)
            menu1.addAction(self.actionLogXScale)
            menu1.addAction(self.actionLogScale)
            menu1.addAction(self.actionAutoScale)
            menu1.addAction(self.actionScaleX)
            menu1.addAction(self.actionScaleY)
            menu1.addAction(self.actionLegend)
            menu1.addAction(self.actionErrors)
            menu1.addSeparator()

            menu2 = QMenu('Data &manipulation', self)
            menu2.addAction(self.actionModifyData)
            menu2.addSeparator()
            ag = QActionGroup(menu2)
            ag.addAction(self.actionFitPeakGaussian)
            ag.addAction(self.actionFitPeakLorentzian)
            ag.addAction(self.actionFitPeakPV)
            ag.addAction(self.actionFitPeakPVII)
            ag.addAction(self.actionFitTc)
            ag.addAction(self.actionFitCosine)
            ag.addAction(self.actionFitSigmoid)
            ag.addAction(self.actionFitLinear)
            ag.addAction(self.actionFitExponential)
            menu2.addAction(self.actionFitPeak)
            menu2.addAction(self.actionPickInitial)
            menu2.addAction(self.actionFitPeakGaussian)
            menu2.addAction(self.actionFitPeakLorentzian)
            menu2.addAction(self.actionFitPeakPV)
            menu2.addAction(self.actionFitPeakPVII)
            menu2.addAction(self.actionFitTc)
            menu2.addAction(self.actionFitCosine)
            menu2.addAction(self.actionFitSigmoid)
            menu2.addAction(self.actionFitLinear)
            menu2.addAction(self.actionFitExponential)
            menu2.addSeparator()
            menu2.addAction(self.actionFitArby)

            self.menus = [menu1, menu2]

        return self.menus

    def getToolbars(self):
        if not self.bars:
            bar = QToolBar('Scans')
            bar.addAction(self.actionSavePlot)
            bar.addAction(self.actionPrint)
            bar.addSeparator()
            bar.addAction(self.actionXAxis)
            bar.addAction(self.actionYAxis)
            bar.addAction(self.actionNormalized)
            bar.addSeparator()
            bar.addAction(self.actionLogXScale)
            bar.addAction(self.actionLogScale)
            bar.addAction(self.actionUnzoom)
            bar.addSeparator()
            bar.addAction(self.actionAutoScale)
            bar.addAction(self.actionScaleX)
            bar.addAction(self.actionScaleY)
            bar.addAction(self.actionLegend)
            bar.addAction(self.actionErrors)
            bar.addAction(self.actionResetPlot)
            bar.addAction(self.actionDeletePlot)
            bar.addSeparator()
            bar.addAction(self.actionAutoDisplay)
            bar.addAction(self.actionCombine)

            fitbar = QToolBar('Scan fitting')
            fitbar.addAction(self.actionFitPeak)
            wa = QWidgetAction(fitbar)
            self.fitPickCheckbox = QCheckBox(fitbar)
            self.fitPickCheckbox.setText('Pick')
            self.fitPickCheckbox.setChecked(True)
            self.actionPickInitial.setChecked(True)
            self.fitPickCheckbox.toggled.connect(self.actionPickInitial.setChecked)
            self.actionPickInitial.toggled.connect(self.fitPickCheckbox.setChecked)
            layout = QHBoxLayout()
            layout.setContentsMargins(10, 0, 10, 0)
            layout.addWidget(self.fitPickCheckbox)
            frame = QFrame(fitbar)
            frame.setLayout(layout)
            wa.setDefaultWidget(frame)
            fitbar.addAction(wa)
            ag = QActionGroup(fitbar)
            ag.addAction(self.actionFitPeakGaussian)
            ag.addAction(self.actionFitPeakLorentzian)
            ag.addAction(self.actionFitPeakPV)
            ag.addAction(self.actionFitPeakPVII)
            ag.addAction(self.actionFitTc)
            ag.addAction(self.actionFitCosine)
            ag.addAction(self.actionFitSigmoid)
            ag.addAction(self.actionFitLinear)
            ag.addAction(self.actionFitExponential)
            wa = QWidgetAction(fitbar)
            self.fitComboBox = QComboBox(fitbar)
            for a in ag.actions():
                itemtext = a.text().replace('&', '')
                self.fitComboBox.addItem(itemtext)
                self.fitfuncmap[itemtext] = a
            self.fitComboBox.currentIndexChanged.connect(
                self.on_fitComboBox_currentIndexChanged)
            wa.setDefaultWidget(self.fitComboBox)
            fitbar.addAction(wa)
            fitbar.addSeparator()
            fitbar.addAction(self.actionFitArby)

            self.bars = [bar, fitbar]

        return self.bars

    def updateList(self):
        self.datasetList.clear()
        for dataset in self.data.sets:
            if dataset.invisible:
                continue
            shortname = '%s - %s' % (dataset.name, dataset.default_xname)
            item = QListWidgetItem(shortname, self.datasetList)
            item.setData(32, dataset.uid)
            self.setitems[dataset.uid] = item

    def on_logYinDomain(self, flag):
        if not flag:
            self.actionLogScale.setChecked(flag)

    def on_logXinDomain(self, flag):
        if not flag:
            self.actionLogXScale.setChecked(flag)

    def on_datasetList_currentItemChanged(self, item, previous):
        if self.no_openset or item is None:
            return
        self.openDataset(itemuid(item))

    def on_datasetList_itemClicked(self, item):
        # this handler is needed in addition to currentItemChanged
        # since one can't change the current item if it's the only one
        if self.no_openset or item is None:
            return
        self.openDataset(itemuid(item))

    def openDataset(self, uid):
        dataset = self.data.uid2set[uid]
        newplot = None
        if dataset.uid not in self.setplots:
            newplot = DataSetPlot(self.plotFrame, self, dataset)
            if self.currentPlot:
                newplot.enableCurvesFrom(self.currentPlot)
            self.setplots[dataset.uid] = newplot
        self.datasetList.setCurrentItem(self.setitems[uid])
        plot = self.setplots[dataset.uid]
        self.enableAutoScaleActions(plot.HAS_AUTOSCALE)
        if newplot and plot.HAS_AUTOSCALE:
            from gr.pygr import PlotAxes
            plot.plot.autoscale = PlotAxes.SCALE_X | PlotAxes.SCALE_Y
        self.setCurrentDataset(plot)

    def setCurrentDataset(self, plot):
        if self.currentPlot:
            self.plotLayout.removeWidget(self.currentPlot)
            self.currentPlot.hide()
            self.metaTable.clearContents()
        self.currentPlot = plot
        if plot is None:
            self.enablePlotActions(False)
        else:
            try:
                self.setUidStack.remove(plot.dataset.uid)
            except ValueError:
                pass
            self.setUidStack.append(plot.dataset.uid)

            num_items = 0
            for catname in INTERESTING_CATS:
                if catname in plot.dataset.headerinfo:
                    num_items += 2 + len(plot.dataset.headerinfo[catname])
            num_items -= 1  # remove last empty row
            self.metaTable.setRowCount(num_items)

            i = 0
            for catname in INTERESTING_CATS:
                if catname in plot.dataset.headerinfo:
                    values = plot.dataset.headerinfo[catname]
                    catdesc = catname
                    for name_desc in INFO_CATEGORIES:
                        if name_desc[0] == catname:
                            catdesc = name_desc[1]
                    catitem = QTableWidgetItem(catdesc)
                    font = catitem.font()
                    font.setBold(True)
                    catitem.setFont(font)
                    self.metaTable.setItem(i, 0, catitem)
                    self.metaTable.setSpan(i, 0, 1, 2)
                    i += 1
                    for dev, name, value in sorted(values):
                        key = '%s_%s' % (dev, name) if name != 'value' else dev
                        self.metaTable.setItem(i, 0, QTableWidgetItem(key))
                        self.metaTable.setItem(i, 1, QTableWidgetItem(value))
                        if self.metaTable.columnSpan(i, 0) == 2:
                            self.metaTable.setSpan(i, 0, 1, 1)
                        i += 1
                    i += 1
            self.metaTable.resizeRowsToContents()

            self.enablePlotActions(True)
            self.enableAutoScaleActions(self.currentPlot.HAS_AUTOSCALE)
            self.datasetList.setCurrentItem(self.setitems[plot.dataset.uid])

            self.actionXAxis.setText('X axis: %s' % plot.current_xname)
            self.actionNormalized.setChecked(bool(plot.normalized))

            self.actionLogScale.setChecked(plot.isLogScaling())
            self.actionLogXScale.setChecked(plot.isLogXScaling())
            self.actionLegend.setChecked(plot.isLegendEnabled())
            self.actionErrors.setChecked(plot.isErrorBarEnabled())
            if plot.HAS_AUTOSCALE:
                from gr.pygr import PlotAxes
                mask = plot.plot.autoscale
                self._autoscale(x=mask & PlotAxes.SCALE_X,
                                y=mask & PlotAxes.SCALE_Y)
                plot.logYinDomain.connect(self.on_logYinDomain)
                plot.logXinDomain.connect(self.on_logXinDomain)
            self.plotLayout.addWidget(plot)
            plot.show()

    def on_data_datasetAdded(self, dataset):
        shortname = '%s - %s' % (dataset.name, dataset.default_xname)
        if dataset.uid in self.setitems:
            self.setitems[dataset.uid].setText(shortname)
            if dataset.uid in self.setplots:
                self.setplots[dataset.uid].updateDisplay()
        else:
            self.no_openset = True
            item = QListWidgetItem(shortname, self.datasetList)
            item.setData(32, dataset.uid)
            self.setitems[dataset.uid] = item
            if self.actionAutoDisplay.isChecked() and not self.data.bulk_adding:
                self.openDataset(dataset.uid)
            self.no_openset = False
        # If the dataset is a continuation of another dataset, automatically
        # create a combined dataset.
        contuids = dataset.continuation
        if contuids:
            alluids = tuple(contuids.split(',')) + (dataset.uid,)
            # Did we already create this set?  Then don't create it again.
            if self.contSetUids.get(alluids) in self.setitems:
                return
            allsets = list(map(self.data.uid2set.get, alluids))
            newuid = self._combine(COMBINE, allsets)
            if newuid:
                self.contSetUids[alluids] = newuid

    def on_data_pointsAdded(self, dataset):
        if dataset.uid in self.setplots:
            self.setplots[dataset.uid].pointsAdded()

    def on_data_fitAdded(self, dataset, res):
        if dataset.uid in self.setplots:
            self.setplots[dataset.uid]._plotFit(res)

    def on_client_experiment(self, data):
        self.datasetList.clear()

        # hide plot
        self.setCurrentDataset(None)

        # back to the beginning
        self.setplots = {}
        self.setitems = {}
        self.currentPlot = None
        self.setUidStack = []

    @pyqtSlot()
    def on_actionClosePlot_triggered(self):
        current_set = self.setUidStack.pop()
        if self.setUidStack:
            self.setCurrentDataset(self.setplots[self.setUidStack[-1]])
        else:
            self.setCurrentDataset(None)
        del self.setplots[current_set]

    @pyqtSlot()
    def on_actionResetPlot_triggered(self):
        current_set = self.setUidStack.pop()
        del self.setplots[current_set]
        self.openDataset(current_set)

    @pyqtSlot()
    def on_actionDeletePlot_triggered(self):
        if self.currentPlot.dataset.scaninfo != 'combined set':
            if not self.askQuestion('This is not a combined set: still '
                                    'delete it from the list?'):
                return
        current_set = self.setUidStack.pop()
        self.data.uid2set[current_set].invisible = True
        if self.setUidStack:
            self.setCurrentDataset(self.setplots[self.setUidStack[-1]])
        else:
            self.setCurrentDataset(None)
        del self.setplots[current_set]
        for i in range(self.datasetList.count()):
            if itemuid(self.datasetList.item(i)) == current_set:
                self.datasetList.takeItem(i)
                break

    @pyqtSlot()
    def on_actionSavePlot_triggered(self):
        filename = self.currentPlot.savePlot()
        if filename:
            self.statusBar.showMessage('Plot successfully saved to %s.' %
                                       filename)

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        if self.currentPlot.printPlot():
            self.statusBar.showMessage('Plot successfully printed.')

    @pyqtSlot()
    def on_actionAttachElog_triggered(self):
        newdlg = dialogFromUi(self, 'panels/plot_attach.ui')
        suffix = self.currentPlot.SAVE_EXT
        newdlg.filename.setText(
            safeName('data_%s' % self.currentPlot.dataset.name + suffix))
        ret = newdlg.exec_()
        if ret != QDialog.Accepted:
            return
        descr = newdlg.description.text()
        fname = newdlg.filename.text()
        pathname = self.currentPlot.saveQuietly()
        with open(pathname, 'rb') as fp:
            remotefn = self.client.ask('transfer', fp.read())
        if remotefn is not None:
            self.client.eval('_LogAttach(%r, [%r], [%r])' %
                             (descr, remotefn, fname))
        os.unlink(pathname)

    @pyqtSlot()
    def on_actionUnzoom_triggered(self):
        self.currentPlot.unzoom()

    @pyqtSlot(bool)
    def on_actionLogScale_toggled(self, on):
        self.currentPlot.setLogScale(on)

    @pyqtSlot(bool)
    def on_actionLogXScale_toggled(self, on):
        self.currentPlot.setLogXScale(on)

    @pyqtSlot(bool)
    def on_actionAutoScale_toggled(self, on):
        self._autoscale(on, on)

    @pyqtSlot(bool)
    def on_actionScaleX_toggled(self, on):
        self._autoscale(x=on)

    @pyqtSlot(bool)
    def on_actionScaleY_toggled(self, on):
        self._autoscale(y=on)

    def on_x_menu_aboutToShow(self):
        self.x_menu.clear()
        if not self.currentPlot:
            return
        done = set()
        for name in self.currentPlot.dataset.xnameunits:
            if name in done:
                continue
            done.add(name)
            action = self.x_menu.addAction(name)
            action.setData(name)
            action.setCheckable(True)
            if name == self.currentPlot.current_xname:
                action.setChecked(True)
            action.triggered.connect(self.on_x_action_triggered)

    @pyqtSlot()
    def on_x_action_triggered(self, text=None):
        if text is None:
            text = self.sender().data()
        self.actionXAxis.setText('X axis: %s' % text)
        self.currentPlot.current_xname = text
        self.currentPlot.updateDisplay()
        self.on_actionUnzoom_triggered()

    @pyqtSlot()
    def on_actionXAxis_triggered(self):
        self.bars[0].widgetForAction(self.actionXAxis).showMenu()

    def on_y_menu_aboutToShow(self):
        self.y_menu.clear()
        if not self.currentPlot:
            return
        for curve in self.currentPlot.dataset.curves:
            action = self.y_menu.addAction(curve.full_description)
            action.setData(curve.full_description)
            action.setCheckable(True)
            if not curve.hidden:
                action.setChecked(True)
            action.triggered.connect(self.on_y_action_triggered)

    @pyqtSlot()
    def on_y_action_triggered(self, text=None):
        if text is None:
            text = self.sender().data()
        if not self.currentPlot:
            return
        for curve in self.currentPlot.dataset.curves:
            if curve.full_description == text:
                curve.hidden = not curve.hidden
        self.currentPlot.updateDisplay()
        self.on_actionUnzoom_triggered()

    @pyqtSlot()
    def on_actionYAxis_triggered(self):
        self.bars[0].widgetForAction(self.actionYAxis).showMenu()

    @pyqtSlot()
    def on_actionNormalized_triggered(self):
        if not self.currentPlot:
            return
        if self.currentPlot.normalized is not None:
            self.on_norm_action_triggered('None')
        else:
            all_normnames = [name for (_, name)
                             in self.currentPlot.dataset.normindices]
            if self.last_norm_selection and \
               self.last_norm_selection in all_normnames:
                use = self.last_norm_selection
            else:
                use = all_normnames[0] if all_normnames else 'None'
            self.on_norm_action_triggered(use)

    def on_norm_menu_aboutToShow(self):
        self.norm_menu.clear()
        if self.currentPlot:
            none_action = self.norm_menu.addAction('None')
            none_action.setData('None')
            none_action.setCheckable(True)
            none_action.setChecked(True)
            none_action.triggered.connect(self.on_norm_action_triggered)
            max_action = self.norm_menu.addAction('Maximum')
            max_action.setData('Maximum')
            max_action.setCheckable(True)
            if self.currentPlot.normalized == 'Maximum':
                max_action.setChecked(True)
                none_action.setChecked(False)
            max_action.triggered.connect(self.on_norm_action_triggered)
            for _, name in self.currentPlot.dataset.normindices:
                action = self.norm_menu.addAction(name)
                action.setData(name)
                action.setCheckable(True)
                if name == self.currentPlot.normalized:
                    action.setChecked(True)
                    none_action.setChecked(False)
                action.triggered.connect(self.on_norm_action_triggered)

    @pyqtSlot()
    def on_norm_action_triggered(self, text=None):
        if text is None:
            text = self.sender().data()
        if text == 'None':
            self.currentPlot.normalized = None
            self.actionNormalized.setChecked(False)
        else:
            self.last_norm_selection = text
            self.currentPlot.normalized = text
            self.actionNormalized.setChecked(True)
        self.currentPlot.updateDisplay()
        self.on_actionUnzoom_triggered()

    @pyqtSlot(bool)
    def on_actionLegend_toggled(self, on):
        self.currentPlot.setLegend(on)

    @pyqtSlot(bool)
    def on_actionErrors_toggled(self, on):
        self.currentPlot.setErrorBarEnabled(on)

    @pyqtSlot()
    def on_actionModifyData_triggered(self):
        self.currentPlot.modifyData()

    @pyqtSlot()
    def on_actionFitPeak_triggered(self):
        self.currentPlot.beginFit(self.fitclass, self.actionFitPeak,
                                  pickmode=self.fitPickCheckbox.isChecked())

    @pyqtSlot(int)
    def on_fitComboBox_currentIndexChanged(self, index):
        self.fitfuncmap[self.fitComboBox.currentText()].trigger()

    @pyqtSlot()
    def on_actionFitPeakGaussian_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitPeakGaussian.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = GaussFitter

    @pyqtSlot()
    def on_actionFitPeakLorentzian_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitPeakLorentzian.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = LorentzFitter

    @pyqtSlot()
    def on_actionFitPeakPV_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitPeakPV.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = PseudoVoigtFitter

    @pyqtSlot()
    def on_actionFitPeakPVII_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitPeakPVII.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = PearsonVIIFitter

    @pyqtSlot()
    def on_actionFitTc_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitTc.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = TcFitter

    @pyqtSlot()
    def on_actionFitCosine_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitCosine.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = CosineFitter

    @pyqtSlot()
    def on_actionFitSigmoid_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitSigmoid.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = SigmoidFitter

    @pyqtSlot()
    def on_actionFitLinear_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitLinear.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = LinearFitter

    @pyqtSlot()
    def on_actionFitExponential_triggered(self):
        cbi = self.fitComboBox.findText(self.actionFitExponential.text().replace('&', ''))
        self.fitComboBox.setCurrentIndex(cbi)
        self.fitclass = ExponentialFitter

    @pyqtSlot()
    def on_actionFitArby_triggered(self):
        # no second argument: the "arbitrary" action is not checkable
        self.currentPlot.beginFit(ArbitraryFitter, None,
                                  pickmode=self.fitPickCheckbox.isChecked())

    @pyqtSlot()
    def on_quickfit(self):
        if not self.currentPlot or not self.currentPlot.underMouse():
            return
        self.currentPlot.fitQuick()

    @pyqtSlot()
    def on_actionCombine_triggered(self):
        current = self.currentPlot.dataset.uid
        dlg = dialogFromUi(self, 'panels/dataops.ui')
        for i in range(self.datasetList.count()):
            item = self.datasetList.item(i)
            newitem = QListWidgetItem(item.text(), dlg.otherList)
            newitem.setData(32, item.data(32))
            if itemuid(item) == current:
                dlg.otherList.setCurrentItem(newitem)
                # paint the current set in grey to indicate it's not allowed
                # to be selected
                newitem.setBackground(self.palette().brush(QPalette.Mid))
                newitem.setFlags(Qt.NoItemFlags)
        if dlg.exec_() != QDialog.Accepted:
            return
        items = dlg.otherList.selectedItems()
        sets = [self.data.uid2set[current]]
        for item in items:
            if itemuid(item) == current:
                return self.showError('Cannot combine set with itself.')
            sets.append(self.data.uid2set[itemuid(item)])
        for rop, rb in [(TOGETHER, dlg.opTogether),
                        (COMBINE, dlg.opCombine),
                        (ADD, dlg.opAdd),
                        (SUBTRACT, dlg.opSubtract),
                        (DIVIDE, dlg.opDivide)]:
            if rb.isChecked():
                op = rop
                break
        self._combine(op, sets)

    def _combine(self, op, sets):
        if op == TOGETHER:
            newset = ScanData()
            newset.name = combineattr(sets, 'name', sep=', ')
            newset.invisible = False
            newset.curves = []
            newset.scaninfo = 'combined set'
            # combine xnameunits from those that are in all sets
            all_xnu = set(sets[0].xnameunits)
            for dset in sets[1:]:
                all_xnu &= set(dset.xnameunits)
            newset.xnameunits = ['Default'] + [xnu for xnu in sets[0].xnameunits
                                               if xnu in all_xnu]
            newset.default_xname = 'Default'
            newset.normindices = sets[0].normindices
            # for together only, the number of curves and their columns
            # are irrelevant, just put all together
            for dataset in sets:
                for curve in dataset.curves:
                    newcurve = curve.copy()
                    if not newcurve.source:
                        newcurve.source = dataset.name
                    newset.curves.append(newcurve)
            self.data.add_existing_dataset(newset, [dataset.uid for dataset in sets])
            return newset.uid
        # else, need same axes, and same number and types of curves

        firstset = sets[0]
        nameprops = [firstset.xnames, firstset.xunits]
        curveprops = [(curve.description, curve.yindex)
                      for curve in firstset.curves]
        for dataset in sets[1:]:
            if [dataset.xnames, dataset.xunits] != nameprops:
                self.showError('Sets have different axes.')
                return
            if [(curve.description, curve.yindex)
                    for curve in dataset.curves] != curveprops:
                self.showError('Sets have different curves.')
                return
        if op == COMBINE:
            newset = ScanData()
            newset.name = combineattr(sets, 'name', sep=', ')
            newset.invisible = False
            newset.curves = []
            newset.scaninfo = 'combined set'
            newset.xnameunits = firstset.xnameunits
            newset.default_xname = firstset.default_xname
            newset.normindices = firstset.normindices
            for curves in zip(*(dataset.curves for dataset in sets)):
                newcurve = curves[0].copy()
                newcurve.datay = DataProxy(c.datay for c in curves)
                newcurve.datady = DataProxy(c.datady for c in curves)
                newcurve.datax = {xnu: DataProxy(c.datax[xnu] for c in curves)
                                  for xnu in newset.xnameunits}
                newcurve.datanorm = {nn: DataProxy(c.datanorm[nn] for c in curves)
                                     for i, nn in newset.normindices}
                newset.curves.append(newcurve)
            self.data.add_existing_dataset(newset,
                                           [dataset.uid for dataset in sets])
            return newset.uid

        if op == ADD:
            sep = ' + '
        elif op == SUBTRACT:
            sep = ' - '
        elif op == DIVIDE:
            sep = ' / '

        newset = ScanData()
        newset.name = combineattr(sets, 'name', sep=sep)
        newset.invisible = False
        newset.scaninfo = 'combined set'
        newset.curves = []
        newset.xnameunits = firstset.xnameunits
        newset.default_xname = firstset.default_xname
        if op in (SUBTRACT, DIVIDE):
            # remove information about normalization -- doesn't make sense
            newset.normindices = []
        else:
            newset.normindices = firstset.normindices

        for curves in zip(*(dataset.curves for dataset in sets)):
            newcurve = curves[0].deepcopy()
            # CRUDE HACK: don't care about the x values, operate by index
            removepoints = set()
            for curve in curves[1:]:
                for i in range(len(newcurve.datay)):
                    y1, y2 = float(newcurve.datay[i]), float(curve.datay[i])
                    if newcurve.dyindex != -1:
                        dy1 = newcurve.datady[i]
                        dy2 = curve.datady[i]
                    else:
                        dy1 = dy2 = 1.
                    if op == ADD:
                        newcurve.datay[i] = y1 + y2
                        newcurve.datady[i] = sqrt(dy1**2 + dy2**2)
                        for name in newcurve.datanorm:
                            newcurve.datanorm[name][i] += curve.datanorm[name][i]
                    elif op == SUBTRACT:
                        newcurve.datay[i] = y1 - y2
                        newcurve.datady[i] = sqrt(dy1**2 + dy2**2)
                    elif op == DIVIDE:
                        if y2 == 0:
                            y2 = 1.  # generate a value for now
                            removepoints.add(i)
                        newcurve.datay[i] = y1 / y2
                        newcurve.datady[i] = sqrt((dy1/y2)**2 +
                                                  (dy2*y1 / y2**2)**2)
            # remove points where we would have divided by zero
            if removepoints:
                newcurve.datay = [v for (i, v) in enumerate(newcurve.datay)
                                  if i not in removepoints]
                newcurve.datady = [v for (i, v) in enumerate(newcurve.datady)
                                   if i not in removepoints]
                for name in newcurve.datax:
                    newcurve.datax[name] = \
                        [v for (i, v) in enumerate(newcurve.datax[name])
                         if i not in removepoints]
                for name in newcurve.datanorm:
                    newcurve.datanorm[name] = \
                        [v for (i, v) in enumerate(newcurve.datanorm[name])
                         if i not in removepoints]
            newset.curves.append(newcurve)
        self.data.add_existing_dataset(newset)
        return newset.uid
コード例 #22
0
    def _reinit(self):
        classes = self.devinfo.classes

        if sip.isdeleted(self.devitem):
            # The item we're controlling has been removed from the list (e.g.
            # due to client reconnect), get it again.
            self.devitem = self.device_panel._devitems.get(
                self.devname.lower())
            # No such device anymore...
            if self.devitem is None:
                self.close()
                return

        self.deviceName.setText('Device: %s' % self.devname)
        self.setWindowTitle('Control %s' % self.devname)

        self.settingsBtn = self.buttonBox.button(
            QDialogButtonBox.RestoreDefaults)
        self.settingsBtn.clicked.connect(self.on_settingsBtn_clicked)

        # trigger parameter poll
        self.client.eval('%s.pollParams()' % self.devname, None)

        # now get all cache keys pertaining to the device and set the
        # properties we want
        params = self.client.getDeviceParams(self.devname)
        self.paraminfo = self.client.getDeviceParamInfo(self.devname)
        self.paramvalues = dict(params)

        # put parameter values in the list widget
        self.paramItems.clear()
        self.paramList.clear()
        for key, value in sorted(iteritems(params)):
            if self.paraminfo.get(key):
                # normally, show only userparams, except in expert mode
                is_userparam = self.paraminfo[key]['userparam']
                if is_userparam or self.device_panel._show_lowlevel:
                    self.paramItems[key] = item = \
                        QTreeWidgetItem(self.paramList, [key, str(value)])
                    # display non-userparams in grey italics, like lowlevel
                    # devices in the device list
                    if not is_userparam:
                        item.setFont(0, lowlevelFont[True])
                        item.setForeground(0, lowlevelBrush[True])

        # set description label
        if params.get('description'):
            self.description.setText(params['description'])
        else:
            self.description.setVisible(False)

        # check how to refer to the device in commands: if it is lowlevel,
        # we need to use quotes
        self.devrepr = srepr(self.devname) if params.get('lowlevel', True) \
            else self.devname

        # show "Set alias" group box if it is an alias device
        if 'alias' in params:
            if params['alias']:
                self.deviceName.setText(self.deviceName.text() +
                                        ' (alias for %s)' % params['alias'])
            alias_config = self.client.eval('session.alias_config', {})
            self.aliasTarget = QComboBox(self)
            self.aliasTarget.setEditable(True)
            if self.devname in alias_config:
                items = [t[0] for t in alias_config[self.devname]]
                self.aliasTarget.addItems(items)
                if params['alias'] in items:
                    self.aliasTarget.setCurrentIndex(
                        items.index(params['alias']))
            self.targetLayoutAlias.takeAt(1).widget().deleteLater()
            self.targetLayoutAlias.insertWidget(1, self.aliasTarget)
            if self.client.viewonly:
                self.setAliasBtn.setEnabled(False)
        else:
            self.aliasGroup.setVisible(False)

        historyBtn = self.buttonBox.button(QDialogButtonBox.Reset)
        # show current value/status if it is readable
        if 'nicos.core.device.Readable' not in classes:
            self.valueFrame.setVisible(False)
            self.buttonBox.removeButton(historyBtn)
        else:
            self.valuelabel.setText(self.devitem.text(1))
            self.statuslabel.setText(self.devitem.text(2))
            self.statusimage.setPixmap(self.devitem.icon(0).pixmap(16, 16))
            setForegroundBrush(self.statuslabel, self.devitem.foreground(2))
            setBackgroundBrush(self.statuslabel, self.devitem.background(2))

            # modify history button: add icon and set text
            historyBtn.setIcon(QIcon(':/find'))
            historyBtn.setText('Plot history...')
            historyBtn.clicked.connect(self.on_historyBtn_clicked)

        if self.client.viewonly:
            self.limitFrame.setVisible(False)
            self.targetFrame.setVisible(False)
            return

        # add a menu for the "More" button
        self.moveBtns.clear()
        menu = QMenu(self)
        if 'nicos.core.mixins.HasLimits' in classes:
            menu.addAction(self.actionSetLimits)
        if 'nicos.core.mixins.HasOffset' in classes:
            menu.addAction(self.actionAdjustOffset)
        if 'nicos.devices.abstract.CanReference' in classes:
            menu.addAction(self.actionReference)
        if 'nicos.devices.abstract.Coder' in classes:
            menu.addAction(self.actionSetPosition)
        if 'nicos.core.device.Moveable' in classes:
            if not menu.isEmpty():
                menu.addSeparator()
            menu.addAction(self.actionFix)
            menu.addAction(self.actionRelease)
        if 'nicos.core.mixins.CanDisable' in classes:
            if not menu.isEmpty():
                menu.addSeparator()
            menu.addAction(self.actionEnable)
            menu.addAction(self.actionDisable)
        if not menu.isEmpty():
            menuBtn = QPushButton('More', self)
            menuBtn.setMenu(menu)
            self.moveBtns.addButton(menuBtn, QDialogButtonBox.ResetRole)

        def reset(checked):
            self.device_panel.exec_command('reset(%s)' % self.devrepr)

        def stop(checked):
            self.device_panel.exec_command('stop(%s)' % self.devrepr,
                                           immediate=True)

        self.moveBtns.addButton('Reset', QDialogButtonBox.ResetRole)\
                     .clicked.connect(reset)

        if 'nicos.core.device.Moveable' in classes or \
           'nicos.core.device.Measurable' in classes:
            self.moveBtns.addButton('Stop', QDialogButtonBox.ResetRole)\
                         .clicked.connect(stop)

        # show target and limits if the device is Moveable
        if 'nicos.core.device.Moveable' not in classes:
            self.limitFrame.setVisible(False)
            self.targetFrame.setVisible(False)
        else:
            if 'nicos.core.mixins.HasLimits' not in classes:
                self.limitFrame.setVisible(False)
            else:
                self.limitMin.setText(str(params['userlimits'][0]))
                self.limitMax.setText(str(params['userlimits'][1]))

            # insert a widget to enter a new device value
            # allowEnter=False because we catch pressing Enter ourselves
            self.target = DeviceValueEdit(self,
                                          dev=self.devname,
                                          useButtons=True,
                                          allowEnter=False)
            self.target.setClient(self.client)

            def btn_callback(target):
                self.device_panel.exec_command('move(%s, %s)' %
                                               (self.devrepr, srepr(target)))

            self.target.valueChosen.connect(btn_callback)
            self.targetFrame.layout().takeAt(1).widget().deleteLater()
            self.targetFrame.layout().insertWidget(1, self.target)

            def move(checked):
                try:
                    target = self.target.getValue()
                except ValueError:
                    return
                self.device_panel.exec_command('move(%s, %s)' %
                                               (self.devrepr, srepr(target)))

            if self.target.getValue() is not Ellipsis:  # (button widget)
                self.moveBtn = self.moveBtns.addButton(
                    'Move', QDialogButtonBox.AcceptRole)
                self.moveBtn.clicked.connect(move)
            else:
                self.moveBtn = None

            if params.get('fixed') and self.moveBtn:
                self.moveBtn.setEnabled(False)
                self.moveBtn.setText('(fixed)')
コード例 #23
0
ファイル: mainwindow.py プロジェクト: umithardal/nicos
    def __init__(self, log, gui_conf, viewonly=False, tunnel=''):
        DefaultMainWindow.__init__(self, log, gui_conf, viewonly, tunnel)
        self.addLogo()
        self.addInstrument()
        self.addExperiment()
        self.set_icons()
        self.stylefile = gui_conf.stylefile

        # Cheesburger menu
        dropdown = QMenu('')
        dropdown.addAction(self.actionConnect)
        dropdown.addAction(self.actionViewOnly)
        dropdown.addAction(self.actionPreferences)
        dropdown.addAction(self.actionExpert)
        dropdown.addSeparator()
        dropdown.addAction(self.actionExit)
        self.actionUser.setMenu(dropdown)
        self.actionUser.setIconVisibleInMenu(True)
        self.dropdown = dropdown
コード例 #24
0
 def getMenus(self):
     menu = QMenu('&History viewer', self)
     menu.addAction(self.actionNew)
     menu.addSeparator()
     menu.addAction(self.actionSavePlot)
     menu.addAction(self.actionPrint)
     menu.addAction(self.actionAttachElog)
     menu.addAction(self.actionSaveData)
     menu.addSeparator()
     menu.addAction(self.actionEditView)
     menu.addAction(self.actionCloseView)
     menu.addAction(self.actionDeleteView)
     menu.addAction(self.actionResetView)
     menu.addSeparator()
     menu.addAction(self.actionLogScale)
     menu.addAction(self.actionAutoScale)
     menu.addAction(self.actionScaleX)
     menu.addAction(self.actionScaleY)
     menu.addAction(self.actionUnzoom)
     menu.addAction(self.actionLegend)
     menu.addAction(self.actionSymbols)
     menu.addAction(self.actionLines)
     ag = QActionGroup(menu)
     ag.addAction(self.actionFitPeakGaussian)
     ag.addAction(self.actionFitPeakLorentzian)
     ag.addAction(self.actionFitPeakPV)
     ag.addAction(self.actionFitPeakPVII)
     ag.addAction(self.actionFitTc)
     ag.addAction(self.actionFitCosine)
     ag.addAction(self.actionFitSigmoid)
     ag.addAction(self.actionFitLinear)
     ag.addAction(self.actionFitExponential)
     menu.addAction(self.actionFitPeak)
     menu.addAction(self.actionPickInitial)
     menu.addAction(self.actionFitPeakGaussian)
     menu.addAction(self.actionFitPeakLorentzian)
     menu.addAction(self.actionFitPeakPV)
     menu.addAction(self.actionFitPeakPVII)
     menu.addAction(self.actionFitTc)
     menu.addAction(self.actionFitCosine)
     menu.addAction(self.actionFitSigmoid)
     menu.addAction(self.actionFitLinear)
     menu.addAction(self.actionFitExponential)
     menu.addSeparator()
     menu.addAction(self.actionFitArby)
     menu.addSeparator()
     menu.addAction(self.actionClose)
     self._refresh_presets()
     return [menu, self.presetmenu]
コード例 #25
0
    def __init__(self):
        loadUi(self, 'panels/history.ui')

        self.user_color = Qt.white
        self.user_font = QFont('Monospace')

        self.views = []
        # stack of views to display
        self.viewStack = []
        # maps watched keys to their views
        self.keyviews = {}
        # current plot object
        self.currentPlot = None
        self.fitclass = LinearFitter
        self.fitfuncmap = {}

        self.enablePlotActions(False)

        self.presetmenu = QMenu('&Presets', self)

        for (name, view) in self.last_views:
            item = QListWidgetItem(name, self.viewList)
            item.setForeground(QBrush(QColor('#aaaaaa')))
            item.setData(Qt.UserRole, view)

        self.menus = None
        self.bar = None

        # NOTE: for this class, automatic connections don't work on PyQt4 >=
        # 4.12 since this class is not derived from QObject. But on older PyQt4
        # and PyQt5, they do work, so we change use the usual naming scheme
        # slightly to avoid double connections.
        self.viewList.currentItemChanged.connect(
            self.on__viewList_currentItemChanged)
        self.viewList.itemClicked.connect(self.on__viewList_itemClicked)
        self.viewList.itemDoubleClicked.connect(
            self.on__viewList_itemDoubleClicked)
        self.actionNew.triggered.connect(self.on__actionNew_triggered)
        self.actionEditView.triggered.connect(
            self.on__actionEditView_triggered)
        self.actionCloseView.triggered.connect(
            self.on__actionCloseView_triggered)
        self.actionResetView.triggered.connect(
            self.on__actionResetView_triggered)
        self.actionDeleteView.triggered.connect(
            self.on__actionDeleteView_triggered)
        self.actionSavePlot.triggered.connect(
            self.on__actionSavePlot_triggered)
        self.actionPrint.triggered.connect(self.on__actionPrint_triggered)
        self.actionUnzoom.triggered.connect(self.on__actionUnzoom_triggered)
        self.actionLogScale.toggled.connect(self.on__actionLogScale_toggled)
        self.actionAutoScale.toggled.connect(self.on__actionAutoScale_toggled)
        self.actionScaleX.toggled.connect(self.on__actionScaleX_toggled)
        self.actionScaleY.toggled.connect(self.on__actionScaleY_toggled)
        self.actionLegend.toggled.connect(self.on__actionLegend_toggled)
        self.actionSymbols.toggled.connect(self.on__actionSymbols_toggled)
        self.actionLines.toggled.connect(self.on__actionLines_toggled)
        self.actionSaveData.triggered.connect(
            self.on__actionSaveData_triggered)
        self.actionFitPeak.triggered.connect(self.on__actionFitPeak_triggered)
        self.actionFitArby.triggered.connect(self.on__actionFitArby_triggered)
        self.actionFitPeakGaussian.triggered.connect(
            self.on__actionFitPeakGaussian_triggered)
        self.actionFitPeakLorentzian.triggered.connect(
            self.on__actionFitPeakLorentzian_triggered)
        self.actionFitPeakPV.triggered.connect(
            self.on__actionFitPeakPV_triggered)
        self.actionFitPeakPVII.triggered.connect(
            self.on__actionFitPeakPVII_triggered)
        self.actionFitTc.triggered.connect(self.on__actionFitTc_triggered)
        self.actionFitCosine.triggered.connect(
            self.on__actionFitCosine_triggered)
        self.actionFitSigmoid.triggered.connect(
            self.on__actionFitSigmoid_triggered)
        self.actionFitLinear.triggered.connect(
            self.on__actionFitLinear_triggered)
        self.actionFitExponential.triggered.connect(
            self.on__actionFitExponential_triggered)
コード例 #26
0
    def on_tree_customContextMenuRequested(self, point):

        item = self.tree.itemAt(point)
        if item is None:
            return
        if item.type() == DEVICE_TYPE:
            self._menu_dev = item.text(0)
            ldevname = self._menu_dev.lower()
            if charmpowersupply in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                i = 0
                for menuItem in params['transitions']:
                    self.cps_actions.append(QAction(menuItem))
                    self.cpsmenu.addAction(self.cps_actions[i])
                    self.cps_actions[i].triggered.connect(
                        partial(self.on_actionApply_triggered, i))
                    i = i + 1
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

            if roimanager in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                self.cps_actions.append(QAction('Edit...'))
                self.cpsmenu.addAction(self.cps_actions[0])
                self.cps_actions[0].triggered.connect(
                    partial(self.on_roimanagerEdit, ldevname))
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

            if compareimage in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                self.cps_actions.append(QAction('Show &Compare...'))
                self.cpsmenu.addAction(self.cps_actions[0])
                self.cps_actions[0].triggered.connect(
                    partial(self.on_images_compare, ldevname))
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

            if playlistmanager in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                self.cps_actions.append(QAction('Edit...'))
                self.cpsmenu.addAction(self.cps_actions[0])
                self.cps_actions[0].triggered.connect(
                    partial(self.on_playlist_edit, ldevname))
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

        return super().on_tree_customContextMenuRequested(point)
コード例 #27
0
class DevicesPanel1(DevicesPanel):
    def on_tree_customContextMenuRequested(self, point):

        item = self.tree.itemAt(point)
        if item is None:
            return
        if item.type() == DEVICE_TYPE:
            self._menu_dev = item.text(0)
            ldevname = self._menu_dev.lower()
            if charmpowersupply in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                i = 0
                for menuItem in params['transitions']:
                    self.cps_actions.append(QAction(menuItem))
                    self.cpsmenu.addAction(self.cps_actions[i])
                    self.cps_actions[i].triggered.connect(
                        partial(self.on_actionApply_triggered, i))
                    i = i + 1
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

            if roimanager in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                self.cps_actions.append(QAction('Edit...'))
                self.cpsmenu.addAction(self.cps_actions[0])
                self.cps_actions[0].triggered.connect(
                    partial(self.on_roimanagerEdit, ldevname))
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

            if compareimage in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                self.cps_actions.append(QAction('Show &Compare...'))
                self.cpsmenu.addAction(self.cps_actions[0])
                self.cps_actions[0].triggered.connect(
                    partial(self.on_images_compare, ldevname))
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

            if playlistmanager in self._devinfo[ldevname].classes:
                params = self.client.getDeviceParams(ldevname)
                self.cpsmenu = QMenu()
                self.cps_actions = []
                self.cps_actions.append(QAction('Edit...'))
                self.cpsmenu.addAction(self.cps_actions[0])
                self.cps_actions[0].triggered.connect(
                    partial(self.on_playlist_edit, ldevname))
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionMove)
                self.cpsmenu.addAction(self.actionReset)
                self.cpsmenu.addSeparator()
                if self.mainwindow.history_wintype is not None:
                    self.cpsmenu.addAction(self.actionPlotHistory)
                self.cpsmenu.addSeparator()
                self.cpsmenu.addAction(self.actionShutDown)
                self.cpsmenu.addAction(self.actionHelp)
                self.cpsmenu.popup(self.tree.viewport().mapToGlobal(point))
                return

        return super().on_tree_customContextMenuRequested(point)

    @pyqtSlot()
    def on_playlist_edit(self, ldevname):
        if not playlisteditor.win:
            playlisteditor.win = playlisteditor.Window(self.client, ldevname)
            playlisteditor.win.show()

    @pyqtSlot()
    def on_images_compare(self, ldevname):
        if not imagecompare.win:
            imagecompare.win = imagecompare.Window(self.client, ldevname)
            imagecompare.win.show()

    @pyqtSlot()
    def on_roimanagerEdit(self, ldevname):
        if not roieditor.win:
            roieditor.win = roieditor.Window(self.client, ldevname)
            roieditor.win.show()

    @pyqtSlot()
    def on_actionApply_triggered(self, index):
        if self._menu_dev:
            self.client.eval(self._menu_dev + '.apply(' + str(index) + ')')

    def on_client_cache(self, data):
        rv = super().on_client_cache(data)

        # here we truncate the long status message for charmpowersupply devices
        (time, key, op, value) = data
        if '/' not in key:
            return
        ldevname, subkey = key.rsplit('/', 1)
        if ldevname not in self._devinfo:
            return

        devitem = self._devitems[ldevname]
        devinfo = self._devinfo[ldevname]

        if charmpowersupply in self._devinfo[ldevname].classes:
            if subkey == 'status':
                t = devitem.text(2)
                i = t.rfind('[')
                if i >= 0:
                    t = t[:i - 1]
                devitem.setText(2, str(t))
            if ldevname in self._control_dialogs:
                dlg = self._control_dialogs[ldevname]
                ct = dlg.statuslabel.text()
                i = ct.rfind('[')
                if i >= 0:
                    ct = ct[:i - 1]
                dlg.statuslabel.setText(ct)

        return rv
コード例 #28
0
class DevicesPanel(Panel):
    """Provides a graphical list of NICOS devices and their current values.

    The user can operate basic device functions (move, stop, reset) by
    selecting an item from the list, which opens a control dialog.

    Options:

    * ``useicons`` (default True) -- if set to False, the list widget does not
      display status icons for the devices.

    * ``param_display`` (default {}) -- a dictionary containing the device name
      as key and a parameter name or a list of the parameter names which should
      be displayed in the device tree as subitems of the device item, for
      example::

         param_display = {
             'tas': 'scanmode',
             'Exp': ['lastpoint', 'lastscan']
         }

    * ``filters`` (default []) -- a list of tuples containing the name of the
      filter and the regular expression to filter out the devices.
      example::

          filters = [
              ('All', ''),
              ('Default', 'T|UBahn'),
              ('Foo', 'bar$'),
          ]

    """

    panelName = 'Devices'
    ui = 'panels/devices.ui'

    @classmethod
    def _createIcons(cls):
        # hack to make non-Qt usage as in checksetups work
        if not hasattr(cls, 'statusIcon'):
            cls.statusIcon = {
                OK: QIcon(':/leds/status_green'),
                WARN: QIcon(':/leds/status_warn'),
                BUSY: QIcon(':/leds/status_yellow'),
                NOTREACHED: QIcon(':/leds/status_red'),
                DISABLED: QIcon(':/leds/status_white'),
                ERROR: QIcon(':/leds/status_red'),
                UNKNOWN: QIcon(':/leds/status_unknown'),
            }

    @property
    def groupIcon(self):
        return QIcon(':/setup')

    def __init__(self, parent, client, options):
        DevicesPanel._createIcons()
        Panel.__init__(self, parent, client, options)
        loadUi(self, self.ui)
        self.useicons = bool(options.get('icons', True))
        self.param_display = {}
        param_display = options.get('param_display', {})
        for (key, value) in param_display.items():
            value = [value] if isinstance(value, string_types) else list(value)
            self.param_display[key.lower()] = value

        self.tree.header().restoreState(self._headerstate)
        self.clear()

        self.devmenu = QMenu(self)
        self.devmenu.addAction(self.actionMove)
        self.devmenu.addAction(self.actionStop)
        self.devmenu.addAction(self.actionReset)
        self.devmenu.addSeparator()
        self.devmenu.addAction(self.actionFix)
        self.devmenu.addAction(self.actionRelease)
        self.devmenu.addSeparator()
        if self.mainwindow.history_wintype is not None:
            self.devmenu.addAction(self.actionPlotHistory)
            self.devmenu.addSeparator()
        self.devmenu.addAction(self.actionShutDown)
        self.devmenu.addAction(self.actionHelp)

        self.devmenu_ro = QMenu(self)
        self.devmenu_ro.addAction(self.actionMove)
        self.devmenu_ro.addAction(self.actionReset)
        self.devmenu_ro.addSeparator()
        if self.mainwindow.history_wintype is not None:
            self.devmenu_ro.addAction(self.actionPlotHistory)
            self.devmenu_ro.addSeparator()
        self.devmenu_ro.addAction(self.actionShutDown)
        self.devmenu_ro.addAction(self.actionHelp)

        self._menu_dev = None  # device for which context menu is shown
        self._dev2setup = {}
        self._setupinfo = {}

        self._control_dialogs = {}
        self._show_lowlevel = self.mainwindow.expertmode

        # daemon request ID of last command executed from this panel
        # (used to display messages from this command)
        self._current_status = 'idle'
        self._exec_reqid = None
        self._error_window = None

        client.connected.connect(self.on_client_connected)
        client.disconnected.connect(self.on_client_disconnected)
        client.cache.connect(self.on_client_cache)
        client.device.connect(self.on_client_device)
        client.setup.connect(self.on_client_setup)
        client.message.connect(self.on_client_message)

        self.filters = options.get('filters', [])
        self.filter.addItem('')
        for text, rx in self.filters:
            self.filter.addItem('Filter: %s' % text, rx)
        self.filter.lineEdit().setPlaceholderText('Enter search expression')

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

    def saveSettings(self, settings):
        settings.setValue('headers', self.tree.header().saveState())

    def loadSettings(self, settings):
        self._headerstate = settings.value('headers', '', QByteArray)

    def _update_view(self):
        with self.sgroup as settings:
            for i in range(self.tree.topLevelItemCount()):
                v = settings.value(
                    '%s/expanded' % self.tree.topLevelItem(i).text(0), True,
                    bool)
                self.tree.topLevelItem(i).setExpanded(v)

    def _store_view(self):
        with self.sgroup as settings:
            for i in range(self.tree.topLevelItemCount()):
                settings.setValue(
                    '%s/expanded' % self.tree.topLevelItem(i).text(0),
                    self.tree.topLevelItem(i).isExpanded())

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

    def setExpertMode(self, expert):
        self._show_lowlevel = expert
        self.on_client_connected()

    def clear(self):
        if self.tree:
            self._store_view()
        self._catitems = {}
        # map lowercased devname -> tree widget item
        self._devitems = {}
        self._devparamitems = {}
        # map lowercased devname -> DevInfo instance
        self._devinfo = {}
        self.tree.clear()

    def on_client_connected(self):
        self.clear()

        state = self.client.ask('getstatus')
        if not state:
            return
        devlist = state['devices']
        self._read_setup_info(state['setups'])

        for devname in devlist:
            self._create_device_item(devname)

        # close all control dialogs for now nonexisting devices
        for ldevname in list(self._control_dialogs):
            if ldevname not in self._devitems:
                self._control_dialogs[ldevname].close()

        # add all toplevel items to the tree, sorted
        for cat in self._catitems:
            self.tree.addTopLevelItem(self._catitems[cat])
            self._catitems[cat].setExpanded(True)
        for devitem in itervalues(self._devitems):
            devitem.setExpanded(True)
        self.tree.sortItems(0, Qt.AscendingOrder)
        self._update_view()

    def on_client_disconnected(self):
        self.clear()

    def on_client_message(self, message):
        # show warnings and errors emitted by the current command in a window
        if message[5] != self._exec_reqid or message[2] < WARNING:
            return
        msg = '%s: %s' % (message[0], message[3].strip())
        if self._error_window is None:

            def reset_errorwindow():
                self._error_window = None

            self._error_window = ErrorDialog(self)
            self._error_window.accepted.connect(reset_errorwindow)
            self._error_window.addMessage(msg)
            self._error_window.show()
        else:
            self._error_window.addMessage(msg)
            self._error_window.activateWindow()

    def _read_setup_info(self, setuplists=None):
        if setuplists is None:
            allstatus = self.client.ask('getstatus')
            if allstatus is None:
                return
            setuplists = allstatus['setups']
        loaded_setups = set(setuplists[0])
        self._dev2setup = {}
        self._setupinfo = self.client.eval('session.getSetupInfo()', {})
        if self._setupinfo is None:
            self.log.warning('session.getSetupInfo() returned None instead '
                             'of {}')
            return
        for setupname, info in iteritems(self._setupinfo):
            if info is None:
                continue
            if setupname not in loaded_setups:
                continue
            for devname in info['devices']:
                self._dev2setup[devname] = setupname

    def _create_device_item(self, devname, add_cat=False):
        ldevname = devname.lower()
        # get all cache keys pertaining to the device
        params = self.client.getDeviceParams(devname)
        if not params:
            return
        lowlevel_device = params.get('lowlevel') or False
        if lowlevel_device and not self._show_lowlevel:
            return
        if 'nicos.core.data.sink.DataSink' in params.get('classes', []) and \
           not self._show_lowlevel:
            return

        # remove still-existing previous item for the same device name
        if ldevname in self._devitems:
            self.on_client_device(('destroy', [devname]))

        cat = self._dev2setup.get(devname)
        if cat is None:  # device is not in any setup? reread setup info
            self._read_setup_info()
            cat = self._dev2setup.get(devname)
            if cat is None:  # still not there -> give up
                return

        if cat not in self._catitems:
            display_order = self._setupinfo[cat].get('display_order', 50)
            representative = self._setupinfo[cat].get('extended', {}).get(
                'representative', '').lower()
            catitem = SetupTreeWidgetItem(cat, display_order, representative)
            catitem.setToolTip(0, self._setupinfo[cat].get('description', ''))
            f = catitem.font(0)
            f.setBold(True)
            catitem.setFont(0, f)
            catitem.setIcon(0, self.groupIcon)
            self._catitems[cat] = catitem
            if add_cat:
                self.tree.addTopLevelItem(catitem)
                catitem.setExpanded(True)
        else:
            catitem = self._catitems[cat]

        # create a tree node for the device
        devitem = QTreeWidgetItem(catitem, [devname, '', ''], DEVICE_TYPE)

        devitem.setForeground(0, lowlevelBrush[lowlevel_device])
        devitem.setFont(0, lowlevelFont[lowlevel_device])

        if self.useicons:
            devitem.setIcon(0, self.statusIcon[OK])
        devitem.setToolTip(0, params.get('description', ''))
        self._devitems[ldevname] = devitem
        # fill the device info with dummy values, will be populated below
        self._devinfo[ldevname] = DevInfo(devname)

        # let the cache handler process all properties
        for key, value in iteritems(params):
            self.on_client_cache(
                (0, ldevname + '/' + key, OP_TELL, cache_dump(value)))

    def on_client_setup(self, setuplists):
        # update setup tooltips
        self._read_setup_info(setuplists)
        for i in range(self.tree.topLevelItemCount()):
            catitem = self.tree.topLevelItem(i)
            cat = catitem.text(0)
            catitem.setToolTip(0, self._setupinfo[cat].get('description', ''))

    def on_client_device(self, data):
        (action, devlist) = data
        if not devlist:
            return
        if action == 'create':
            for devname in devlist:
                self._create_device_item(devname, add_cat=True)
            self.tree.sortItems(0, Qt.AscendingOrder)
            self._update_view()
        elif action == 'destroy':
            self._store_view()
            for devname in devlist:
                ldevname = devname.lower()
                if ldevname in self._devitems:
                    # remove device item and cached info...
                    item = self._devitems[ldevname]
                    del self._devitems[ldevname]
                    del self._devinfo[ldevname]
                    self._devparamitems.pop(ldevname, None)
                    try:
                        catitem = item.parent()
                    except RuntimeError:
                        # Qt object has already been destroyed
                        pass
                    else:
                        catitem.removeChild(item)
                        # remove category item if it has no further children
                        if catitem.childCount() == 0:
                            self.tree.takeTopLevelItem(
                                self.tree.indexOfTopLevelItem(catitem))
                            del self._catitems[catitem.text(0)]
            self._update_view()

    def on_client_cache(self, data):
        (time, key, op, value) = data
        if '/' not in key:
            return
        ldevname, subkey = key.rsplit('/', 1)
        if ldevname not in self._devinfo:
            return
        if ldevname in self._control_dialogs:
            self._control_dialogs[ldevname].on_cache(subkey, value)
        devitem = self._devitems[ldevname]
        devinfo = self._devinfo[ldevname]
        if subkey == 'value':
            if time < devinfo.valtime:
                return
            if not value:
                fvalue = ''
            else:
                fvalue = cache_load(value)
                if isinstance(fvalue, list):
                    fvalue = tuple(fvalue)
            devinfo.value = fvalue
            devinfo.expired = op != OP_TELL
            devinfo.valtime = time
            fmted = devinfo.fmtValUnit()
            devitem.setText(1, fmted)
            if ldevname in self._control_dialogs:
                self._control_dialogs[ldevname].valuelabel.setText(fmted)
            devitem.setForeground(1, valueBrush[devinfo.expired,
                                                devinfo.fixed])
            if not devitem.parent().isExpanded():
                if ldevname == devitem.parent().representative:
                    devitem.parent().setText(1, fmted)
        elif subkey == 'status':
            if time < devinfo.stattime:
                return
            if not value:
                status = (UNKNOWN, '?')
            else:
                status = cache_load(value)
            devinfo.status = status
            devinfo.stattime = time
            devitem.setText(2, str(status[1]))
            if status[0] not in self.statusIcon:
                # old or wrong status constant
                return
            if self.useicons:
                devitem.setIcon(0, self.statusIcon[status[0]])
                devitem.setForeground(2, foregroundBrush[status[0]])
                devitem.setBackground(2, backgroundBrush[status[0]])
            else:
                devitem.setForeground(0, foregroundBrush[BUSY])
                devitem.setBackground(0, backgroundBrush[status[0]])
            if not devitem.parent().isExpanded():
                item = devitem.parent()
                item.setBackground(
                    0, backgroundBrush[self._getHighestStatus(item)])
            else:
                devitem.parent().setBackground(0, backgroundBrush[OK])
            if ldevname in self._control_dialogs:
                dlg = self._control_dialogs[ldevname]
                dlg.statuslabel.setText(status[1])
                dlg.statusimage.setPixmap(self.statusIcon[status[0]].pixmap(
                    16, 16))
                setForegroundBrush(dlg.statuslabel, foregroundBrush[status[0]])
                setBackgroundBrush(dlg.statuslabel, backgroundBrush[status[0]])
        elif subkey == 'fmtstr':
            if not value:
                return
            devinfo.fmtstr = cache_load(value)
            devitem.setText(1, devinfo.fmtValUnit())
        elif subkey == 'unit':
            if not value:
                value = "''"
            devinfo.unit = cache_load(value)
            devitem.setText(1, devinfo.fmtValUnit())
        elif subkey == 'fixed':
            if not value:
                value = "''"
            devinfo.fixed = bool(cache_load(value))
            devitem.setForeground(1, valueBrush[devinfo.expired,
                                                devinfo.fixed])
            if ldevname in self._control_dialogs:
                dlg = self._control_dialogs[ldevname]
                if dlg.moveBtn:
                    dlg.moveBtn.setEnabled(not devinfo.fixed)
                    dlg.moveBtn.setText(devinfo.fixed and '(fixed)' or 'Move')
        elif subkey == 'userlimits':
            if not value:
                return
            value = cache_load(value)
            if ldevname in self._control_dialogs:
                dlg = self._control_dialogs[ldevname]
                dlg.limitMin.setText(str(value[0]))
                dlg.limitMax.setText(str(value[1]))
        elif subkey == 'classes':
            if not value:
                value = "[]"
            devinfo.classes = set(cache_load(value))
        elif subkey == 'alias':
            if not value:
                return
            if ldevname in self._control_dialogs:
                dlg = self._control_dialogs[ldevname]
                dlg._reinit()
        elif subkey == 'description':
            devitem.setToolTip(0, cache_load(value or "''"))
        if subkey in self.param_display.get(ldevname, ()):
            if not devinfo.params:
                devinfo.params = self.client.getDeviceParamInfo(devinfo.name)
            value = devinfo.fmtParam(subkey, cache_load(value))
            if subkey not in self._devparamitems.setdefault(ldevname, {}):
                devitem = self._devitems[ldevname]
                self._devparamitems[ldevname][subkey] = \
                    QTreeWidgetItem(devitem, [subkey, value, ''], PARAM_TYPE)
                devitem.setExpanded(True)
            else:
                self._devparamitems[ldevname][subkey].setText(1, value)

    def on_tree_itemExpanded(self, item):
        if item.type() == SETUP_TYPE:
            item.setText(1, '')
        item.setBackground(0, backgroundBrush[OK])

    def _getHighestStatus(self, item):
        retval = OK
        for i in range(item.childCount()):
            lstatus = self._devinfo[item.child(i).text(0).lower()].status[0]
            if retval < lstatus:
                retval = lstatus
        return retval

    def on_tree_itemCollapsed(self, item):
        if item.type() == SETUP_TYPE:
            item.setBackground(0,
                               backgroundBrush[self._getHighestStatus(item)])
            if item.representative:
                item.setText(1, self._devitems[item.representative].text(1))

    def on_tree_customContextMenuRequested(self, point):
        item = self.tree.itemAt(point)
        if item is None:
            return
        if item.type() == DEVICE_TYPE:
            self._menu_dev = item.text(0)
            ldevname = self._menu_dev.lower()
            if 'nicos.core.device.Moveable' in self._devinfo[ldevname].classes and \
               not self.client.viewonly:
                self.devmenu.popup(self.tree.viewport().mapToGlobal(point))
            elif 'nicos.core.device.Readable' in self._devinfo[
                    ldevname].classes:
                self.devmenu_ro.popup(self.tree.viewport().mapToGlobal(point))

    def on_filter_editTextChanged(self, text):
        for i in range(self.filter.count()):
            if text == self.filter.itemText(i):
                rx = QRegExp(self.filter.itemData(i))
                break
        else:
            rx = QRegExp(text)
        for i in range(self.tree.topLevelItemCount()):
            setupitem = self.tree.topLevelItem(i)
            all_children_hidden = True
            for j in range(setupitem.childCount()):
                devitem = setupitem.child(j)
                if rx.indexIn(devitem.text(0)) == -1:
                    devitem.setHidden(True)
                else:
                    devitem.setHidden(False)
                    all_children_hidden = False
            setupitem.setHidden(all_children_hidden)

    @pyqtSlot()
    def on_actionShutDown_triggered(self):
        if self._menu_dev:
            if self.askQuestion('This will unload the device until the setup '
                                'is loaded again. Proceed?'):
                self.exec_command('RemoveDevice(%s)' % srepr(self._menu_dev),
                                  ask_queue=False)

    @pyqtSlot()
    def on_actionReset_triggered(self):
        if self._menu_dev:
            self.exec_command('reset(%s)' % srepr(self._menu_dev))

    @pyqtSlot()
    def on_actionFix_triggered(self):
        if self._menu_dev:
            reason, ok = QInputDialog.getText(
                self, 'Fix',
                'Please enter the reason for fixing %s:' % self._menu_dev)
            if not ok:
                return
            self.exec_command('fix(%s, %r)' % (srepr(self._menu_dev), reason))

    @pyqtSlot()
    def on_actionRelease_triggered(self):
        if self._menu_dev:
            self.exec_command('release(%s)' % srepr(self._menu_dev))

    @pyqtSlot()
    def on_actionStop_triggered(self):
        if self._menu_dev:
            self.exec_command('stop(%s)' % srepr(self._menu_dev),
                              immediate=True)

    @pyqtSlot()
    def on_actionMove_triggered(self):
        if self._menu_dev:
            self._open_control_dialog(self._menu_dev)

    @pyqtSlot()
    def on_actionHelp_triggered(self):
        if self._menu_dev:
            self.client.eval('session.showHelp(session.devices[%r])' %
                             self._menu_dev)

    @pyqtSlot()
    def on_actionPlotHistory_triggered(self):
        if self._menu_dev:
            self.plot_history(self._menu_dev)

    def on_tree_itemActivated(self, item, column):
        if item.type() == DEVICE_TYPE:
            devname = item.text(0)
            self._open_control_dialog(devname)
        elif item.type() == PARAM_TYPE:
            devname = item.parent().text(0)
            dlg = self._open_control_dialog(devname)
            dlg.editParam(item.text(0))

    def _open_control_dialog(self, devname):
        ldevname = devname.lower()
        if ldevname in self._control_dialogs:
            dlg = self._control_dialogs[ldevname]
            if dlg.isVisible():
                dlg.activateWindow()
                return dlg
        devinfo = self._devinfo[ldevname]
        item = self._devitems[ldevname]
        dlg = ControlDialog(self, devname, devinfo, item, self.log,
                            self._show_lowlevel)
        dlg.closed.connect(self._control_dialog_closed)
        dlg.rejected.connect(dlg.close)
        self._control_dialogs[ldevname] = dlg
        dlg.show()
        return dlg

    def _control_dialog_closed(self, ldevname):
        dlg = self._control_dialogs.pop(ldevname, None)
        if dlg:
            dlg.deleteLater()

    # API shared with ControlDialog

    def exec_command(self, command, ask_queue=True, immediate=False):
        if ask_queue and not immediate and self._current_status != 'idle':
            qwindow = ScriptExecQuestion()
            result = qwindow.exec_()
            if result == QMessageBox.Cancel:
                return
            elif result == QMessageBox.Apply:
                immediate = True
        if immediate:
            self.client.tell('exec', command)
            self._exec_reqid = None  # no request assigned to this command
        else:
            self._exec_reqid = self.client.run(command)

    def plot_history(self, dev):
        if self.mainwindow.history_wintype is not None:
            win = self.mainwindow.createWindow(self.mainwindow.history_wintype)
            if win:
                panel = win.getPanel('History viewer')
                panel.newView(dev)
                showPanel(panel)
コード例 #29
0
    def getMenus(self):
        if not self.menus:
            menu1 = QMenu('&Data plot', self)
            menu1.addAction(self.actionSavePlot)
            menu1.addAction(self.actionPrint)
            menu1.addAction(self.actionAttachElog)
            menu1.addSeparator()
            menu1.addAction(self.actionResetPlot)
            menu1.addAction(self.actionAutoDisplay)
            menu1.addAction(self.actionCombine)
            menu1.addAction(self.actionClosePlot)
            menu1.addAction(self.actionDeletePlot)
            menu1.addSeparator()
            menu1.addAction(self.actionXAxis)
            menu1.addAction(self.actionYAxis)
            menu1.addAction(self.actionNormalized)
            menu1.addSeparator()
            menu1.addAction(self.actionUnzoom)
            menu1.addAction(self.actionLogXScale)
            menu1.addAction(self.actionLogScale)
            menu1.addAction(self.actionAutoScale)
            menu1.addAction(self.actionScaleX)
            menu1.addAction(self.actionScaleY)
            menu1.addAction(self.actionLegend)
            menu1.addAction(self.actionErrors)
            menu1.addSeparator()

            menu2 = QMenu('Data &manipulation', self)
            menu2.addAction(self.actionModifyData)
            menu2.addSeparator()
            ag = QActionGroup(menu2)
            ag.addAction(self.actionFitPeakGaussian)
            ag.addAction(self.actionFitPeakLorentzian)
            ag.addAction(self.actionFitPeakPV)
            ag.addAction(self.actionFitPeakPVII)
            ag.addAction(self.actionFitTc)
            ag.addAction(self.actionFitCosine)
            ag.addAction(self.actionFitSigmoid)
            ag.addAction(self.actionFitLinear)
            ag.addAction(self.actionFitExponential)
            menu2.addAction(self.actionFitPeak)
            menu2.addAction(self.actionPickInitial)
            menu2.addAction(self.actionFitPeakGaussian)
            menu2.addAction(self.actionFitPeakLorentzian)
            menu2.addAction(self.actionFitPeakPV)
            menu2.addAction(self.actionFitPeakPVII)
            menu2.addAction(self.actionFitTc)
            menu2.addAction(self.actionFitCosine)
            menu2.addAction(self.actionFitSigmoid)
            menu2.addAction(self.actionFitLinear)
            menu2.addAction(self.actionFitExponential)
            menu2.addSeparator()
            menu2.addAction(self.actionFitArby)

            self.menus = [menu1, menu2]

        return self.menus
コード例 #30
0
    def __init__(self, parent, client, options):
        DevicesPanel._createIcons()
        Panel.__init__(self, parent, client, options)
        loadUi(self, self.ui)
        self.useicons = bool(options.get('icons', True))
        self.param_display = {}
        param_display = options.get('param_display', {})
        for (key, value) in param_display.items():
            value = [value] if isinstance(value, string_types) else list(value)
            self.param_display[key.lower()] = value

        self.tree.header().restoreState(self._headerstate)
        self.clear()

        self.devmenu = QMenu(self)
        self.devmenu.addAction(self.actionMove)
        self.devmenu.addAction(self.actionStop)
        self.devmenu.addAction(self.actionReset)
        self.devmenu.addSeparator()
        self.devmenu.addAction(self.actionFix)
        self.devmenu.addAction(self.actionRelease)
        self.devmenu.addSeparator()
        if self.mainwindow.history_wintype is not None:
            self.devmenu.addAction(self.actionPlotHistory)
            self.devmenu.addSeparator()
        self.devmenu.addAction(self.actionShutDown)
        self.devmenu.addAction(self.actionHelp)

        self.devmenu_ro = QMenu(self)
        self.devmenu_ro.addAction(self.actionMove)
        self.devmenu_ro.addAction(self.actionReset)
        self.devmenu_ro.addSeparator()
        if self.mainwindow.history_wintype is not None:
            self.devmenu_ro.addAction(self.actionPlotHistory)
            self.devmenu_ro.addSeparator()
        self.devmenu_ro.addAction(self.actionShutDown)
        self.devmenu_ro.addAction(self.actionHelp)

        self._menu_dev = None  # device for which context menu is shown
        self._dev2setup = {}
        self._setupinfo = {}

        self._control_dialogs = {}
        self._show_lowlevel = self.mainwindow.expertmode

        # daemon request ID of last command executed from this panel
        # (used to display messages from this command)
        self._current_status = 'idle'
        self._exec_reqid = None
        self._error_window = None

        client.connected.connect(self.on_client_connected)
        client.disconnected.connect(self.on_client_disconnected)
        client.cache.connect(self.on_client_cache)
        client.device.connect(self.on_client_device)
        client.setup.connect(self.on_client_setup)
        client.message.connect(self.on_client_message)

        self.filters = options.get('filters', [])
        self.filter.addItem('')
        for text, rx in self.filters:
            self.filter.addItem('Filter: %s' % text, rx)
        self.filter.lineEdit().setPlaceholderText('Enter search expression')