예제 #1
0
파일: data.py 프로젝트: umithardal/nicos
class DataExportDialog(QFileDialog):
    def __init__(self, viewplot, curvenames, *args):
        QFileDialog.__init__(self, viewplot, *args)
        self.setOption(self.DontConfirmOverwrite, False)
        # allow adding some own widgets
        self.setOption(self.DontUseNativeDialog, True)
        self.setAcceptMode(QFileDialog.AcceptSave)
        layout = self.layout()
        layout.addWidget(QLabel('Curve:', self), 4, 0)
        self.curveCombo = QComboBox(self)
        if len(curvenames) > 1:
            self.curveCombo.addItem('all (in separate files)')
            self.curveCombo.addItem('all (in one file, multiple data columns)')
        self.curveCombo.addItems(curvenames)
        layout.addWidget(self.curveCombo, 4, 1)
        layout.addWidget(QLabel('Time format:', self), 5, 0)
        self.formatCombo = QComboBox(self)
        self.formatCombo.addItems([
            'Seconds since first datapoint', 'UNIX timestamp',
            'Text timestamp (YYYY-MM-dd.HH:MM:SS)'
        ])
        layout.addWidget(self.formatCombo, 5, 1)
예제 #2
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
예제 #3
0
class BaseHistoryWindow(object):

    client = None
    presetdict = None

    def __init__(self):
        loadUi(self, 'panels/history.ui')

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

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

        self.enablePlotActions(False)

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

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

        self.menus = None
        self.bar = None

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

    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]

    def getToolbars(self):
        if not self.bar:
            bar = QToolBar('History viewer')
            bar.addAction(self.actionNew)
            bar.addAction(self.actionEditView)
            bar.addSeparator()
            bar.addAction(self.actionSavePlot)
            bar.addAction(self.actionPrint)
            bar.addAction(self.actionSaveData)
            bar.addSeparator()
            bar.addAction(self.actionUnzoom)
            bar.addAction(self.actionLogScale)
            bar.addSeparator()
            bar.addAction(self.actionAutoScale)
            bar.addAction(self.actionScaleX)
            bar.addAction(self.actionScaleY)
            bar.addSeparator()
            bar.addAction(self.actionResetView)
            bar.addAction(self.actionDeleteView)
            bar.addSeparator()
            bar.addAction(self.actionFitPeak)
            wa = QWidgetAction(bar)
            self.fitPickCheckbox = QCheckBox(bar)
            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(bar)
            frame.setLayout(layout)
            wa.setDefaultWidget(frame)
            bar.addAction(wa)
            ag = QActionGroup(bar)
            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(bar)
            self.fitComboBox = QComboBox(bar)
            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)
            bar.addAction(wa)
            bar.addSeparator()
            bar.addAction(self.actionFitArby)
            self.bar = bar
            self.actionFitLinear.trigger()

        return [self.bar]

    def loadSettings(self, settings):
        self.splitterstate = settings.value('splitter', '', QByteArray)
        self.presetdict = {}
        # read new format if present
        settings.beginGroup('presets_new')
        for key in settings.childKeys():
            self.presetdict[key] = json.loads(settings.value(key))
        settings.endGroup()
        # convert old format
        try:
            presetval = settings.value('presets')
            if presetval:
                for (name, value) in presetval.items():
                    if not isinstance(value, bytes):
                        value = value.encode('latin1')
                    self.presetdict[name] = pickle.loads(value)
        except Exception:
            pass
        settings.remove('presets')
        self.last_views = []
        settings.beginGroup('views_new')
        for key in settings.childKeys():
            try:
                info = json.loads(settings.value(key))
                self.last_views.append((key, info))
            except Exception:
                pass
        settings.endGroup()

    def saveSettings(self, settings):
        settings.setValue('splitter', self.splitter.saveState())
        settings.beginGroup('presets_new')
        for (key, info) in self.presetdict.items():
            settings.setValue(key, json.dumps(info))
        settings.endGroup()
        settings.beginGroup('views_new')
        for view in self.views:
            settings.setValue(view.name, json.dumps(view.dlginfo))
        settings.endGroup()

    def openViews(self, views):
        """Open some views given by the specs in *views*, a list of strings.

        Each string can be a comma-separated list of key names, and an optional
        simple time spec (like "1h") separated by a colon.

        If a view spec matches the name of a preset, it is used instead.
        """
        for viewspec in views:
            timespec = '1h'
            if ':' in viewspec:
                viewspec, timespec = viewspec.rsplit(':', 1)
            info = dict(
                name=viewspec,
                devices=viewspec,
                simpleTime=True,
                simpleTimeSpec=timespec,
                slidingWindow=True,
                frombox=False,
                tobox=False,
                fromdate=0,
                todate=0,
                interval='',
                customY=False,
                customYFrom='',
                customYTo='',
            )
            self._createViewFromDialog(info)

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

    def _add_preset(self, name, info):
        if name:
            self.presetdict[name] = info.copy()
            self._refresh_presets()

    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.actionAttachElog,
                self.actionSaveData,
                self.actionAutoScale,
                self.actionScaleX,
                self.actionScaleY,
                self.actionEditView,
                self.actionCloseView,
                self.actionDeleteView,
                self.actionResetView,
                self.actionUnzoom,
                self.actionLogScale,
                self.actionLegend,
                self.actionSymbols,
                self.actionLines,
                self.actionFitPeak,
                self.actionFitArby,
        ]:
            action.setEnabled(on)

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

    def on__fitComboBox_currentIndexChanged(self, index):
        self.fitfuncmap[self.fitComboBox.currentText()].trigger()

    def on__viewList_currentItemChanged(self, item, previous):
        if item is None:
            return
        for view in self.views:
            if view.listitem == item:
                self.openView(view)

    def on__viewList_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
        self.on__viewList_currentItemChanged(item, None)
        # is it a "saved from last time" item?
        info = item.data(Qt.UserRole)
        if info is not None:
            row = self.viewList.row(item)

            do_restore = self.askQuestion('Restore this view from last time?')
            self.viewList.takeItem(row)
            if do_restore:
                self._createViewFromDialog(info, row)

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

    def newvalue_callback(self, data):
        (vtime, key, op, value) = data
        if key not in self.keyviews:
            return
        if not value:
            return
        value = cache_load(value)
        for view in self.keyviews[key]:
            view.newValue(vtime, key, op, value)

    def _createViewFromDialog(self, info, row=None):
        if not info['devices'].strip():
            return
        keys_indices = [
            extractKeyAndIndex(d.strip()) for d in info['devices'].split(',')
        ]
        if self.client is not None:
            meta = self._getMetainfo(keys_indices)
        else:
            meta = ({}, {})
        name = info['name']
        if not name:
            name = info['devices']
        if info['simpleTime']:
            name += ' (%s)' % info['simpleTimeSpec']
        window = None
        if info['simpleTime']:
            try:
                itime, _ = get_time_and_interval(info['simpleTimeSpec'])
            except ValueError:
                return
            fromtime = currenttime() - itime
            totime = None
            if info['slidingWindow']:
                window = itime
        else:
            if info['frombox']:
                fromtime = mktime(localtime(info['fromdate']))
            else:
                fromtime = None
            if info['tobox']:
                totime = mktime(localtime(info['todate']))
            else:
                totime = None
        try:
            interval = float(info['interval'])
        except ValueError:
            interval = 5.0
        if info['customY']:
            try:
                yfrom = float(info['customYFrom'])
            except ValueError:
                return
            try:
                yto = float(info['customYTo'])
            except ValueError:
                return
        else:
            yfrom = yto = None
        view = View(self, name, keys_indices, interval, fromtime, totime,
                    yfrom, yto, window, meta, info, self.gethistory_callback)
        self.views.append(view)
        view.listitem = QListWidgetItem(view.name)
        if row is not None:
            self.viewList.insertItem(row, view.listitem)
        else:
            self.viewList.addItem(view.listitem)
        self.openView(view)
        if view.totime is None:
            for key in view.uniq_keys:
                self.keyviews.setdefault(key, []).append(view)
        return view

    def _getMetainfo(self, keys_indices):
        """Collect unit and string<->integer mapping for each key that
        refers to a device main value.
        """
        units = {}
        mappings = {}
        seen = set()
        for key, _, _, _ in keys_indices:
            if key in seen or not key.endswith('/value'):
                continue
            seen.add(key)
            devname = key[:-6]
            devunit = self.client.getDeviceParam(devname, 'unit')
            if devunit:
                units[key] = devunit
            devmapping = self.client.getDeviceParam(devname, 'mapping')
            if devmapping:
                mappings[key] = m = {}
                i = 0
                for k, v in sorted(devmapping.items()):
                    if isinstance(v, integer_types):
                        m[k] = v
                    else:
                        m[k] = i
                        i += 1
        return units, mappings

    def on__actionNew_triggered(self):
        self.showNewDialog()

    def showNewDialog(self, devices=''):
        newdlg = NewViewDialog(self, client=self.client)
        newdlg.devices.setText(devices)
        ret = newdlg.exec_()
        if ret != QDialog.Accepted:
            return
        info = newdlg.infoDict()
        self._createViewFromDialog(info)
        if newdlg.savePreset.isChecked():
            self._add_preset(info['name'], info)

    def newView(self, devices):
        newdlg = NewViewDialog(self)
        newdlg.devices.setText(devices)
        info = newdlg.infoDict()
        self._createViewFromDialog(info)

    def openView(self, view):
        if not view.plot:
            view.plot = ViewPlot(self.plotFrame, self, view)
        self.viewList.setCurrentItem(view.listitem)
        self.setCurrentView(view)

    def setCurrentView(self, view):
        newView = False
        if self.currentPlot:
            self.plotLayout.removeWidget(self.currentPlot)
            self.currentPlot.hide()
        if view is None:
            self.currentPlot = None
            self.enablePlotActions(False)
        else:
            self.currentPlot = view.plot
            try:
                self.viewStack.remove(view)
            except ValueError:
                newView = True
            self.viewStack.append(view)

            self.enablePlotActions(True)
            self.enableAutoScaleActions(view.plot.HAS_AUTOSCALE)
            self.viewList.setCurrentItem(view.listitem)
            self.actionLogScale.setChecked(view.plot.isLogScaling())
            self.actionLegend.setChecked(view.plot.isLegendEnabled())
            self.actionSymbols.setChecked(view.plot.hasSymbols)
            self.actionLines.setChecked(view.plot.hasLines)
            self.plotLayout.addWidget(view.plot)
            if view.plot.HAS_AUTOSCALE:
                from gr.pygr import PlotAxes
                if newView:
                    mask = PlotAxes.SCALE_X | PlotAxes.SCALE_Y
                else:
                    mask = view.plot.plot.autoscale
                if view.yfrom and view.yto:
                    mask &= ~PlotAxes.SCALE_Y
                self._autoscale(x=mask & PlotAxes.SCALE_X,
                                y=mask & PlotAxes.SCALE_Y)
                view.plot.logYinDomain.connect(self.on_logYinDomain)
            view.plot.setSlidingWindow(view.window)
            view.plot.show()

    def on__viewList_itemDoubleClicked(self, item):
        if item:
            self.on__actionEditView_triggered()

    def on__actionEditView_triggered(self):
        view = self.viewStack[-1]
        newdlg = NewViewDialog(self, view.dlginfo, client=self.client)
        newdlg.setWindowTitle('Edit history view')
        ret = newdlg.exec_()
        if ret != QDialog.Accepted:
            return
        info = newdlg.infoDict()
        if newdlg.savePreset.isChecked():
            self._add_preset(info['name'], info)
        self.viewStack.pop()
        row = self.clearView(view)
        new_view = self._createViewFromDialog(info, row)
        if new_view.plot.HAS_AUTOSCALE:
            self._autoscale(True, False)

    def on__actionCloseView_triggered(self):
        view = self.viewStack.pop()
        if self.viewStack:
            self.setCurrentView(self.viewStack[-1])
        else:
            self.setCurrentView(None)
        view.plot = None

    def on__actionResetView_triggered(self):
        view = self.viewStack.pop()
        hassym = view.plot.hasSymbols
        view.plot = None
        self.openView(view)
        self.actionSymbols.setChecked(hassym)
        view.plot.setSymbols(hassym)

    def on__actionDeleteView_triggered(self):
        view = self.viewStack.pop()
        self.clearView(view)
        if self.viewStack:
            self.setCurrentView(self.viewStack[-1])
        else:
            self.setCurrentView(None)

    def clearView(self, view):
        self.views.remove(view)
        row = self.viewList.row(view.listitem)
        self.viewList.takeItem(row)
        if view.totime is None:
            for key in view.uniq_keys:
                self.keyviews[key].remove(view)
        view.cleanup()
        return row

    def on__actionSavePlot_triggered(self):
        filename = self.currentPlot.savePlot()
        if filename:
            self.statusBar.showMessage('View successfully saved to %s.' %
                                       filename)

    def on__actionPrint_triggered(self):
        if self.currentPlot.printPlot():
            self.statusBar.showMessage('View successfully printed.')

    def on__actionUnzoom_triggered(self):
        self.currentPlot.unzoom()

    def on__actionLogScale_toggled(self, on):
        self.currentPlot.setLogScale(on)

    def on__actionAutoScale_toggled(self, on):
        self._autoscale(on, on)

    def on__actionScaleX_toggled(self, on):
        self._autoscale(x=on)

    def on__actionScaleY_toggled(self, on):
        self._autoscale(y=on)

    def on__actionLegend_toggled(self, on):
        self.currentPlot.setLegend(on)

    def on__actionSymbols_toggled(self, on):
        self.currentPlot.setSymbols(on)

    def on__actionLines_toggled(self, on):
        self.currentPlot.setLines(on)

    def on__actionSaveData_triggered(self):
        self.currentPlot.saveData()

    def on__actionFitPeak_triggered(self):
        self.currentPlot.beginFit(self.fitclass, self.actionFitPeak,
                                  self.fitPickCheckbox.isChecked())

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

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

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

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

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

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

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

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

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

    def on__actionFitArby_triggered(self):
        # no second argument: the "arbitrary" action is not checkable
        self.currentPlot.beginFit(ArbitraryFitter, None,
                                  self.fitPickCheckbox.isChecked())
