コード例 #1
0
ファイル: expdescription.py プロジェクト: tacaswell/sardana
 def onPlotsButtonToggled(self, checked):
     if checked:
         from sardana.taurus.qt.qtgui.macrolistener import \
             DynamicPlotManager
         self.__plotManager = DynamicPlotManager(self)
         self.__plotManager.setModel(self.getModelName())
     else:
         self.__plotManager.removePanels()
         self.__plotManager.setModel(None)
         self.__plotManager = None
コード例 #2
0
ファイル: expdescription.py プロジェクト: suyzhu/sardana
 def onPlotsButtonToggled(self, checked):
     if checked:
         from sardana.taurus.qt.qtgui.macrolistener import \
             DynamicPlotManager
         self.__plotManager = DynamicPlotManager(self)
         self.__plotManager.setModel(self.getModelName())
         self.experimentConfigurationChanged.connect(
             self.__plotManager.onExpConfChanged)
     else:
         self.experimentConfigurationChanged.disconnect(
             self.__plotManager.onExpConfChanged)
         self.__plotManager.removePanels()
         self.__plotManager.setModel(None)
         self.__plotManager = None
コード例 #3
0
 def removeTemporaryPanels(self, names=None):
     '''Remove temporary panels managed by this widget'''
     # for now, the only temporary panels are the plots
     DynamicPlotManager.removePanels(self, names=names)
コード例 #4
0
 def onExpConfChanged(self, expconf):
     DynamicPlotManager.onExpConfChanged(self, expconf)
     activeMntGrp = expconf['ActiveMntGrp']
     msg = 'Plotting scans for "%s" Measurement Group' % activeMntGrp
     self.parent().newShortMessage.emit(msg)
コード例 #5
0
 def __init__(self, parent):
     DynamicPlotManager.__init__(self, parent)
