Esempio n. 1
0
    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()
Esempio n. 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)
Esempio n. 3
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)')
Esempio n. 4
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)
Esempio n. 5
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)
Esempio n. 6
0
    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
Esempio n. 7
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()
Esempio n. 8
0
    def contextMenuOnItem(self, item, pos):
        if item is None:
            return  # invoked context menu on whitespace

        if item.type() == ItemTypes.Setup:
            menu = QMenu(self)
            addDeviceAction = menu.addAction('Add device...')
            addDeviceAction.triggered.connect(self.addDevice)
            menu.popup(pos)

        elif item.type() == ItemTypes.Directory:
            menu = QMenu(self)
            addSetupAction = menu.addAction('Add setup...')
            addSetupAction.triggered.connect(self.addSetup)
            menu.popup(pos)

        elif item.type() == ItemTypes.Device:
            menu = QMenu(self)
            removeDeviceAction = menu.addAction('Remove')
            removeDeviceAction.triggered.connect(self.removeDevice)
            menu.popup(pos)
Esempio n. 9
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]
Esempio n. 10
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)
Esempio n. 11
0
    def _show_context_menu(self):
        menu = QMenu()

        copy_action = QAction("Copy", self)
        copy_action.triggered.connect(self._handle_copy_cells)
        copy_action.setIcon(get_icon("file_copy-24px.svg"))
        menu.addAction(copy_action)

        cut_action = QAction("Cut", self)
        cut_action.triggered.connect(self._handle_cut_cells)
        cut_action.setIcon(get_icon("cut_24px.svg"))
        menu.addAction(cut_action)

        paste_action = QAction("Paste", self)
        paste_action.triggered.connect(self._handle_table_paste)
        paste_action.setIcon(get_icon("paste_24px.svg"))
        menu.addAction(paste_action)

        delete_action = QAction("Delete", self)
        delete_action.triggered.connect(self._delete_rows)
        delete_action.setIcon(get_icon("remove-24px.svg"))
        menu.addAction(delete_action)

        menu.exec_(QCursor.pos())
Esempio n. 12
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')
Esempio n. 13
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]
Esempio n. 14
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
Esempio n. 15
0
    def getMenus(self):
        if self._liveOnlyIndex is not None:
            return []

        if not self.menu:
            menu = QMenu('&Live data', self)
            menu.addAction(self.actionOpen)
            menu.addAction(self.actionPrint)
            menu.addAction(self.actionSavePlot)
            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)
            menu.addAction(self.actionSymbols)
            menu.addAction(self.actionLines)
            self.menu = menu
        return [self.menu]
Esempio n. 16
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)')
Esempio n. 17
0
class DevicesPanel(Panel):
    """Provides a graphical list of NICOS devices and their current values.

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

    Options:

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

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

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

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

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

    """

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def on_client_connected(self):
        self.clear()

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

        for devname in devlist:
            self._create_device_item(devname)

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

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

    def on_client_disconnected(self):
        self.clear()

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

            def reset_errorwindow():
                self._error_window = None

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # API shared with ControlDialog

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

    def plot_history(self, dev):
        if self.mainwindow.history_wintype is not None:
            win = self.mainwindow.createWindow(self.mainwindow.history_wintype)
            if win:
                panel = win.getPanel('History viewer')
                panel.newView(dev)
                showPanel(panel)
Esempio n. 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:

    * ``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 displaying of previously measured data.
    * ``liveonlyindex`` (default None) - Enable live only view. This disables
      interaction with the liveDataPanel and only displays the dataset of the
      set index.

    * ``defaults`` (default []) - List of strings representing options to be
      set for every configured plot.
      These options can not be set on a per plot basis since they are global.
      Options are as follows:

        * ``logscale`` - Switch the logarithic scale on.
        * ``center`` - Display the center lines for the image.
        * ``nolines`` - Display lines for the curve.
        * ``markers`` - Display symbols for data points.
        * ``unzoom`` - Unzoom the plot when new data is received.

    * ``plotsettings`` (default []) - List of dictionaries which contain
      settings for the individual datasets.

      Each entry will be applied to one of the detector's datasets.

      * ``plotcount`` (default 1) - Amount of plots in the dataset.
      * ``marks`` (default 'omark') - Shape of the markers (if displayed).
        Possible values are:

          'dot', 'plus', 'asterrisk', 'circle', 'diagonalcross', 'solidcircle',
          'triangleup', 'solidtriangleup', 'triangledown', 'solidtriangledown',
          'square', 'solidsquare', 'bowtie', 'solidbowtie', 'hourglass',
          'solidhourglass', 'diamond', 'soliddiamond', 'star', 'solidstar',
          'triupdown', 'solidtriright', 'solidtrileft', 'hollowplus',
          'solidplus', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star4',
          'star5', 'star6', 'star7', 'star8', 'vline', 'hline', 'omark'

      * ``markersize`` (default 1) - Size of the markers (if displayed).
      * ``offset`` (default 0) - Offset for the X axis labels of
        each curve in 1D plots.
      * ``colors`` (default ['blue']) - Color of the marks and lines
        (if displayed).
        If colors are set as a list the colors will be applied to the
        individual plots (and default back to blue when wrong/missing),
        for example:

        ['red', 'green']: The first plot will be red, the second green and
        the others will be blue (default).

        'red': all plots will be red.
    """

    panelName = 'Live data view'

    ui = f'{uipath}/panels/live.ui'

    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, self.ui)

        self._allowed_filetypes = set()
        self._allowed_detectors = set()
        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.unzoom = False
        self.lastSettingsIndex = None
        self._axis_labels = {}
        self.params = {}
        self._offset = 0

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

        # hide fileselection in liveonly mode
        self._liveOnlyIndex = options.get('liveonlyindex', None)
        if self._liveOnlyIndex is not None:
            self.pastFilesWidget.hide()
            self.statusBar.hide()
            # disable interactions with the plot
            self.setAttribute(Qt.WA_TransparentForMouseEvents)

        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.connected.connect(self.on_client_connected)
        client.cache.connect(self.on_cache)

        self.rois = {}
        self.detectorskey = None
        # configure allowed file types
        supported_filetypes = ReaderRegistry.filetypes()
        opt_filetypes = set(options.get('filetypes', supported_filetypes))
        self._allowed_filetypes = opt_filetypes & set(supported_filetypes)

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

        defaults = options.get('defaults', [])

        if 'logscale' in defaults:
            self.actionLogScale.setChecked(True)
        if 'center' in defaults:
            self.actionMarkCenter.setChecked(True)
        if 'nolines' not in defaults:
            self.actionLines.setChecked(True)
        if 'markers' in defaults:
            self.actionSymbols.setChecked(True)
        if 'unzoom' in defaults:
            self.unzoom = True

        self.plotsettings = options.get('plotsettings', [DEFAULTS])

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

        self._initControlsGUI()

    def _initControlsGUI(self):
        pass

    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(FILETYPE, '')
                item.setData(FILETAG, LIVE)
                self.fileList.insertItem(self.fileList.count(), item)
                self.liveitems.append(item)
            if self._liveOnlyIndex is not None:
                self.fileList.setCurrentRow(self._liveOnlyIndex)
        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

        # 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)

    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 self._liveOnlyIndex is not None:
            return []

        if not self.menu:
            menu = QMenu('&Live data', self)
            menu.addAction(self.actionOpen)
            menu.addAction(self.actionPrint)
            menu.addAction(self.actionSavePlot)
            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)
            menu.addAction(self.actionSymbols)
            menu.addAction(self.actionLines)
            self.menu = menu
        return [self.menu]

    def _get_all_widgets(self):
        yield self.widget
        yield from self._livewidgets.values()

    def getToolbars(self):
        if self._liveOnlyIndex is not None:
            return []

        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())

    @pyqtSlot()
    def on_actionLines_triggered(self):
        if self.widget and isinstance(self.widget, LiveWidget1D):
            self.widget.setLines(self.actionLines.isChecked())

    @pyqtSlot()
    def on_actionSymbols_triggered(self):
        if self.widget and isinstance(self.widget, LiveWidget1D):
            self.widget.setSymbols(self.actionSymbols.isChecked())

    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 self.rois.items():
                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 self._livewidgets.items():
                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'])
        self.detectorskey = (self.client.eval('session.experiment.name') +
                             '/detlist').lower()

    def normalizeType(self, dtype):
        normalized_type = numpy.dtype(dtype).str
        if normalized_type not in DATATYPES:
            self.log.warning('Unsupported live data format: %s',
                             normalized_type)
            return
        return normalized_type

    def getIndexedUID(self, idx):
        return str(self.params['uid']) + '-' + str(idx)

    def _process_axis_labels(self, blobs):
        """Convert the raw axis label descriptions.
        tuple: `from, to`: Distribute labels equidistantly between the two
                           values.
        numbertype: `index into labels`: Actual labels are provided.
                                         Value is the starting index.
                                         Extract from first available blob.
                                         Remove said blob from list.
        None: `default`: Start at 0 with stepwidth 1.

        Save the axis labels to the datacache.
        """

        CLASSIC = {'define': 'classic'}

        for i, datadesc in enumerate(self.params['datadescs']):
            labels = {}
            titles = {}
            for size, axis in zip(reversed(datadesc['shape']), AXES):
                # if the 'labels' key does not exist or does not have the right
                # axis key set default to 'classic'.
                label = datadesc.get('labels', {
                    'x': CLASSIC,
                    'y': CLASSIC
                }).get(axis, CLASSIC)

                if label['define'] == 'range':
                    start = label.get('start', 0)
                    size = label.get('length', 1)
                    step = label.get('step', 1)
                    end = start + step * size
                    labels[axis] = numpy.arange(start, end, step)
                elif label['define'] == 'array':
                    index = label.get('index', 0)
                    labels[axis] = numpy.frombuffer(blobs[index],
                                                    label.get('dtype', '<i4'))
                else:
                    labels[axis] = self.getDefaultLabels(size)
                labels[axis] += self._offset if axis == 'x' else 0
                titles[axis] = label.get('title')

            # save the labels in the datacache with uid as key
            uid = self.getIndexedUID(i)
            if uid not in self._datacache:
                self._datacache[uid] = {}

            self._datacache[uid]['labels'] = labels
            self._datacache[uid]['titles'] = titles

    def _process_livedata(self, data, idx):
        # ignore irrelevant data in liveOnly mode
        if self._liveOnlyIndex is not None and idx != self._liveOnlyIndex:
            return

        try:
            descriptions = self.params['datadescs']
        except KeyError:
            self.log.warning('Livedata with tag "Live" without '
                             '"datadescs" provided.')
            return

        # pylint: disable=len-as-condition
        if len(data):
            # we got live data with specified formats
            arrays = self.processDataArrays(
                idx, numpy.frombuffer(data, descriptions[idx]['dtype']))

            if arrays is None:
                return

            # put everything into the cache
            uid = self.getIndexedUID(idx)
            self._datacache[uid]['dataarrays'] = arrays

            self.liveitems[idx].setData(FILEUID, uid)

    def _process_filenames(self):
        # TODO: allow multiple fileformats?
        #       would need to modify input from DemonSession.notifyDataFile

        number_of_items = self.fileList.count()
        for i, filedesc in enumerate(self.params['filedescs']):
            uid = self.getIndexedUID(number_of_items + i)
            name = filedesc['filename']
            filetype = filedesc.get('fileformat')
            if filetype is None or filetype not in ReaderRegistry.filetypes():
                continue  # Ignore unregistered file types
            self.add_to_flist(name, filetype, FILE, uid)
            try:
                # update display for selected live channel,
                # just cache otherwise
                self.setDataFromFile(name,
                                     filetype,
                                     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 on_client_livedata(self, params, blobs):
        # blobs is a list of data blobs and labels blobs
        if self._allowed_detectors \
                and params['det'] not in self._allowed_detectors:
            return

        self.params = params
        self._runtime = params['time']
        if params['tag'] == LIVE:
            datacount = len(params['datadescs'])
            self.setLiveItems(datacount)

            self._process_axis_labels(blobs[datacount:])

            for i, blob in enumerate(blobs[:datacount]):
                self._process_livedata(blob, i)
            if not datacount:
                self._process_livedata([], 0)
        elif params['tag'] == FILE:
            self._process_filenames()

        self._show()

    def getDefaultLabels(self, size):
        return numpy.array(range(size))

    def convertLabels(self, labelinput):
        """Convert the input into a processable format"""

        for i, entry in enumerate(labelinput):
            if isinstance(entry, str):
                labelinput[i] = self.normalizeType(entry)

        return labelinput

    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 setDataFromFile(self, filename, filetype, uid=None, display=True):
        """Load data array from file and dispatch to live widgets using
        ``setData``. Do not use caching if uid is ``None``.
        """
        array = readDataFromFile(filename, filetype)
        if array is not None:
            if uid:
                if uid not in self._datacache:
                    self.log.debug('add to cache: %s', uid)
                self._datacache[uid] = {}
                self._datacache[uid]['dataarrays'] = [array]
            if display:
                self._initLiveWidget(array)
                for widget in self._get_all_widgets():
                    widget.setData(array)


#           self.setData([array], uid, display=display)
            return array.shape
        else:
            raise NicosError('Cannot read file %r' % filename)

    def processDataArrays(self, index, entry):
        """Check if the input 1D array has the expected amount of values.
        If the array is too small an Error is raised.
        If the size exceeds the expected amount it is truncated.

        Returns a list of arrays corresponding to the ``plotcount`` of
        ``index`` into ``datadescs`` of the current params"""

        datadesc = self.params['datadescs'][index]
        count = datadesc.get('plotcount', DEFAULTS['plotcount'])
        shape = datadesc['shape']

        # ignore irrelevant data in liveOnly mode
        if self._liveOnlyIndex is not None and index != self._liveOnlyIndex:
            return

        # determine 1D array size
        arraysize = numpy.product(shape)

        # check and split the input array
        if len(entry) < count * arraysize:
            self.log.warning('Expected dataarray with %d entries, got %d',
                             count * arraysize, len(entry))
            return
        arrays = numpy.split(entry[:count * arraysize], count)

        # reshape every array in the list
        for i, array in enumerate(arrays):
            arrays[i] = array.reshape(shape)
        return arrays

    def applyPlotSettings(self):
        if not self.widget or not isinstance(self.widget, LiveWidget1D):
            return

        if self._liveOnlyIndex is not None:
            index = self._liveOnlyIndex
        elif self.fileList.currentItem() not in self.liveitems:
            return
        else:
            index = self.fileList.currentRow()

        if isinstance(self.widget, LiveWidget1D):

            def getElement(l, index, default):
                try:
                    return l[index]
                except IndexError:
                    return default

            settings = getElement(self.plotsettings, index, DEFAULTS)

            if self.params['tag'] == LIVE:
                plotcount = self.params['datadescs'][index].get(
                    'plotcount', DEFAULTS['plotcount'])
            else:
                plotcount = DEFAULTS['plotcount']
            marks = [settings.get('marks', DEFAULTS['marks'])]
            markersize = settings.get('markersize', DEFAULTS['markersize'])
            offset = settings.get('offset', DEFAULTS['offset'])
            colors = settings.get('colors', DEFAULTS['colors'])

            if isinstance(colors, list):
                if len(colors) > plotcount:
                    colors = colors[:plotcount]
                while len(colors) < plotcount:
                    colors.append(DEFAULTS['colors'])
            else:
                colors = [colors] * plotcount

            self.setOffset(offset)
            self.widget.setMarks(marks)
            self.widget.setMarkerSize(markersize)
            self.widget.setPlotCount(plotcount, colors)

    def setOffset(self, offset):
        self._offset = offset

    def getDataFromItem(self, item):
        """Extract and return the data associated with the item.
        If the data is in the cache return it.
        If the data is in a valid file extract it from there.
        """

        if item is None:
            return

        uid = item.data(FILEUID)
        # data is cached
        if uid and hasattr(self, '_datacache') and uid in self._datacache:
            return self._datacache[uid]
        # cache has cleared data or data has not been cached in the first place
        elif uid is None and item.data(FILETAG) == FILE:
            filename = item.data(FILENAME)
            filetype = item.data(FILETYPE)

            if path.isfile(filename):
                rawdata = readDataFromFile(filename, filetype)
                labels = {}
                titles = {}
                for axis, entry in zip(AXES, reversed(rawdata.shape)):
                    labels[axis] = numpy.arange(entry)
                    titles[axis] = axis
                data = {
                    'labels': labels,
                    'titles': titles,
                    'dataarrays': [rawdata]
                }
                return data
            # else:
            # TODO: mark for deletion on item changed?

    def _show(self, data=None):
        """Show the provided data. If no data has been provided extract it
        from the datacache via the current item's uid.

        :param data: dictionary containing 'dataarrays' and 'labels'
        """

        idx = self.fileList.currentRow()
        if idx == -1:
            self.fileList.setCurrentRow(0)
            return

        # no data has been provided, try to get it from the cache
        if data is None:
            data = self.getDataFromItem(self.fileList.currentItem())
            # still no data
            if data is None:
                return

        arrays = data.get('dataarrays', [])
        labels = data.get('labels', {})
        titles = data.get('titles', {})

        # if multiple datasets have to be displayed in one widget, they have
        # the same dimensions, so we only need the dimensions of one set
        self._initLiveWidget(arrays[0])
        self.applyPlotSettings()
        for widget in self._get_all_widgets():
            widget.setData(arrays, labels)
            widget.setTitles(titles)

        if self.unzoom and self.widget:
            self.on_actionUnzoom_triggered()

    def remove_obsolete_cached_files(self):
        """Remove or flag items which are no longer cached.
        The cache will delete items if it's size exceeds ´cachesize´.
        This checks the items in the filelist and their caching status,
        removing items with deleted associated files and flagging items
        with valid files to be reloaded if selected by the user.
        """

        for index in reversed(range(self.fileList.count())):
            item = self.fileList.item(index)
            uid = item.data(FILEUID)
            # is the uid still cached
            if uid and uid not in self._datacache:
                # does the file still exist on the filesystem
                if path.isfile(item.data(FILENAME)):
                    item.setData(FILEUID, None)
                else:
                    self.fileList.takeItem(index)

    def add_to_flist(self, filename, filetype, tag, uid=None, scroll=True):
        # liveonly mode doesn't display a filelist
        if self._liveOnlyIndex is not None:
            return

        shortname = path.basename(filename)
        item = QListWidgetItem(shortname)
        item.setData(FILENAME, filename)
        item.setData(FILETYPE, filetype)
        item.setData(FILETAG, tag)
        item.setData(FILEUID, uid)
        self.fileList.insertItem(self.fileList.count(), item)
        if uid:
            self.remove_obsolete_cached_files()
        if scroll:
            self.fileList.scrollToBottom()
        return item

    def on_fileList_currentItemChanged(self):
        self._show()

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

        def _cacheFile(fn, filetype):
            uid = uuid4()
            # setDataFromFile may raise an `NicosException`, e.g.
            # if the file cannot be opened.
            try:
                self.setDataFromFile(fn, filetype, uid, display=False)
            except Exception as err:
                errors.append('%s: %s' % (fn, err))
            else:
                return self.add_to_flist(fn, filetype, FILE, uid)

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

        if errors:
            self.showError('Some files could not be opened:\n\n' +
                           '\n'.join(errors))

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

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

    @pyqtSlot()
    def on_actionSavePlot_triggered(self):
        self.widget.savePlot()

    @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())
Esempio n. 19
0
    def getMenus(self):
        if not self.menus:
            menu1 = QMenu('&Browser', self)
            menu1.addAction(self.actionBack)
            menu1.addAction(self.actionForward)
            menu1.addSeparator()
            menu1.addAction(self.actionRefresh)
            menu1.addAction(self.actionPrint)
            menu2 = QMenu('&Logbook', self)
            menu2.addAction(self.actionAddComment)
            menu2.addAction(self.actionAddRemark)
            menu2.addSeparator()
            menu2.addAction(self.actionAttachFile)
            menu2.addSeparator()
            menu2.addAction(self.actionNewSample)
            self.menus = [menu1, menu2]

        return self.menus
Esempio n. 20
0
class EditorPanel(Panel):
    """Provides a text editor specialized for entering scripts.

    Together with actions such as `Run` or `Simulate` it gives the user the
    opportunity to create and check measurement scripts.  The editor widget
    uses `QScintilla` if it is installed, and a standard text edit box
    otherwise.

    Options:

    * ``tools`` (default None) -- a list of `tools` which may configure some
      special commands or scripts.  The tools can generate code to insert
      into the editor window.  The access to these tools will be given via a
      special menu ``Editor tools``.
    * ``show_browser`` (default True) -- Toggle the default visibility of
      the Script Browser widget.
    * ``sim_window`` -- how to display dry run results: either ``"inline"``
      (in a dock widget in the panel, the default), ``"single"`` (in an
      external window, the same for each run), or ``"multi"`` (each run opens
      a new window).
    """

    panelName = 'User editor'

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

        self.window = parent
        self.custom_font = None
        self.custom_back = None

        self.mainwindow.codeGenerated.connect(self.on_codeGenerated)

        if not has_scintilla:
            self.actionComment.setEnabled(False)

        self.menus = None
        self.bar = None
        self.current_status = None
        self.recentf_actions = []
        self.searchdlg = None
        self.menuRecent = QMenu('Recent files')

        self.menuToolsActions = []

        for fn in self.recentf:
            action = QAction(fn.replace('&', '&&'), self)
            action.setData(fn)
            action.triggered.connect(self.openRecentFile)
            self.recentf_actions.append(action)
            self.menuRecent.addAction(action)

        self.tabber = QTabWidget(self, tabsClosable=True, documentMode=True)
        self.tabber.currentChanged.connect(self.on_tabber_currentChanged)
        self.tabber.tabCloseRequested.connect(self.on_tabber_tabCloseRequested)

        self.toolconfig = options.get('tools')
        self.sim_window = options.get('sim_window', 'inline')
        if self.sim_window not in ('single', 'multi', 'inline'):
            self.log.warning('invalid sim_window option %r, using inline',
                             self.sim_window)
            self.sim_window = 'inline'

        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.addWidget(self.tabber)
        self.mainFrame.setLayout(hlayout)

        self.editors = []    # tab index -> editor
        self.filenames = {}  # editor -> filename
        self.watchers = {}   # editor -> QFileSystemWatcher
        self.currentEditor = None

        self.saving = False  # True while saving
        self.warnWidget.hide()

        self.simFrame = SimResultFrame(self, None, self.client)
        self.simPaneFrame.layout().addWidget(self.simFrame)
        self.simPane.hide()
        self.simWindows = []

        self.splitter.restoreState(self.splitterstate)
        self.treeModel = QFileSystemModel()
        idx = self.treeModel.setRootPath('/')
        self.treeModel.setNameFilters(['*.py', '*.txt'])
        self.treeModel.setNameFilterDisables(False)  # hide them
        self.fileTree.setModel(self.treeModel)
        self.fileTree.header().hideSection(1)
        self.fileTree.header().hideSection(2)
        self.fileTree.header().hideSection(3)
        self.fileTree.header().hide()
        self.fileTree.setRootIndex(idx)
        if not options.get('show_browser', True):
            self.scriptsPane.hide()
        self.actionShowScripts = self.scriptsPane.toggleViewAction()
        self.actionShowScripts.setText('Show Script Browser')

        self.activeGroup = QActionGroup(self)
        self.activeGroup.addAction(self.actionRun)
        self.activeGroup.addAction(self.actionSimulate)
        self.activeGroup.addAction(self.actionUpdate)

        client.simresult.connect(self.on_client_simresult)
        if self.client.connected:
            self.on_client_connected()
        else:
            self.on_client_disconnected()
        client.connected.connect(self.on_client_connected)
        client.disconnected.connect(self.on_client_disconnected)
        client.setup.connect(self.on_client_connected)
        client.cache.connect(self.on_client_cache)
        client.experiment.connect(self.on_client_experiment)

        if self.openfiles:
            for fn in self.openfiles:
                self.openFile(fn, quiet=True)
        else:
            self.newFile()

    def __del__(self):
        # On some systems the  QFilesystemWatchers deadlock on application exit
        # so destroy them explicitly
        self.watchers.clear()

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

    def getMenus(self):
        menuFile = QMenu('&File', self)
        menuFile.addAction(self.actionNew)
        menuFile.addAction(self.actionOpen)
        menuFile.addAction(self.menuRecent.menuAction())
        menuFile.addAction(self.actionSave)
        menuFile.addAction(self.actionSaveAs)
        menuFile.addAction(self.actionReload)
        menuFile.addSeparator()
        menuFile.addAction(self.actionPrint)

        menuView = QMenu('&View', self)
        menuView.addAction(self.actionShowScripts)

        menuEdit = QMenu('&Edit', self)
        menuEdit.addAction(self.actionUndo)
        menuEdit.addAction(self.actionRedo)
        menuEdit.addSeparator()
        menuEdit.addAction(self.actionCut)
        menuEdit.addAction(self.actionCopy)
        menuEdit.addAction(self.actionPaste)
        menuEdit.addSeparator()
        menuEdit.addAction(self.actionComment)
        menuEdit.addSeparator()
        menuEdit.addAction(self.actionFind)

        menuScript = QMenu('&Script', self)
        menuScript.addSeparator()
        menuScript.addAction(self.actionRun)
        menuScript.addAction(self.actionSimulate)
        menuScript.addAction(self.actionUpdate)
        menuScript.addSeparator()
        menuScript.addAction(self.actionGet)

        if self.toolconfig:
            menuTools = QMenu('Editor t&ools', self)
            createToolMenu(self, self.toolconfig, menuTools)
            menus = [menuFile, menuView, menuEdit, menuScript, menuTools]
        else:
            menus = [menuFile, menuView, menuEdit, menuScript]

        self.menus = menus
        return self.menus

    def getToolbars(self):
        if not self.bar:
            bar = QToolBar('Editor')
            bar.addAction(self.actionNew)
            bar.addAction(self.actionOpen)
            bar.addAction(self.actionSave)
            bar.addSeparator()
            bar.addAction(self.actionPrint)
            bar.addSeparator()
            bar.addAction(self.actionUndo)
            bar.addAction(self.actionRedo)
            bar.addSeparator()
            bar.addAction(self.actionCut)
            bar.addAction(self.actionCopy)
            bar.addAction(self.actionPaste)
            bar.addSeparator()
            bar.addAction(self.actionRun)
            bar.addAction(self.actionSimulate)
            bar.addAction(self.actionGet)
            bar.addAction(self.actionUpdate)
            showToolText(bar, self.actionRun)
            showToolText(bar, self.actionSimulate)
            showToolText(bar, self.actionGet)
            showToolText(bar, self.actionUpdate)
            self.bar = bar

        return [self.bar]

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

    def setCustomStyle(self, font, back):
        self.custom_font = font
        self.custom_back = back
        self.simFrame.simOutView.setFont(font)
        self.simFrame.simOutViewErrors.setFont(font)
        for editor in self.editors:
            self._updateStyle(editor)

    def _updateStyle(self, editor):
        if self.custom_font is None:
            return
        bold = QFont(self.custom_font)
        bold.setBold(True)
        if has_scintilla:
            lexer = editor.lexer()
            lexer.setDefaultFont(self.custom_font)
            for i in range(16):
                lexer.setFont(self.custom_font, i)
            # make keywords bold
            lexer.setFont(bold, 5)
        else:
            editor.setFont(self.custom_font)
        if has_scintilla:
            lexer.setPaper(self.custom_back)
        else:
            setBackgroundColor(editor, self.custom_back)

    def enableFileActions(self, on):
        for action in [
            self.actionSave, self.actionSaveAs, self.actionReload,
            self.actionPrint, self.actionUndo, self.actionRedo, self.actionCut,
            self.actionCopy, self.actionPaste, self.actionFind,
        ]:
            action.setEnabled(on)
        self.enableExecuteActions(self.client.isconnected)
        for action in [self.actionComment]:
            action.setEnabled(on and has_scintilla)

    def enableExecuteActions(self, on):
        for action in [
            self.actionRun, self.actionSimulate, self.actionGet,
            self.actionUpdate
        ]:
            action.setEnabled(self.client.isconnected)

    def on_codeGenerated(self, code):
        if self.currentEditor:
            self.currentEditor.beginUndoAction()
            if self.currentEditor.text():
                res = OverwriteQuestion().exec_()
                if res == QMessageBox.Apply:
                    self.currentEditor.clear()
                elif res == QMessageBox.Cancel:
                    return
            # append() and setText() would clear undo history in QScintilla,
            # therefore we use these calls
            self.currentEditor.moveToEnd()
            self.currentEditor.insert(code)
            self.currentEditor.endUndoAction()
        else:
            self.showError('No script is opened at the moment.')

    def on_tabber_currentChanged(self, index):
        self.enableFileActions(index >= 0)
        if index == -1:
            self.currentEditor = None
            self.window.setWindowTitle('%s editor' % self.mainwindow.instrument)
            return
        editor = self.editors[index]
        fn = self.filenames[editor]
        if fn:
            self.window.setWindowTitle('%s[*] - %s editor' %
                                       (fn, self.mainwindow.instrument))
        else:
            self.window.setWindowTitle('New[*] - %s editor' %
                                       self.mainwindow.instrument)
        self.window.setWindowModified(editor.isModified())
        self.actionSave.setEnabled(editor.isModified())
        self.actionUndo.setEnabled(editor.isModified())
        self.currentEditor = editor
        if self.searchdlg:
            self.searchdlg.setEditor(editor)

    def on_tabber_tabCloseRequested(self, index):
        editor = self.editors[index]
        self._close(editor)

    def _close(self, editor):
        if not self.checkDirty(editor):
            return
        index = self.editors.index(editor)
        del self.editors[index]
        del self.filenames[editor]
        del self.watchers[editor]
        self.tabber.removeTab(index)

    def setDirty(self, editor, dirty):
        if editor is self.currentEditor:
            self.actionSave.setEnabled(dirty)
            self.actionUndo.setEnabled(dirty)
            self.window.setWindowModified(dirty)
            index = self.tabber.currentIndex()
            tt = self.tabber.tabText(index).rstrip('*')
            self.tabber.setTabText(index, tt + (dirty and '*' or ''))

    def loadSettings(self, settings):
        self.recentf = settings.value('recentf') or []
        self.splitterstate = settings.value('splitter', '', QByteArray)
        self.openfiles = settings.value('openfiles') or []

    def saveSettings(self, settings):
        settings.setValue('splitter', self.splitter.saveState())
        settings.setValue('openfiles',
                          [self.filenames[e] for e in self.editors
                           if self.filenames[e]])

    def requestClose(self):
        for editor in self.editors:
            if not self.checkDirty(editor):
                return False
        return True

    def createEditor(self):
        if has_scintilla:
            editor = QsciScintillaCustom(self)
            lexer = QsciLexerPython(editor)
            editor.setUtf8(True)
            editor.setLexer(lexer)
            editor.setAutoIndent(True)
            editor.setEolMode(QsciScintilla.EolUnix)
            editor.setIndentationsUseTabs(False)
            editor.setIndentationGuides(True)
            editor.setTabIndents(True)
            editor.setBackspaceUnindents(True)
            editor.setTabWidth(4)
            editor.setIndentationWidth(0)
            editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)
            editor.setFolding(QsciScintilla.PlainFoldStyle)
            editor.setIndentationGuidesForegroundColor(QColor("#CCC"))
            editor.setWrapMode(QsciScintilla.WrapCharacter)
            editor.setMarginLineNumbers(1, True)
            editor.setMarginWidth(
                1, 5 + 4 * QFontMetrics(editor.font()).averageCharWidth())
        else:
            editor = QScintillaCompatible(self)
        # editor.setFrameStyle(0)
        editor.modificationChanged.connect(
            lambda dirty: self.setDirty(editor, dirty))
        self._updateStyle(editor)
        return editor

    def on_client_connected(self):
        self.enableExecuteActions(True)
        self._set_scriptdir()

    def on_client_disconnected(self):
        self.enableExecuteActions(False)

    def _set_scriptdir(self):
        initialdir = self.client.eval('session.experiment.scriptpath', '')
        if initialdir:
            idx = self.treeModel.setRootPath(initialdir)
            self.fileTree.setRootIndex(idx)

    def on_client_cache(self, data):
        (_time, key, _op, _value) = data
        if key.endswith('/scriptpath'):
            self.on_client_connected()

    def on_client_simresult(self, data):
        if self.sim_window == 'inline':
            self.actionSimulate.setEnabled(True)

    def on_client_experiment(self, data):
        (_, proptype) = data
        self._set_scriptdir()
        self.simPane.hide()
        if proptype == 'user':
            # close existing tabs when switching TO a user experiment
            for index in range(len(self.editors) - 1, -1, -1):
                self.on_tabber_tabCloseRequested(index)
            # if all tabs have been closed, open a new file
            if not self.tabber.count():
                self.on_actionNew_triggered()

    def on_fileTree_doubleClicked(self, idx):
        fpath = self.treeModel.filePath(idx)
        for i, editor in enumerate(self.editors):
            if self.filenames[editor] == fpath:
                self.tabber.setCurrentIndex(i)
                return
        self.openFile(fpath)

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        if has_scintilla:
            printer = Printer()
            printer.setOutputFileName('')
            printer.setDocName(self.filenames[self.currentEditor])
            # printer.setFullPage(True)
            if QPrintDialog(printer, self).exec_() == QDialog.Accepted:
                lexer = self.currentEditor.lexer()
                bgcolor = lexer.paper(0)
                # printer prints background color too, so set it to white
                lexer.setPaper(Qt.white)
                printer.printRange(self.currentEditor)
                lexer.setPaper(bgcolor)
        else:
            printer = QPrinter()
            printer.setOutputFileName('')
            if QPrintDialog(printer, self).exec_() == QDialog.Accepted:
                getattr(self.currentEditor, 'print')(printer)

    def validateScript(self):
        script = self.currentEditor.text()
        # XXX: this does not apply to .txt (SPM) scripts
        # try:
        #    compile(script, 'script', 'exec')
        # except SyntaxError as err:
        #    self.showError('Syntax error in script: %s' % err)
        #    self.currentEditor.setCursorPosition(err.lineno - 1, err.offset)
        #    return
        return script

    @pyqtSlot()
    def on_actionRun_triggered(self):
        script = self.validateScript()
        if script is None:
            return
        if not self.checkDirty(self.currentEditor, askonly=True):
            return
        if self.current_status != 'idle':
            if not self.askQuestion('A script is currently running, do you '
                                    'want to queue this script?', True):
                return
        self.client.run(script, self.filenames[self.currentEditor])

    @pyqtSlot()
    def on_actionSimulate_triggered(self):
        script = self.validateScript()
        if script is None:
            return
        if not self.checkDirty(self.currentEditor, askonly=True):
            return
        simuuid = str(uuid1())
        filename = self.filenames[self.currentEditor]
        if self.sim_window == 'inline':
            self.actionSimulate.setEnabled(False)
            self.simFrame.simuuid = simuuid
            self.simFrame.clear()
            self.simPane.setWindowTitle('Dry run results - %s' % filename)
            self.simPane.show()
        else:
            if self.sim_window == 'multi' or not self.simWindows:
                window = SimResultFrame(None, self, self.client)
                window.setWindowTitle('Dry run results - %s' % filename)
                window.layout().setContentsMargins(6, 6, 6, 6)
                window.simOutView.setFont(self.simFrame.simOutView.font())
                window.simOutViewErrors.setFont(self.simFrame.simOutView.font())
                window.show()
                self.simWindows.append(window)
            else:
                window = self.simWindows[0]
                window.clear()
                window.setWindowTitle('Dry run results - %s' % filename)
                window.activateWindow()
            window.simuuid = simuuid
        self.client.tell('simulate', filename, script, simuuid)

    @pyqtSlot()
    def on_actionUpdate_triggered(self):
        script = self.validateScript()
        if script is None:
            return
        if not self.checkDirty(self.currentEditor, askonly=True):
            return
        reason, ok = QInputDialog.getText(
            self, 'Update reason', 'For the logbook, you can enter a reason '
            'for the update here:', text='no reason specified')
        if not ok:
            return
        self.client.tell('update', script, reason)

    @pyqtSlot()
    def on_actionGet_triggered(self):
        script = self.client.ask('getscript')
        if script is not None:
            editor = self.newFile()
            editor.setText(script)

    def checkDirty(self, editor, askonly=False):
        if not editor.isModified():
            return True
        if self.filenames[editor]:
            message = 'Save changes in %s before continuing?' % \
                self.filenames[editor]
        else:
            message = 'Save new file before continuing?'
        buttons = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
        if askonly:
            buttons = QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
        rc = QMessageBox.question(self, 'User Editor', message, buttons)
        if rc in (QMessageBox.Save, QMessageBox.Yes):
            return self.saveFile(editor)
        if rc in (QMessageBox.Discard, QMessageBox.No):
            return True
        return False

    def on_fileSystemWatcher_fileChanged(self, filename):
        if self.saving:
            return
        editor = watcher = None
        for editor, watcher in self.watchers.items():
            if watcher is self.sender():
                break
        else:
            return
        if editor.isModified():
            # warn the user
            self.warnText.setText(
                'The file %r has changed on disk, but has also been edited'
                ' here.\nPlease use either File-Reload to load the'
                ' version on disk or File-Save to save this version.'
                % self.filenames[editor])
            self.warnWidget.show()
        else:
            # reload without asking
            try:
                with open(self.filenames[editor]) as f:
                    text = f.read()
            except Exception:
                return
            editor.setText(text)
            editor.setModified(False)
        # re-add the filename to the watcher if it was deleted
        # (happens for programs that do delete-write on save)
        if not watcher.files():
            watcher.addPath(self.filenames[editor])

    @pyqtSlot()
    def on_actionNew_triggered(self):
        self.newFile()

    def newFile(self):
        editor = self.createEditor()
        editor.setModified(False)
        self.editors.append(editor)
        self.filenames[editor] = ''
        self.watchers[editor] = QFileSystemWatcher(self)
        self.watchers[editor].fileChanged.connect(
            self.on_fileSystemWatcher_fileChanged)
        self.tabber.addTab(editor, '(New script)')
        self.tabber.setCurrentWidget(editor)
        self.simFrame.clear()
        editor.setFocus()
        return editor

    @pyqtSlot()
    def on_actionOpen_triggered(self):
        if self.currentEditor is not None and self.filenames[self.currentEditor]:
            initialdir = path.dirname(self.filenames[self.currentEditor])
        else:
            initialdir = self.client.eval('session.experiment.scriptpath', '')
        fn = QFileDialog.getOpenFileName(self, 'Open script', initialdir,
                                         'Script files (*.py *.txt)')[0]
        if not fn:
            return
        self.openFile(fn)
        self.addToRecentf(fn)

    @pyqtSlot()
    def on_actionReload_triggered(self):
        fn = self.filenames[self.currentEditor]
        if not fn:
            return
        if not self.checkDirty(self.currentEditor):
            return
        try:
            with open(fn, 'r') as f:
                text = f.read()
        except Exception as err:
            return self.showError('Opening file failed: %s' % err)
        self.currentEditor.setText(text)
        self.simFrame.clear()

    def openRecentFile(self):
        self.openFile(self.sender().data())

    def openFile(self, fn, quiet=False):
        try:
            with open(fn.encode(sys.getfilesystemencoding())) as f:
                text = f.read()
        except Exception as err:
            if quiet:
                return
            return self.showError('Opening file failed: %s' % err)

        editor = self.createEditor()
        editor.setText(text)
        editor.setModified(False)

        # replace tab if it's a single new file
        if len(self.editors) == 1 and not self.filenames[self.editors[0]] and \
           not self.editors[0].isModified():
            self._close(self.editors[0])

        self.editors.append(editor)
        self.filenames[editor] = fn
        self.watchers[editor] = QFileSystemWatcher(self)
        self.watchers[editor].fileChanged.connect(
            self.on_fileSystemWatcher_fileChanged)
        self.watchers[editor].addPath(fn)
        self.tabber.addTab(editor, path.basename(fn))
        self.tabber.setCurrentWidget(editor)
        self.simFrame.clear()
        editor.setFocus()

    def addToRecentf(self, fn):
        new_action = QAction(fn.replace('&', '&&'), self)
        new_action.setData(fn)
        new_action.triggered.connect(self.openRecentFile)
        if self.recentf_actions:
            self.menuRecent.insertAction(self.recentf_actions[0], new_action)
            self.recentf_actions.insert(0, new_action)
            del self.recentf_actions[10:]
        else:
            self.menuRecent.addAction(new_action)
            self.recentf_actions.append(new_action)
        with self.sgroup as settings:
            settings.setValue('recentf',
                              [a.data() for a in self.recentf_actions])

    @pyqtSlot()
    def on_actionSave_triggered(self):
        self.saveFile(self.currentEditor)
        self.window.setWindowTitle(
            '%s[*] - %s editor' %
            (self.filenames[self.currentEditor], self.mainwindow.instrument))

    @pyqtSlot()
    def on_actionSaveAs_triggered(self):
        self.saveFileAs(self.currentEditor)
        self.window.setWindowTitle(
            '%s[*] - %s editor' %
            (self.filenames[self.currentEditor], self.mainwindow.instrument))

    def saveFile(self, editor):
        if not self.filenames[editor]:
            return self.saveFileAs(editor)

        try:
            self.saving = True
            try:
                with open(self.filenames[editor], 'w') as f:
                    f.write(editor.text())
            finally:
                self.saving = False
        except Exception as err:
            self.showError('Writing file failed: %s' % err)
            return False

        self.watchers[editor].addPath(self.filenames[editor])
        editor.setModified(False)
        return True

    def saveFileAs(self, editor):
        if self.filenames[editor]:
            initialdir = path.dirname(self.filenames[editor])
        else:
            initialdir = self.client.eval('session.experiment.scriptpath', '')
        if self.client.eval('session.spMode', False):
            defaultext = '.txt'
            flt = 'Script files (*.txt *.py)'
        else:
            defaultext = '.py'
            flt = 'Script files (*.py *.txt)'
        fn = QFileDialog.getSaveFileName(self, 'Save script', initialdir, flt)[0]
        if not fn:
            return False
        if not fn.endswith(('.py', '.txt')):
            fn += defaultext
        self.addToRecentf(fn)
        self.watchers[editor].removePath(self.filenames[editor])
        self.filenames[editor] = fn
        self.tabber.setTabText(self.editors.index(editor), path.basename(fn))
        return self.saveFile(editor)

    @pyqtSlot()
    def on_actionFind_triggered(self):
        if not self.searchdlg:
            self.searchdlg = SearchDialog(self, self.currentEditor,
                                          has_scintilla)
        self.searchdlg.setEditor(self.currentEditor)
        self.searchdlg.show()

    @pyqtSlot()
    def on_actionUndo_triggered(self):
        self.currentEditor.undo()

    @pyqtSlot()
    def on_actionRedo_triggered(self):
        self.currentEditor.redo()

    @pyqtSlot()
    def on_actionCut_triggered(self):
        self.currentEditor.cut()

    @pyqtSlot()
    def on_actionCopy_triggered(self):
        self.currentEditor.copy()

    @pyqtSlot()
    def on_actionPaste_triggered(self):
        self.currentEditor.paste()

    @pyqtSlot()
    def on_actionComment_triggered(self):
        clen = len(COMMENT_STR)
        # act on selection?
        if self.currentEditor.hasSelectedText():
            # get the selection boundaries
            line1, index1, line2, index2 = self.currentEditor.getSelection()
            if index2 == 0:
                endLine = line2 - 1
            else:
                endLine = line2
            assert endLine >= line1

            self.currentEditor.beginUndoAction()
            # iterate over the lines
            action = []
            for line in range(line1, endLine + 1):
                if self.currentEditor.text(line).startswith(COMMENT_STR):
                    self.currentEditor.setSelection(line, 0, line, clen)
                    self.currentEditor.removeSelectedText()
                    action.append(-1)
                else:
                    self.currentEditor.insertAt(COMMENT_STR, line, 0)
                    action.append(1)
            # adapt original selection boundaries
            if index1 > 0:
                if action[0] == 1:
                    index1 += clen
                else:
                    index1 = max(0, index1 - clen)
            if endLine > line1 and index2 > 0:
                if action[-1] == 1:
                    index2 += clen
                else:
                    index2 = max(0, index2 - clen)
            # restore selection accordingly
            self.currentEditor.setSelection(line1, index1, line2, index2)
            self.currentEditor.endUndoAction()
        else:
            # comment line
            line, _ = self.currentEditor.getCursorPosition()
            self.currentEditor.beginUndoAction()
            if self.currentEditor.text(line).startswith(COMMENT_STR):
                self.currentEditor.setSelection(line, 0, line, clen)
                self.currentEditor.removeSelectedText()
            else:
                self.currentEditor.insertAt(COMMENT_STR, line, 0)
            self.currentEditor.endUndoAction()
Esempio n. 21
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
Esempio n. 22
0
    def getMenus(self):
        menuFile = QMenu('&File', self)
        menuFile.addAction(self.actionNew)
        menuFile.addAction(self.actionOpen)
        menuFile.addAction(self.menuRecent.menuAction())
        menuFile.addAction(self.actionSave)
        menuFile.addAction(self.actionSaveAs)
        menuFile.addAction(self.actionReload)
        menuFile.addSeparator()
        menuFile.addAction(self.actionPrint)

        menuView = QMenu('&View', self)
        menuView.addAction(self.actionShowScripts)

        menuEdit = QMenu('&Edit', self)
        menuEdit.addAction(self.actionUndo)
        menuEdit.addAction(self.actionRedo)
        menuEdit.addSeparator()
        menuEdit.addAction(self.actionCut)
        menuEdit.addAction(self.actionCopy)
        menuEdit.addAction(self.actionPaste)
        menuEdit.addSeparator()
        menuEdit.addAction(self.actionComment)
        menuEdit.addSeparator()
        menuEdit.addAction(self.actionFind)

        menuScript = QMenu('&Script', self)
        menuScript.addSeparator()
        menuScript.addAction(self.actionRun)
        menuScript.addAction(self.actionSimulate)
        menuScript.addAction(self.actionUpdate)
        menuScript.addSeparator()
        menuScript.addAction(self.actionGet)

        if self.toolconfig:
            menuTools = QMenu('Editor t&ools', self)
            createToolMenu(self, self.toolconfig, menuTools)
            menus = [menuFile, menuView, menuEdit, menuScript, menuTools]
        else:
            menus = [menuFile, menuView, menuEdit, menuScript]

        self.menus = menus
        return self.menus
Esempio n. 23
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
Esempio n. 24
0
class MainWindow(DlgUtils, QMainWindow):
    name = 'MainWindow'
    # Emitted when a panel generates code that an editor panel should add.
    codeGenerated = pyqtSignal(object)

    # Interval (in ms) to make "keepalive" queries to the daemon.
    keepaliveInterval = 12 * 3600 * 1000

    ui = 'main.ui'

    def __init__(self, log, gui_conf, viewonly=False, tunnel=''):
        QMainWindow.__init__(self)
        DlgUtils.__init__(self, 'NICOS')
        loadUi(self, self.ui)

        # set app icon in multiple sizes
        icon = QIcon()
        icon.addFile(':/appicon')
        icon.addFile(':/appicon-16')
        icon.addFile(':/appicon-48')
        self.setWindowIcon(icon)

        if tunnel and SSHTunnelForwarder is None:
            self.showError('You want to establish a connection to NICOS via '
                           "a SSH tunnel, but the 'sshtunnel' module is not "
                           'installed. The tunneling feature will disabled.')
        self.tunnel = tunnel if SSHTunnelForwarder is not None else ''
        self.tunnelServer = None

        # hide admin label until we are connected as admin
        self.adminLabel.hide()

        # our logger instance
        self.log = log

        # window for displaying errors
        self.errorWindow = None

        # window for "prompt" event confirmation
        self.promptWindow = None

        # debug console window, if opened
        self.debugConsole = None

        # log messages sent by the server
        self.messages = []

        # are we in expert mode?  (always false on startup)
        self.expertmode = False

        # no wrapping at startup
        self.allowoutputlinewrap = False

        # set-up the initial connection data
        self.conndata = ConnectionData(host='localhost',
                                       port=1301,
                                       user='******',
                                       password=None,
                                       viewonly=viewonly)

        # state members
        self.current_status = None

        # connect the client's events
        self.client = NicosGuiClient(self, self.log)
        self.client.error.connect(self.on_client_error)
        self.client.broken.connect(self.on_client_broken)
        self.client.failed.connect(self.on_client_failed)
        self.client.connected.connect(self.on_client_connected)
        self.client.disconnected.connect(self.on_client_disconnected)
        self.client.status.connect(self.on_client_status)
        self.client.showhelp.connect(self.on_client_showhelp)
        self.client.clientexec.connect(self.on_client_clientexec)
        self.client.plugplay.connect(self.on_client_plugplay)
        self.client.watchdog.connect(self.on_client_watchdog)
        self.client.prompt.connect(self.on_client_prompt)

        # data handling setup
        self.data = DataHandler(self.client)

        # panel configuration
        self.gui_conf = gui_conf
        self.initDataReaders()
        self.mainwindow = self

        # determine if there is an editor window type, because we would like to
        # have a way to open files from a console panel later
        self.editor_wintype = self.gui_conf.find_panel(
            ('editor.EditorPanel',
             'nicos.clients.gui.panels.editor.EditorPanel'))
        self.history_wintype = self.gui_conf.find_panel(
            ('history.HistoryPanel',
             'nicos.clients.gui.panels.history.HistoryPanel'))

        # additional panels
        self.panels = []
        self.splitters = []
        self.windowtypes = []
        self.windows = {}

        # setting presets
        self.instrument = self.gui_conf.name

        self.createWindowContent()

        # timer for reconnecting
        self.reconnectTimer = QTimer(singleShot=True, timeout=self._reconnect)
        self._reconnect_count = 0
        self._reconnect_time = 0

        # timer for session keepalive, every 12 hours
        self.keepaliveTimer = QTimer(singleShot=False, timeout=self._keepalive)
        self.keepaliveTimer.start(self.keepaliveInterval)

        # setup tray icon
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.activated.connect(self.on_trayIcon_activated)
        self.trayMenu = QMenu(self)
        nameAction = self.trayMenu.addAction(self.instrument)
        nameAction.setEnabled(False)
        self.trayMenu.addSeparator()
        toggleAction = self.trayMenu.addAction('Hide main window')
        toggleAction.setCheckable(True)
        toggleAction.triggered[bool].connect(
            lambda hide: self.setVisible(not hide))
        self.trayIcon.setContextMenu(self.trayMenu)

        # help window
        self.helpWindow = None
        # watchdog window
        self.watchdogWindow = None
        # plug-n-play notification windows
        self.pnpWindows = {}

        # create initial state
        self._init_toolbar()

    def _init_toolbar(self):
        self.statusLabel = QLabel('',
                                  self,
                                  pixmap=QPixmap(':/disconnected'),
                                  margin=5,
                                  minimumSize=QSize(30, 10))

        self.toolbar = self.toolBarMain
        self.toolbar.addWidget(self.statusLabel)
        self.setStatus('disconnected')

    def addPanel(self, panel, always=True):
        if always or panel not in self.panels:
            self.panels.append(panel)

    def createWindowContent(self):
        self.sgroup = SettingGroup('MainWindow')
        with self.sgroup as settings:
            loadUserStyle(self, settings)
            # load saved settings and stored layout for panel config
            self.loadSettings(settings)

        # create panels in the main window
        widget = createWindowItem(self.gui_conf.main_window, self, self, self,
                                  self.log)
        if widget:
            self.centralLayout.addWidget(widget)
        self.centralLayout.setContentsMargins(0, 0, 0, 0)

        # call postInit after creation of all panels
        for panel in self.panels:
            panel.postInit()

        with self.sgroup as settings:
            # geometry and window appearance
            loadBasicWindowSettings(self, settings)
            self.update()
            # load auxiliary windows state
            self.loadAuxWindows(settings)
        if len(self.splitstate) == len(self.splitters):
            for sp, st in zip(self.splitters, self.splitstate):
                sp.restoreState(st)

        if not self.gui_conf.windows:
            self.menuBar().removeAction(self.menuWindows.menuAction())
        for i, wconfig in enumerate(self.gui_conf.windows):
            action = ToolAction(self.client, QIcon(':/' + wconfig.icon),
                                wconfig.name, wconfig.options, self)
            self.toolBarWindows.addAction(action)
            self.menuWindows.addAction(action)

            def window_callback(on, i=i):
                self.createWindow(i)

            action.triggered[bool].connect(window_callback)
        if not self.gui_conf.windows:
            self.toolBarWindows.hide()
        else:
            self.toolBarWindows.show()

        createToolMenu(self, self.gui_conf.tools, self.menuTools)
        if isinstance(self.gui_conf.main_window, tabbed) and widget:
            widget.tabChangedTab(0)

    def createWindow(self, wtype):
        # for the history_wintype or editor_wintype
        if wtype == -1:
            return self
        try:
            wconfig = self.gui_conf.windows[wtype]
        except IndexError:
            # config outdated, window type doesn't exist
            return
        if wtype in self.windows:
            window = self.windows[wtype]
            window.activateWindow()
            return window
        window = AuxiliaryWindow(self, wtype, wconfig)
        if window.centralLayout.count():
            window.setWindowIcon(QIcon(':/' + wconfig.icon))
            self.windows[wtype] = window
            window.closed.connect(self.on_auxWindow_closed)
            for panel in window.panels:
                panel.updateStatus(self.current_status)
            window.show()
            return window
        else:
            del window
            return None

    def getPanel(self, panelName):
        for panelobj in self.panels:
            if panelobj.panelName == panelName:
                return panelobj

    def initDataReaders(self):
        try:
            # just import to register all default readers
            # pylint: disable=unused-import
            import nicos.devices.datasinks
        except ImportError:
            pass
        classes = self.gui_conf.options.get('reader_classes', [])
        for clsname in classes:
            try:
                importString(clsname)
            except ImportError:
                pass

    def on_auxWindow_closed(self, window):
        del self.windows[window.type]
        window.deleteLater()

    def setConnData(self, data):
        self.conndata = data

    def _reconnect(self):
        if self._reconnect_count and self.conndata.password is not None:
            self._reconnect_count -= 1
            if self._reconnect_count <= self.client.RECONNECT_TRIES_LONG:
                self._reconnect_time = self.client.RECONNECT_INTERVAL_LONG
            self.client.connect(self.conndata)

    def _keepalive(self):
        if self.client.isconnected:
            self.client.ask('keepalive')

    def show(self):
        QMainWindow.show(self)
        if self.autoconnect and not self.client.isconnected:
            self.on_actionConnect_triggered(True)
        if sys.platform == 'darwin':
            # on Mac OS loadBasicWindowSettings seems not to work before show()
            # so we do it here again
            with self.sgroup as settings:
                loadBasicWindowSettings(self, settings)

    def startup(self):
        self.show()
        startStartupTools(self, self.gui_conf.tools)

    def loadSettings(self, settings):
        self.autoconnect = settings.value('autoconnect', True, bool)

        self.connpresets = {}
        # new setting key, with dictionary values
        for (k, v) in settings.value('connpresets_new', {}).items():
            self.connpresets[k] = ConnectionData(**v)
        # if it was empty, try old setting key with list values
        if not self.connpresets:
            for (k, v) in settings.value('connpresets', {}).items():
                self.connpresets[k] = ConnectionData(host=v[0],
                                                     port=int(v[1]),
                                                     user=v[2],
                                                     password=None)
        self.lastpreset = settings.value('lastpreset', '')
        if self.lastpreset in self.connpresets:
            self.conndata = self.connpresets[self.lastpreset].copy()

        self.instrument = settings.value('instrument', self.gui_conf.name)
        self.confirmexit = settings.value('confirmexit', True, bool)
        self.warnwhenadmin = settings.value('warnwhenadmin', True, bool)
        self.showtrayicon = settings.value('showtrayicon', True, bool)
        self.autoreconnect = settings.value('autoreconnect', True, bool)
        self.autosavelayout = settings.value('autosavelayout', True, bool)
        self.allowoutputlinewrap = settings.value('allowoutputlinewrap', False,
                                                  bool)

        self.update()

    def loadAuxWindows(self, settings):
        open_wintypes = settings.value('auxwindows') or []
        if isinstance(open_wintypes, str):
            open_wintypes = [int(w) for w in open_wintypes.split(',')]

        for wtype in open_wintypes:
            if isinstance(wtype, str):
                wtype = int(wtype)
            self.createWindow(wtype)

    def saveWindowLayout(self):
        with self.sgroup as settings:
            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('windowstate', self.saveState())
            settings.setValue('splitstate',
                              [sp.saveState() for sp in self.splitters])
            open_wintypes = list(self.windows)
            settings.setValue('auxwindows', open_wintypes)

    def saveSettings(self, settings):
        settings.setValue('autoconnect', self.client.isconnected)
        settings.setValue(
            'connpresets_new',
            {k: v.serialize()
             for (k, v) in self.connpresets.items()})
        settings.setValue('lastpreset', self.lastpreset)
        settings.setValue('font', self.user_font)
        settings.setValue('color', self.user_color)

    def closeEvent(self, event):
        if self.confirmexit and QMessageBox.question(
                self, 'Quit', 'Do you really want to quit?',
                QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
            event.ignore()
            return

        for panel in self.panels:
            if not panel.requestClose():
                event.ignore()
                return

        if self.autosavelayout:
            self.saveWindowLayout()
        with self.sgroup as settings:
            self.saveSettings(settings)
        for panel in self.panels:
            with panel.sgroup as settings:
                panel.saveSettings(settings)

        for window in list(self.windows.values()):
            if not window.close():
                event.ignore()
                return

        if self.helpWindow:
            self.helpWindow.close()

        if self.client.isconnected:
            self.on_actionConnect_triggered(False)

        event.accept()
        QApplication.instance().quit()

    def setTitlebar(self, connected):
        inststr = str(self.instrument) or 'NICOS'
        if connected:
            hoststr = '%s at %s:%s' % (self.client.login, self.client.host,
                                       self.client.port)
            self.setWindowTitle('%s - %s' % (inststr, hoststr))
        else:
            self.setWindowTitle('%s - disconnected' % inststr)

    def setStatus(self, status, exception=False):
        if status == self.current_status:
            return
        if self.client.last_action_at and \
           self.current_status == 'running' and \
           status in ('idle', 'paused') and \
           currenttime() - self.client.last_action_at > 20:
            # show a visual indication of what happened
            if status == 'paused':
                msg = 'Script is now paused.'
            elif exception:
                msg = 'Script has exited with an error.'
            else:
                msg = 'Script has finished.'
            self.trayIcon.showMessage(self.instrument, msg)
            self.client.last_action_at = 0
        self.current_status = status
        isconnected = status != 'disconnected'
        self.actionConnect.setChecked(isconnected)
        if isconnected:
            self.actionConnect.setText('Disconnect')
        else:
            self.actionConnect.setText('Connect to server...')
            self.setTitlebar(False)
        # new status icon
        pixmap = QPixmap(':/' + status + ('exc' if exception else ''))
        self.statusLabel.setPixmap(pixmap)
        self.statusLabel.setToolTip('Script status: %s' % status)
        newicon = QIcon()
        newicon.addPixmap(pixmap, QIcon.Disabled)
        self.trayIcon.setIcon(newicon)
        self.trayIcon.setToolTip('%s status: %s' % (self.instrument, status))
        if self.showtrayicon:
            self.trayIcon.show()
        if self.promptWindow and status != 'paused':
            self.promptWindow.close()
        # propagate to panels
        for panel in self.panels:
            panel.updateStatus(status, exception)
        for window in self.windows.values():
            for panel in window.panels:
                panel.updateStatus(status, exception)

    def on_client_error(self, problem, exc=None):
        if exc is not None:
            self.log.error('Error from daemon', exc=exc)
        problem = strftime('[%m-%d %H:%M:%S] ') + problem
        if self.errorWindow is None:

            def reset_errorWindow():
                self.errorWindow = None

            self.errorWindow = ErrorDialog(self, windowTitle='Daemon error')
            self.errorWindow.accepted.connect(reset_errorWindow)
            self.errorWindow.addMessage(problem)
            self.errorWindow.show()
        else:
            self.errorWindow.addMessage(problem)

    def on_client_broken(self, problem):
        self.on_client_error(problem)
        if self.autoreconnect:
            self._reconnect_count = self.client.RECONNECT_TRIES
            self._reconnect_time = self.client.RECONNECT_INTERVAL_SHORT
            self.reconnectTimer.start(self._reconnect_time)

    def on_client_failed(self, problem):
        if self._reconnect_count:
            self.reconnectTimer.start(self._reconnect_time)
        else:
            self.on_client_error(problem)

    def on_client_connected(self):
        self.setStatus('idle')
        self._reconnect_count = 0

        self.setTitlebar(True)
        # get all server status info
        initstatus = self.client.ask('getstatus')
        if initstatus:
            # handle initial status
            self.on_client_status(initstatus['status'])
            # propagate info to all components
            self.client.signal('initstatus', initstatus)

        # show warning label for admin users
        self.adminLabel.setVisible(self.warnwhenadmin
                                   and self.client.user_level is not None
                                   and self.client.user_level >= ADMIN)

        self.actionViewOnly.setChecked(self.client.viewonly)

        # set focus to command input, if present
        for panel in self.panels:
            if isinstance(panel, ConsolePanel) and panel.hasinput:
                panel.commandInput.setFocus()

    def on_client_status(self, data):
        status = data[0]
        if status == STATUS_IDLE:
            self.setStatus('idle')
        elif status == STATUS_IDLEEXC:
            self.setStatus('idle', exception=True)
        elif status != STATUS_INBREAK:
            self.setStatus('running')
        else:
            self.setStatus('paused')

    def on_client_disconnected(self):
        self.adminLabel.setVisible(False)
        self.setStatus('disconnected')

    def on_client_showhelp(self, data):
        if not HelpWindow:
            return
        if self.helpWindow is None:
            self.helpWindow = HelpWindow(self, self.client)
        self.helpWindow.showHelp(data)
        self.helpWindow.activateWindow()

    def on_client_clientexec(self, data):
        # currently used for client-side plot using matplotlib; data is
        # (funcname, args, ...)
        plot_func_path = data[0]
        try:
            modname, funcname = plot_func_path.rsplit('.', 1)
            func = getattr(__import__(modname, None, None, [funcname]),
                           funcname)
            func(*data[1:])
        except Exception:
            self.log.exception(
                'Error during clientexec:\n%s',
                '\n'.join(traceback.format_tb(sys.exc_info()[2])))

    def on_client_plugplay(self, data):
        windowkey = data[0:2]  # (mode, setupname)
        if windowkey in self.pnpWindows:
            self.pnpWindows[windowkey].activateWindow()
        else:
            window = PnPSetupQuestion(self, self.client, data)
            self.pnpWindows[windowkey] = window
            window.closed.connect(self.on_pnpWindow_closed)
            window.show()

    def on_pnpWindow_closed(self, window):
        self.pnpWindows.pop(window.data[0:2], None)

    def on_client_watchdog(self, data):
        if self.watchdogWindow is None:
            self.watchdogWindow = WatchdogDialog(self)
        self.watchdogWindow.addEvent(data)
        if data[0] != 'resolved':
            self.watchdogWindow.show()

    def on_client_prompt(self, data):
        if self.promptWindow:
            self.promptWindow.close()

        # show non-modal dialog box that prompts the user to continue or abort
        prompt_text = data[0]
        dlg = self.promptWindow = QMessageBox(
            QMessageBox.Information, 'Confirmation required', prompt_text,
            QMessageBox.Ok | QMessageBox.Cancel, self)
        dlg.setWindowModality(Qt.NonModal)

        # give the buttons better descriptions
        btn = dlg.button(QMessageBox.Cancel)
        btn.setText('Abort script')
        btn.clicked.connect(lambda: self.client.tell_action('stop', BREAK_NOW))
        btn = dlg.button(QMessageBox.Ok)
        btn.setText('Continue script')
        btn.clicked.connect(lambda: self.client.tell_action('continue'))
        btn.setFocus()

        dlg.show()

    def on_trayIcon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.activateWindow()

    def on_actionExpert_toggled(self, on):
        self.expertmode = on
        for panel in self.panels:
            panel.setExpertMode(on)
        for window in self.windows.values():
            for panel in window.panels:
                panel.setExpertMode(on)

    def on_actionViewOnly_toggled(self, on):
        # also triggered when the action is checked by on_client_connected
        self.client.viewonly = on
        for panel in self.panels:
            panel.setViewOnly(on)
        for window in self.windows.values():
            for panel in window.panels:
                panel.setViewOnly(on)

    @pyqtSlot()
    def on_actionNicosHelp_triggered(self):
        if not HelpWindow:
            self.showError('Cannot open help window: Qt web extension is not '
                           'available on your system.')
            return
        if not self.client.isconnected:
            self.showError('Cannot open online help: you are not connected '
                           'to a daemon.')
            return
        self.client.eval('session.showHelp("index")')

    @pyqtSlot()
    def on_actionNicosDocu_triggered(self):
        if not QWebView:
            self.showError('Cannot open documentation window: Qt web extension'
                           ' is not available on your system.')
            return
        from nicos.clients.gui.tools.website import WebsiteTool
        # XXX: change URL to current release version
        dlg = WebsiteTool(self,
                          self.client,
                          url='http://www.nicos-controls.org')
        dlg.setWindowModality(Qt.NonModal)
        dlg.show()

    @pyqtSlot()
    def on_actionDebugConsole_triggered(self):
        if self.debugConsole is None:
            self.debugConsole = DebugConsole(self)
        self.debugConsole.show()

    @pyqtSlot()
    def on_actionAbout_triggered(self):
        import nicos.authors

        if self.client.isconnected:
            dinfo = self.client.daemon_info.copy()
            dinfo['server_host'] = self.client.host
        else:
            dinfo = {}
        dlg = dialogFromUi(self, 'dialogs/about.ui')
        dlg.clientVersion.setText(nicos_version)
        dlg.pyVersion.setText(
            '%s/%s/%s' %
            (sys.version.split()[0], QT_VERSION_STR, PYQT_VERSION_STR))
        dlg.serverHost.setText(dinfo.get('server_host', 'not connected'))
        dlg.nicosRoot.setText(dinfo.get('nicos_root', ''))
        dlg.serverVersion.setText(dinfo.get('daemon_version', ''))
        dlg.customPath.setText(dinfo.get('custom_path', ''))
        dlg.customVersion.setText(dinfo.get('custom_version', ''))
        dlg.contributors.setPlainText(nicos.authors.authors_list)
        dlg.adjustSize()
        dlg.exec_()

    @pyqtSlot(bool)
    def on_actionConnect_triggered(self, on):
        # connection or disconnection request?
        if not on:
            self.client.disconnect()
            if self.tunnelServer:
                self.tunnelServer.stop()
                self.tunnelServer = None
            return

        self.actionConnect.setChecked(False)  # gets set by connection event
        ret = ConnectionDialog.getConnectionData(self, self.connpresets,
                                                 self.lastpreset,
                                                 self.conndata, self.tunnel)
        new_name, new_data, save, tunnel = ret

        if new_data is None:
            return
        if save:
            self.lastpreset = save
            self.connpresets[save] = new_data
        else:
            self.lastpreset = new_name
        self.conndata = new_data
        if tunnel:
            try:
                host, username, password = splitTunnelString(tunnel)
                self.tunnelServer = SSHTunnelForwarder(
                    host,
                    ssh_username=username,
                    ssh_password=password,
                    remote_bind_address=(self.conndata.host,
                                         self.conndata.port),
                    compression=True)
                self.tunnelServer.start()
                tunnel_port = self.tunnelServer.local_bind_port

                # corresponding ssh command line (debug)
                # print 'ssh -f %s -L %d:%s:%d -N' % (host, tunnel_port,
                #                                     self.conndata.host,
                #                                     self.conndata.port)

                # store the established tunnel information host, user, and
                # password for the next connection try to avoid typing password
                # for every (re)connection via the GUI
                self.tunnel = tunnel
                self.conndata.host = 'localhost'
                self.conndata.port = tunnel_port
            except ValueError as e:
                self.showError(str(e))
                self.tunnelServer = None
            except BaseSSHTunnelForwarderError as e:
                self.showError(str(e))
                self.tunnelServer = None
        self.client.connect(self.conndata)

    @pyqtSlot()
    def on_actionPreferences_triggered(self):
        dlg = SettingsDialog(self)
        ret = dlg.exec_()
        if ret == QDialog.Accepted:
            dlg.saveSettings()

    @pyqtSlot()
    def on_actionFont_triggered(self):
        font, ok = QFontDialog.getFont(self.user_font, self)
        if not ok:
            return
        for panel in self.panels:
            panel.setCustomStyle(font, self.user_color)
        self.user_font = font

    @pyqtSlot()
    def on_actionColor_triggered(self):
        color = QColorDialog.getColor(self.user_color, self)
        if not color.isValid():
            return
        for panel in self.panels:
            panel.setCustomStyle(self.user_font, color)
        self.user_color = color
Esempio n. 25
0
 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]
Esempio n. 26
0
    def __init__(self, parent, client, options):
        Panel.__init__(self, parent, client, options)
        loadUi(self, 'panels/status.ui')

        self.stopcounting = False
        self.menus = None
        self.bar = None
        self.queueFrame.hide()
        self.statusLabel.hide()
        self.pause_color = QColor('#ffdddd')
        self.idle_color = parent.user_color

        self.script_queue = ScriptQueue(self.queueFrame, self.queueView)
        self.current_line = -1
        self.current_request = {}
        self.curlineicon = QIcon(':/currentline')
        self.errlineicon = QIcon(':/errorline')
        empty = QPixmap(16, 16)
        empty.fill(Qt.transparent)
        self.otherlineicon = QIcon(empty)
        self.traceView.setItemDelegate(LineDelegate(24, self.traceView))

        self.stopcounting = bool(options.get('stopcounting', False))
        if self.stopcounting:
            tooltip = 'Aborts the current executed script'
            self.actionStop.setToolTip(tooltip)
            self.actionStop.setText('Abort current script')
            self.actionStop2.setToolTip(tooltip)

        self.showETA = bool(options.get('eta', False))
        self.etaWidget.hide()

        client.request.connect(self.on_client_request)
        client.processing.connect(self.on_client_processing)
        client.blocked.connect(self.on_client_blocked)
        client.status.connect(self.on_client_status)
        client.initstatus.connect(self.on_client_initstatus)
        client.disconnected.connect(self.on_client_disconnected)
        client.rearranged.connect(self.on_client_rearranged)
        client.updated.connect(self.on_client_updated)
        client.eta.connect(self.on_client_eta)

        bar = QToolBar('Script control')
        bar.setObjectName(bar.windowTitle())
        # unfortunately it is not wise to put a menu in its own dropdown menu,
        # so we have to duplicate the actionBreak and actionStop...
        dropdown1 = QMenu('', self)
        dropdown1.addAction(self.actionBreak)
        dropdown1.addAction(self.actionBreakCount)
        dropdown1.addAction(self.actionFinishEarly)
        self.actionBreak2.setMenu(dropdown1)
        dropdown2 = QMenu('', self)
        dropdown2.addAction(self.actionStop)
        dropdown2.addAction(self.actionFinish)
        dropdown2.addAction(self.actionFinishEarlyAndStop)
        self.actionStop2.setMenu(dropdown2)
        bar.addAction(self.actionBreak2)
        bar.addAction(self.actionContinue)
        bar.addAction(self.actionStop2)
        bar.addAction(self.actionEmergencyStop)
        self.bar = bar
        # self.mainwindow.addToolBar(bar)

        menu = QMenu('&Script control', self)
        menu.addAction(self.actionBreak)
        menu.addAction(self.actionBreakCount)
        menu.addAction(self.actionContinue)
        menu.addAction(self.actionFinishEarly)
        menu.addSeparator()
        menu.addAction(self.actionStop)
        menu.addAction(self.actionFinish)
        menu.addAction(self.actionFinishEarlyAndStop)
        menu.addSeparator()
        menu.addAction(self.actionEmergencyStop)
        self.mainwindow.menuBar().insertMenu(
            self.mainwindow.menuWindows.menuAction(), menu)

        self.activeGroup = QActionGroup(self)
        self.activeGroup.addAction(self.actionBreak)
        self.activeGroup.addAction(self.actionBreak2)
        self.activeGroup.addAction(self.actionBreakCount)
        self.activeGroup.addAction(self.actionContinue)
        self.activeGroup.addAction(self.actionStop)
        self.activeGroup.addAction(self.actionStop2)
        self.activeGroup.addAction(self.actionFinish)
        self.activeGroup.addAction(self.actionFinishEarly)
        self.activeGroup.addAction(self.actionFinishEarlyAndStop)

        self._status = 'idle'
Esempio n. 27
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())
Esempio n. 28
0
class ConsolePanel(Panel):
    """Provides a console-like interface.

    The commands can be entered and the output from the NICOS daemon is
    displayed.

    Options:

    * ``hasinput`` (default True) -- if set to False, the input box is hidden
      and the console is just an output view.
    * ``hasmenu`` (default True) -- if set to False, the console does not
      provide its menu (containing actions for the output view such as Save
      or Print).
    * ``fulltime`` (default False) -- if set to True, the console shows the
      full (date + time) timestamp for every line, instead of only for errors
      and warnings.
    * ``watermark`` (default empty) -- the path to an image file that should
      be used as a watermark in the console window.
    """

    panelName = 'Console'
    ui = 'panels/console.ui'

    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)

    def on_outView_customContextMenuRequested(self, point):
        self.menu.popup(self.outView.mapToGlobal(point))

    def setExpertMode(self, expert):
        if not self.hasinput:
            self.inputFrame.setVisible(expert)

    def setViewOnly(self, viewonly):
        self.commandInput.setVisible(not viewonly)
        self.promptLabel.setVisible(not viewonly)

    def loadSettings(self, settings):
        self.cmdhistory = settings.value('cmdhistory') or []

    def saveSettings(self, settings):
        # only save 100 entries of the history
        cmdhistory = self.commandInput.history[-100:]
        settings.setValue('cmdhistory', cmdhistory)

    def getMenus(self):
        if self.hasmenu:
            return [self.menu]
        return []

    def setCustomStyle(self, font, back):
        self.commandInput.idle_color = back
        for widget in (self.outView, self.commandInput):
            widget.setFont(font)
            setBackgroundColor(widget, back)
        self.promptLabel.setFont(font)

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

    def completeInput(self, fullstring, lastword):
        try:
            return self.client.ask('complete', fullstring, lastword,
                                   default=[])
        except Exception:
            return []

    def on_client_connected(self):
        self.actionLabel.hide()
        self.outView._currentuser = self.client.login

    def on_client_mode(self, mode):
        self.promptLabel.setText(modePrompt(mode))

    def on_client_initstatus(self, state):
        self.on_client_mode(state['mode'])
        self.outView.clear()
        messages = self.client.ask('getmessages', '10000', default=[])
        total = len(messages) // 2500 + 1
        for _, batch in enumerateWithProgress(chunks(messages, 2500),
                                              text='Synchronizing...',
                                              parent=self, total=total):
            self.outView.addMessages(batch)
        self.outView.scrollToBottom()

    def on_client_message(self, message):
        self.outView.addMessage(message)

    def on_client_simmessage(self, simmessage):
        if simmessage[-1] == '0':
            self.outView.addMessage(simmessage)

    def on_client_experiment(self, data):
        (_, proptype) = data
        if proptype == 'user':
            # only clear history and output when switching TO a user experiment
            self.commandInput.history = []
            # clear everything except the last command with output
            self.outView.clearAlmostEverything()

    def on_outView_anchorClicked(self, url):
        """Called when the user clicks a link in the out view."""
        scheme = url.scheme()
        if scheme == 'exec':
            # Direct execution is too dangerous. Just insert it in the editor.
            if self.inputFrame.isVisible():
                self.commandInput.setText(url.path())
                self.commandInput.setFocus()
        elif scheme == 'edit':
            if self.mainwindow.editor_wintype is None:
                return
            win = self.mainwindow.createWindow(self.mainwindow.editor_wintype)
            panel = win.getPanel('User editor')
            panel.openFile(url.path())
            showPanel(panel)
        elif scheme == 'trace':
            TracebackDialog(self, self.outView, url.path()).show()
        else:
            self.log.warning('Strange anchor in outView: %s', url)

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        printer = QPrinter()
        printdlg = QPrintDialog(printer, self)
        printdlg.setOption(QAbstractPrintDialog.PrintSelection)
        if printdlg.exec_() == QDialog.Accepted:
            self.outView.print_(printer)

    @pyqtSlot()
    def on_actionSave_triggered(self):
        fn = QFileDialog.getSaveFileName(self, 'Save', '', 'All files (*.*)')[0]
        if not fn:
            return
        try:
            fn = fn.encode(sys.getfilesystemencoding())
            with open(fn, 'w') as f:
                f.write(self.outView.getOutputString())
        except Exception as err:
            QMessageBox.warning(self, 'Error', 'Writing file failed: %s' % err)

    @pyqtSlot()
    def on_actionCopy_triggered(self):
        self.outView.copy()

    @pyqtSlot()
    def on_actionGrep_triggered(self):
        self.grepPanel.setVisible(True)
        self.grepText.setFocus()

    @pyqtSlot()
    def on_grepClose_clicked(self):
        self.grepPanel.setVisible(False)
        self.commandInput.setFocus()
        self.outView.scrollToBottom()

    def on_grepText_returnPressed(self):
        self.on_grepSearch_clicked()

    def on_grepText_escapePressed(self):
        self.on_grepClose_clicked()

    @pyqtSlot()
    def on_grepSearch_clicked(self):
        st = self.grepText.text()
        if not st:
            return
        found = self.outView.findNext(st, self.grepRegex.isChecked())
        self.grepNoMatch.setVisible(not found)

    @pyqtSlot()
    def on_grepOccur_clicked(self):
        st = self.grepText.text()
        if not st:
            return
        self.outView.occur(st, self.grepRegex.isChecked())

    @pyqtSlot(bool)
    def on_actionAllowLineWrap_triggered(self, checked):
        self.mainwindow.allowoutputlinewrap = checked
        if self.mainwindow.allowoutputlinewrap:
            self.outView.setLineWrapMode(QTextEdit.WidgetWidth)
        else:
            self.outView.setLineWrapMode(QTextEdit.NoWrap)

    def on_commandInput_execRequested(self, script, action):
        if action == 'queue':
            self.client.run(script)
        else:
            self.client.tell('exec', script)
        self.commandInput.setText('')