예제 #4
0
class ScansPanel(DefaultScansPanel):
    def __init__(self, parent, client, options):
        DefaultScansPanel.__init__(self, parent, client, options)
        self.bars = self.createPanelToolbar()
        for index, bar in enumerate(self.bars):
            self.layout().insertWidget(index, bar)
        self.set_icons()

    def set_icons(self):
        self.actionPrint.setIcon(get_icon('print-24px.svg'))
        self.actionSavePlot.setIcon(get_icon('save-24px.svg'))
        self.actionUnzoom.setIcon(get_icon('zoom_out-24px.svg'))
        self.actionClose.setIcon(get_icon('zoom_out-24px.svg'))

    def createPanelToolbar(self):
        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)

        bars = [bar, fitbar]

        return bars

    def getToolbars(self):
        return []
예제 #5
0
class HistoryPanel(DefaultHistoryPanel):
    def __init__(self, parent, client, options):
        DefaultHistoryPanel.__init__(self, parent, client, options)
        self.layout().setMenuBar(self.setPanelToolbar())
        self.set_icons()

    def setPanelToolbar(self):
        bar = QToolBar('History viewer')
        bar.addAction(self.actionNew)
        bar.addAction(self.actionEditView)
        bar.addSeparator()
        bar.addAction(self.actionSavePlot)
        bar.addAction(self.actionPrint)
        bar.addAction(self.actionSaveData)
        bar.addSeparator()
        bar.addAction(self.actionUnzoom)
        bar.addAction(self.actionLogScale)
        bar.addSeparator()
        bar.addAction(self.actionAutoScale)
        bar.addAction(self.actionScaleX)
        bar.addAction(self.actionScaleY)
        bar.addSeparator()
        bar.addAction(self.actionResetView)
        bar.addAction(self.actionDeleteView)
        bar.addSeparator()
        bar.addAction(self.actionFitPeak)
        wa = QWidgetAction(bar)
        self.fitPickCheckbox = QCheckBox(bar)
        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(bar)
        frame.setLayout(layout)
        wa.setDefaultWidget(frame)
        bar.addAction(wa)
        ag = QActionGroup(bar)
        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(bar)
        self.fitComboBox = QComboBox(bar)
        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)
        bar.addAction(wa)
        bar.addSeparator()
        bar.addAction(self.actionFitArby)
        self.bar = bar
        self.actionFitLinear.trigger()
        return bar

    def set_icons(self):
        self.actionNew.setIcon(get_icon('add_circle_outline-24px.svg'))
        self.actionEditView.setIcon(get_icon('edit-24px.svg'))
        self.actionSavePlot.setIcon(get_icon('save-24px.svg'))
        self.actionPrint.setIcon(get_icon('print-24px.svg'))
        self.actionUnzoom.setIcon(get_icon('zoom_out-24px.svg'))
        self.actionSaveData.setIcon(get_icon('archive-24px.svg'))

    def getToolbars(self):
        return []