Beispiel #1
0
 def onPlotsButtonToggled(self, checked):
     if checked:
         from taurus.qt.qtgui.taurusgui.macrolistener import \
              DynamicPlotManager
         self.__plotManager = DynamicPlotManager(self)
         self.__plotManager.setModel(self.getModelName())
         self.connect(self, Qt.SIGNAL('experimentConfigurationChanged'), 
                      self.__plotManager.onExpConfChanged)
     else:
         self.disconnect(self, Qt.SIGNAL('experimentConfigurationChanged'), 
                         self.__plotManager.onExpConfChanged)
         self.__plotManager.removePanels()
         self.__plotManager.setModel(None)
         self.__plotManager = None
Beispiel #2
0
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.
    '''
    def __init__(self, parent=None, door=None, plotsButton=True):
        Qt.QWidget.__init__(self, parent)
        TaurusBaseWidget.__init__(self, 'ExpDescriptionEditor')
        self.loadUi()
        self.ui.buttonBox.setStandardButtons(Qt.QDialogButtonBox.Reset | Qt.QDialogButtonBox.Apply)
        newperspectivesDict = copy.deepcopy(self.ui.sardanaElementTree.KnownPerspectives)
        #newperspectivesDict[self.ui.sardanaElementTree.DftPerspective]['model'] = [SardanaAcquirableProxyModel, SardanaElementPlainModel]
        newperspectivesDict[self.ui.sardanaElementTree.DftPerspective]['model'][0] = SardanaAcquirableProxyModel
        self.ui.sardanaElementTree.KnownPerspectives = newperspectivesDict  #assign a copy because if just a key of this class memberwas modified, all instances of this class would be affected
        self.ui.sardanaElementTree._setPerspective(self.ui.sardanaElementTree.DftPerspective)

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

        self.connect(self.ui.activeMntGrpCB, Qt.SIGNAL('activated (QString)'), self.changeActiveMntGrp)
        self.connect(self.ui.createMntGrpBT, Qt.SIGNAL('clicked ()'), self.createMntGrp)
        self.connect(self.ui.deleteMntGrpBT, Qt.SIGNAL('clicked ()'), self.deleteMntGrp)
        self.connect(self.ui.compressionCB, Qt.SIGNAL('currentIndexChanged (int)'), self.onCompressionCBChanged)
        self.connect(self.ui.pathLE, Qt.SIGNAL('textEdited (QString)'), self.onPathLEEdited)
        self.connect(self.ui.filenameLE, Qt.SIGNAL('textEdited (QString)'), self.onFilenameLEEdited)
        self.connect(self.ui.channelEditor.getQModel(), Qt.SIGNAL('dataChanged (QModelIndex, QModelIndex)'), self._updateButtonBox)
        self.connect(self.ui.channelEditor.getQModel(), Qt.SIGNAL('modelReset ()'), self._updateButtonBox)
        preScanList = self.ui.preScanList
        self.connect(preScanList, Qt.SIGNAL('dataChanged'),
                     self.onPreScanSnapshotChanged)
        #TODO: For Taurus 4 compatibility
        if hasattr(preScanList, "dataChangedSignal"):
            preScanList.dataChangedSignal.connect(self.onPreScanSnapshotChanged)
        self.connect(self.ui.choosePathBT, Qt.SIGNAL('clicked ()'), self.onChooseScanDirButtonClicked)
        
        self.__plotManager = None
        icon = resource.getIcon(":/actions/view.svg")
        self.togglePlotsAction = Qt.QAction(icon, "Show/Hide plots", self)
        self.togglePlotsAction.setCheckable(True)
        self.togglePlotsAction.setChecked(False)
        self.togglePlotsAction.setEnabled(plotsButton)
        self.addAction(self.togglePlotsAction)
        self.connect(self.togglePlotsAction, Qt.SIGNAL("toggled(bool)"), 
                     self.onPlotsButtonToggled)
        self.ui.plotsButton.setDefaultAction(self.togglePlotsAction)
        
        if door is not None:
            self.setModel(door)
        self.connect(self.ui.buttonBox, Qt.SIGNAL("clicked(QAbstractButton *)"), self.onDialogButtonClicked)

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

    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.emit(Qt.SIGNAL('textEdited (QString)'), 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
        tghost = taurus.Database().getNormalName()  #@todo: get the tghost from the door model instead
        msname = door.macro_server.getFullName()
        self.ui.taurusModelTree.setModel(tghost)
        self.ui.sardanaElementTree.setModel(msname)

    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)

    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()
        self.ui.activeMntGrpCB.addItems(sorted(self._localConfig['MntGrpConfigs'].keys()))
        idx = self.ui.activeMntGrpCB.findText(activeMntGrpName)
        self.ui.activeMntGrpCB.setCurrentIndex(idx)

        #set the system snapshot list
        psl = self._localConfig.get('PreScanSnapshot')  #I get it before clearing because clear() changes the _localConfig
        # TODO: For Taurus 4 compatibility
        psl_fullname = []
        for name, display in psl:
            psl_fullname.append(("tango://%s" % 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'):
                Qt.QMessageBox.information(self, "Empty Measurement group",
                "The measurement group '%s' is empty. Fill it (or delete it) before applying" % mgname,
                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.emit(Qt.SIGNAL('experimentConfigurationChanged'), copy.deepcopy(conf))
        return True

    def changeActiveMntGrp(self, activeMntGrpName):
        activeMntGrpName = str(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, Qt.Qt.MatchExactly)
        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)

    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
            # TODO: For Taurus 4 compatibility
            preScanList.append((full_name.lstrip("tango://"), display))
        self._localConfig['PreScanSnapshot'] = preScanList
        self._setDirty(True)
        
    def onPlotsButtonToggled(self, checked):
        if checked:
            from taurus.qt.qtgui.taurusgui.macrolistener import \
                 DynamicPlotManager
            self.__plotManager = DynamicPlotManager(self)
            self.__plotManager.setModel(self.getModelName())
            self.connect(self, Qt.SIGNAL('experimentConfigurationChanged'), 
                         self.__plotManager.onExpConfChanged)
        else:
            self.disconnect(self, Qt.SIGNAL('experimentConfigurationChanged'), 
                            self.__plotManager.onExpConfChanged)
            self.__plotManager.removePanels()
            self.__plotManager.setModel(None)
            self.__plotManager = None