コード例 #6
0
ファイル: expdescription.py プロジェクト: suyzhu/sardana
class ExpDescriptionEditor(Qt.QWidget, TaurusBaseWidget):
    '''
    A widget for editing the configuration of a experiment (measurement groups,
    plot and storage parameters, etc).

    It receives a Sardana Door name as its model and gets/sets the configuration
    using the `ExperimentConfiguration` environmental variable for that Door.
    '''

    createExpConfChangedDialog = Qt.pyqtSignal()
    experimentConfigurationChanged = Qt.pyqtSignal(object)

    def __init__(self, parent=None, door=None, plotsButton=True,
                 autoUpdate=False):
        Qt.QWidget.__init__(self, parent)
        TaurusBaseWidget.__init__(self, 'ExpDescriptionEditor')
        self.loadUi()
        self.ui.buttonBox.setStandardButtons(
            Qt.QDialogButtonBox.Reset | Qt.QDialogButtonBox.Apply)
        self.ui.buttonBox.button(Qt.QDialogButtonBox.Reset).setText('Reload')

        newperspectivesDict = copy.deepcopy(
            self.ui.sardanaElementTree.KnownPerspectives)
        #newperspectivesDict[self.ui.sardanaElementTree.DftPerspective]['model'] = [SardanaAcquirableProxyModel, SardanaElementPlainModel]
        newperspectivesDict[self.ui.sardanaElementTree.DftPerspective][
            'model'][0] = SardanaAcquirableProxyModel
        # assign a copy because if just a key of this class memberwas modified,
        # all instances of this class would be affected
        self.ui.sardanaElementTree.KnownPerspectives = newperspectivesDict
        self.ui.sardanaElementTree._setPerspective(
            self.ui.sardanaElementTree.DftPerspective)

        self._localConfig = None
        self._originalConfiguration = None
        self._dirty = False
        self._dirtyMntGrps = set()

        self._autoUpdate = False
        self._warningWidget = None
        self.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)
        self._autoUpdateAction = Qt.QAction("Auto update", self)
        self._autoUpdateAction.setCheckable(True)
        self._autoUpdateAction.toggled.connect(self.setAutoUpdate)
        self.addAction(self._autoUpdateAction)
        self._autoUpdateAction.setChecked(autoUpdate)
        self.registerConfigProperty(
            self._autoUpdateAction.isChecked,
            self._autoUpdateAction.setChecked,
            "autoUpdate")

        # Pending event variables
        self._expConfChangedDialog = None

        self.createExpConfChangedDialog.connect(
            self._createExpConfChangedDialog)
        self.ui.activeMntGrpCB.activated['QString'].connect(
            self.changeActiveMntGrp)
        self.ui.createMntGrpBT.clicked.connect(
            self.createMntGrp)
        self.ui.deleteMntGrpBT.clicked.connect(
            self.deleteMntGrp)
        self.ui.compressionCB.currentIndexChanged['int'].connect(
            self.onCompressionCBChanged)
        self.ui.pathLE.textEdited.connect(
            self.onPathLEEdited)
        self.ui.filenameLE.textEdited.connect(
            self.onFilenameLEEdited)
        self.ui.channelEditor.getQModel().dataChanged.connect(
            self._updateButtonBox)
        self.ui.channelEditor.getQModel().modelReset.connect(
            self._updateButtonBox)
        preScanList = self.ui.preScanList
        preScanList.dataChangedSignal.connect(self.onPreScanSnapshotChanged)
        self.ui.choosePathBT.clicked.connect(
            self.onChooseScanDirButtonClicked)

        self.__plotManager = None
        tooltip = None

        # TODO: Disable show scan button since scan plot have to be
        # adapted to support QT5
        # --------------------------------------------------------------------
        from taurus.external.qt import PYQT4, API
        if not PYQT4:
            self.debug('Show plots is only supported with PyQt4 for now')
            plotsButton = False
            tooltip = "Show/Hide plots is not ready for %s" % API
        # --------------------------------------------------------------------

        icon = resource.getIcon(":/actions/view.svg")
        measGrpTab = self.ui.tabWidget.widget(0)
        self.togglePlotsAction = Qt.QAction(icon, "Show/Hide plots", self)
        if tooltip is not None:
            self.togglePlotsAction.setToolTip(tooltip)
        self.togglePlotsAction.setCheckable(True)
        self.togglePlotsAction.setChecked(False)
        self.togglePlotsAction.setEnabled(plotsButton)
        measGrpTab.addAction(self.togglePlotsAction)
        measGrpTab.setContextMenuPolicy(Qt.Qt.ActionsContextMenu)
        self.togglePlotsAction.toggled.connect(self.onPlotsButtonToggled)
        self.ui.plotsButton.setDefaultAction(self.togglePlotsAction)

        if door is not None:
            self.setModel(door)

        self.ui.buttonBox.clicked.connect(self.onDialogButtonClicked)

        # Taurus Configuration properties and delegates
        self.registerConfigDelegate(self.ui.channelEditor)

    def setAutoUpdate(self, auto_update):
        if auto_update and not self._autoUpdate:
            self._warningWidget = self._getWarningWidget()
            self.ui.verticalLayout_3.insertWidget(0, self._warningWidget)
        if not auto_update and self._autoUpdate:
            self.ui.verticalLayout_3.removeWidget(self._warningWidget)
            self._warningWidget.deleteLater()
            self._warningWidget = None
        self._autoUpdate = auto_update

    def _getWarningWidget(self):
        w = Qt.QWidget()
        layout = QtGui.QHBoxLayout()
        w.setLayout(layout)
        icon = QtGui.QIcon.fromTheme('dialog-warning')
        pixmap = QtGui.QPixmap(icon.pixmap(QtCore.QSize(32, 32)))
        label_icon = QtGui.QLabel()
        label_icon.setPixmap(pixmap)
        label = QtGui.QLabel('This experiment configuration dialog '
                             'updates automatically on external changes!')
        layout.addWidget(label_icon)
        layout.addWidget(label)
        layout.addStretch(1)
        return w

    def _getResumeText(self):
        msg_resume = '<p> Summary of changes: <ul>'
        mnt_grps = ''
        envs = ''
        for key in self._diff:
            if key == 'MntGrpConfigs':
                for names in self._diff['MntGrpConfigs']:
                    if mnt_grps != '':
                        mnt_grps += ', '
                    mnt_grps += '<b>{0}</b>'.format(names)
            else:
                if envs != '':
                    envs += ', '
                envs += '<b>{0}</b>'.format(key)
        values = ''
        if mnt_grps != '':
            values += '<li> Measurement Groups: {0}</li>'.format(mnt_grps)
        if envs != '':
            values += '<li> Enviroment variables: {0}</li>'.format(envs)

        msg_resume += values
        msg_resume += ' </ul> </p>'
        return msg_resume

    def _getDetialsText(self):
        msg_detials = 'Changes {key: [external, local], ...}\n'
        msg_detials += json.dumps(self._diff, sort_keys=True)
        return msg_detials

    def _createExpConfChangedDialog(self):
        msg_details = self._getDetialsText()
        msg_info = self._getResumeText()
        self._expConfChangedDialog = Qt.QMessageBox()
        self._expConfChangedDialog.setIcon(Qt.QMessageBox.Warning)
        self._expConfChangedDialog.setWindowTitle('External Changes')
        # text = '''
        # <p align='justify'>
        # The experiment configuration has been modified externally.<br/>
        # You can either:<br/> <l1><b>Load</b> the new configuration from the
        # door
        # (discarding local changes) or <b>Keep</b> your local configuration
        # (would eventually overwrite the external changes when applying).
        # </p>'''
        text = '''
        <p>The experiment configuration has been modified externally.
        You can either:
        <ul>
        <li><strong>Load </strong>the new configuration from the door
        (discarding local changes)</li>
        <li><strong>Keep </strong>your local configuration (would eventually
        overwrite the external changes when applying)</li>
        </ul></p>
        '''
        self._expConfChangedDialog.setText(text)
        self._expConfChangedDialog.setTextFormat(QtCore.Qt.RichText)
        self._expConfChangedDialog.setInformativeText(msg_info)
        self._expConfChangedDialog.setDetailedText(msg_details)
        self._expConfChangedDialog.setStandardButtons(Qt.QMessageBox.Ok |
                                                      Qt.QMessageBox.Cancel)
        btn_ok = self._expConfChangedDialog.button(Qt.QMessageBox.Ok)
        btn_ok.setText('Load')
        btn_cancel = self._expConfChangedDialog.button(Qt.QMessageBox.Cancel)
        btn_cancel.setText('Keep')
        result = self._expConfChangedDialog.exec_()
        self._expConfChangedDialog = None
        if result == Qt.QMessageBox.Ok:
            self._reloadConf(force=True)

    @QtCore.pyqtSlot()
    def _experimentConfigurationChanged(self):
        self._diff = ''
        try:
            self._diff = self._getDiff()
        except Exception as e:
            raise RuntimeError('Error on processing! {0}'.format(e))

        if len(self._diff) > 0:
            if self._autoUpdate:
                self._reloadConf(force=True)
            else:
                if self._expConfChangedDialog is None:
                    if hasattr(self, 'createExpConfChangedDialog'):
                        self.createExpConfChangedDialog.emit()
                else:
                    msg_details = self._getDetialsText()
                    msg_info = self._getResumeText()
                    self._expConfChangedDialog.setInformativeText(msg_info)
                    self._expConfChangedDialog.setDetailedText(msg_details)

    def _getDiff(self):
        door = self.getModelObj()
        if door is None:
            return []

        new_conf = door.getExperimentConfiguration()
        old_conf = self._localConfig
        return find_diff(new_conf, old_conf)

    def getModelClass(self):
        '''reimplemented from :class:`TaurusBaseWidget`'''
        return taurus.core.taurusdevice.TaurusDevice

    def onChooseScanDirButtonClicked(self):
        ret = Qt.QFileDialog.getExistingDirectory(
            self, 'Choose directory for saving files', self.ui.pathLE.text())
        if ret:
            self.ui.pathLE.setText(ret)
            self.ui.pathLE.textEdited.emit(ret)

    def onDialogButtonClicked(self, button):
        role = self.ui.buttonBox.buttonRole(button)
        if role == Qt.QDialogButtonBox.ApplyRole:
            self.writeExperimentConfiguration(ask=False)
        elif role == Qt.QDialogButtonBox.ResetRole:
            self._reloadConf()

    def closeEvent(self, event):
        '''This event handler receives widget close events'''
        if self.isDataChanged():
            self.writeExperimentConfiguration(ask=True)
        Qt.QWidget.closeEvent(self, event)

    def setModel(self, model):
        '''reimplemented from :class:`TaurusBaseWidget`'''
        TaurusBaseWidget.setModel(self, model)
        self._reloadConf(force=True)
        # set the model of some child widgets
        door = self.getModelObj()
        if door is None:
            return
        # @todo: get the tghost from the door model instead
        tghost = taurus.Database().getNormalName()
        msname = door.macro_server.getFullName()
        self.ui.taurusModelTree.setModel(tghost)
        self.ui.sardanaElementTree.setModel(msname)
        door.experimentConfigurationChanged.connect(
            self._experimentConfigurationChanged)

    def _reloadConf(self, force=False):
        if not force and self.isDataChanged():
            op = Qt.QMessageBox.question(self, "Reload info from door",
                                         "If you reload, all current experiment configuration changes will be lost. Reload?",
                                         Qt.QMessageBox.Yes | Qt.QMessageBox.Cancel)
            if op != Qt.QMessageBox.Yes:
                return
        door = self.getModelObj()
        if door is None:
            return
        conf = door.getExperimentConfiguration()
        self._originalConfiguration = copy.deepcopy(conf)
        self.setLocalConfig(conf)
        self._setDirty(False)
        self._dirtyMntGrps = set()
        # set a list of available channels
        avail_channels = {}
        for ch_info in door.macro_server.getExpChannelElements().values():
            avail_channels[ch_info.full_name] = ch_info.getData()
        self.ui.channelEditor.getQModel().setAvailableChannels(avail_channels)
        # set a list of available triggers
        avail_triggers = {'software': {"name": "software"}}
        tg_elements = door.macro_server.getElementsOfType('TriggerGate')
        for tg_info in tg_elements.values():
            avail_triggers[tg_info.full_name] = tg_info.getData()
        self.ui.channelEditor.getQModel().setAvailableTriggers(avail_triggers)
        self.experimentConfigurationChanged.emit(copy.deepcopy(conf))


    def _setDirty(self, dirty):
        self._dirty = dirty
        self._updateButtonBox()

    def isDataChanged(self):
        """Tells if the local data has been modified since it was last refreshed

        :return: (bool) True if he local data has been modified since it was last refreshed
        """
        return bool(self._dirty or self.ui.channelEditor.getQModel().isDataChanged() or self._dirtyMntGrps)

    def _updateButtonBox(self, *args, **kwargs):
        self.ui.buttonBox.setEnabled(self.isDataChanged())

    def getLocalConfig(self):
        return self._localConfig

    def setLocalConfig(self, conf):
        '''gets a ExpDescription dictionary and sets up the widget'''

        self._localConfig = conf

        # set the Channel Editor
        activeMntGrpName = self._localConfig['ActiveMntGrp'] or ''
        if activeMntGrpName in self._localConfig['MntGrpConfigs']:
            mgconfig = self._localConfig['MntGrpConfigs'][activeMntGrpName]
            self.ui.channelEditor.getQModel().setDataSource(mgconfig)

        # set the measurement group ComboBox
        self.ui.activeMntGrpCB.clear()
        mntGrpLabels = []
        for _, mntGrpConf in self._localConfig['MntGrpConfigs'].items():
            # get labels to visualize names with lower and upper case
            mntGrpLabels.append(mntGrpConf['label'])
        self.ui.activeMntGrpCB.addItems(sorted(mntGrpLabels))
        idx = self.ui.activeMntGrpCB.findText(activeMntGrpName,
                                              # case insensitive find
                                              Qt.Qt.MatchFixedString)
        self.ui.activeMntGrpCB.setCurrentIndex(idx)

        # set the system snapshot list
        # I get it before clearing because clear() changes the _localConfig
        psl = self._localConfig.get('PreScanSnapshot')
        # TODO: For Taurus 4 compatibility
        psl_fullname = []
        for name, display in psl:
            name = _to_fqdn(name, self)
            psl_fullname.append((name, display))

        self.ui.preScanList.clear()
        self.ui.preScanList.addModels(psl_fullname)

        # other settings
        self.ui.filenameLE.setText(", ".join(self._localConfig['ScanFile']))
        self.ui.pathLE.setText(self._localConfig['ScanDir'] or '')
        self.ui.compressionCB.setCurrentIndex(
            self._localConfig['DataCompressionRank'] + 1)

    def writeExperimentConfiguration(self, ask=True):
        '''sends the current local configuration to the door

        :param ask: (bool) If True (default) prompts the user before saving.
        '''

        if ask:
            op = Qt.QMessageBox.question(self, "Save configuration?",
                                         'Do you want to save the current configuration?\n(if not, any changes will be lost)',
                                         Qt.QMessageBox.Yes | Qt.QMessageBox.No)
            if op != Qt.QMessageBox.Yes:
                return False

        conf = self.getLocalConfig()

        # make sure that no empty measurement groups are written
        for mgname, mgconfig in conf.get('MntGrpConfigs', {}).items():
            if mgconfig is not None and not mgconfig.get('controllers'):
                mglabel = mgconfig['label']
                Qt.QMessageBox.information(self, "Empty Measurement group",
                                           "The measurement group '%s' is empty. Fill it (or delete it) before applying" % mglabel,
                                           Qt.QMessageBox.Ok)
                self.changeActiveMntGrp(mgname)
                return False

        # check if the currently displayed mntgrp is changed
        if self.ui.channelEditor.getQModel().isDataChanged():
            self._dirtyMntGrps.add(self._localConfig['ActiveMntGrp'])

        door = self.getModelObj()
        door.setExperimentConfiguration(conf, mnt_grps=self._dirtyMntGrps)
        self._originalConfiguration = copy.deepcopy(conf)
        self._dirtyMntGrps = set()
        self.ui.channelEditor.getQModel().setDataChanged(False)
        self._setDirty(False)
        self.experimentConfigurationChanged.emit(copy.deepcopy(conf))
        return True

    @Qt.pyqtSlot('QString')
    def changeActiveMntGrp(self, activeMntGrpName):
        if self._localConfig is None:
            return
        if activeMntGrpName == self._localConfig['ActiveMntGrp']:
            return  # nothing changed
        if activeMntGrpName not in self._localConfig['MntGrpConfigs']:
            raise KeyError('Unknown measurement group "%s"' % activeMntGrpName)

        # add the previous measurement group to the list of "dirty" groups if
        # something was changed
        if self.ui.channelEditor.getQModel().isDataChanged():
            self._dirtyMntGrps.add(self._localConfig['ActiveMntGrp'])

        self._localConfig['ActiveMntGrp'] = activeMntGrpName

        i = self.ui.activeMntGrpCB.findText(activeMntGrpName,
                                            # case insensitive find
                                            Qt.Qt.MatchFixedString)
        self.ui.activeMntGrpCB.setCurrentIndex(i)
        mgconfig = self._localConfig['MntGrpConfigs'][activeMntGrpName]
        self.ui.channelEditor.getQModel().setDataSource(mgconfig)
        self._setDirty(True)

    def createMntGrp(self):
        '''creates a new Measurement Group'''

        if self._localConfig is None:
            return

        mntGrpName, ok = Qt.QInputDialog.getText(self, "New Measurement Group",
                                                 "Enter a name for the new measurement Group")
        if not ok:
            return
        mntGrpName = str(mntGrpName)

        # check that the given name is not an existing pool element
        ms = self.getModelObj().macro_server
        poolElementNames = [
            v.name for v in ms.getElementsWithInterface("PoolElement").values()]
        while mntGrpName in poolElementNames:
            Qt.QMessageBox.warning(self, "Cannot create Measurement group",
                                   "The name '%s' already is used for another pool element. Please Choose a different one." % mntGrpName,
                                   Qt.QMessageBox.Ok)
            mntGrpName, ok = Qt.QInputDialog.getText(self, "New Measurement Group",
                                                     "Enter a name for the new measurement Group",
                                                     Qt.QLineEdit.Normal,
                                                     mntGrpName)
            if not ok:
                return
            mntGrpName = str(mntGrpName)

        # check that the measurement group is not already in the localConfig
        if mntGrpName in self._localConfig['MntGrpConfigs']:
            Qt.QMessageBox.warning(self, "%s already exists" % mntGrpName,
                                   'A measurement group named "%s" already exists. A new one will not be created' % mntGrpName)
            return

        # add an empty configuration dictionary to the local config
        mgconfig = {'label': mntGrpName, 'controllers': {}}
        self._localConfig['MntGrpConfigs'][mntGrpName] = mgconfig
        # add the new measurement group to the list of "dirty" groups
        self._dirtyMntGrps.add(mntGrpName)
        # add the name to the combobox
        self.ui.activeMntGrpCB.addItem(mntGrpName)
        # make it the Active MntGrp
        self.changeActiveMntGrp(mntGrpName)

    def deleteMntGrp(self):
        '''creates a new Measurement Group'''
        activeMntGrpName = str(self.ui.activeMntGrpCB.currentText())
        op = Qt.QMessageBox.question(self, "Delete Measurement Group",
                                     "Remove the measurement group '%s'?" % activeMntGrpName,
                                     Qt.QMessageBox.Yes | Qt.QMessageBox.Cancel)
        if op != Qt.QMessageBox.Yes:
            return
        currentIndex = self.ui.activeMntGrpCB.currentIndex()
        if self._localConfig is None:
            return
        if activeMntGrpName not in self._localConfig['MntGrpConfigs']:
            raise KeyError('Unknown measurement group "%s"' % activeMntGrpName)

        # add the current measurement group to the list of "dirty" groups
        self._dirtyMntGrps.add(activeMntGrpName)

        self._localConfig['MntGrpConfigs'][activeMntGrpName] = None
        self.ui.activeMntGrpCB.setCurrentIndex(-1)
        self.ui.activeMntGrpCB.removeItem(currentIndex)
        self.ui.channelEditor.getQModel().setDataSource({})
        self._setDirty(True)

    @Qt.pyqtSlot('int')
    def onCompressionCBChanged(self, idx):
        if self._localConfig is None:
            return
        self._localConfig['DataCompressionRank'] = idx - 1
        self._setDirty(True)

    def onPathLEEdited(self, text):
        self._localConfig['ScanDir'] = str(text)
        self._setDirty(True)

    def onFilenameLEEdited(self, text):
        self._localConfig['ScanFile'] = [v.strip()
                                         for v in str(text).split(',')]
        self._setDirty(True)

    def onPreScanSnapshotChanged(self, items):
        door = self.getModelObj()
        ms = door.macro_server
        preScanList = []
        for e in items:
            nfo = ms.getElementInfo(e.src)
            if nfo is None:
                full_name = e.src
                display = e.display
            else:
                full_name = nfo.full_name
                display = nfo.name
            preScanList.append((full_name, display))
        self._localConfig['PreScanSnapshot'] = preScanList
        self._setDirty(True)

    def onPlotsButtonToggled(self, checked):
        if checked:
            from sardana.taurus.qt.qtgui.macrolistener import \
                DynamicPlotManager
            self.__plotManager = DynamicPlotManager(self)
            self.__plotManager.setModel(self.getModelName())
            self.experimentConfigurationChanged.connect(
                self.__plotManager.onExpConfChanged)
        else:
            self.experimentConfigurationChanged.disconnect(
                self.__plotManager.onExpConfChanged)
            self.__plotManager.removePanels()
            self.__plotManager.setModel(None)
            self.__plotManager = None
コード例 #7
0
ファイル: showscanonline.py プロジェクト: sardana-org/sardana
 def __init__(self, parent):
     DynamicPlotManager.__init__(self, parent=parent)
     Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage')