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
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
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)
def onExpConfChanged(self, expconf): DynamicPlotManager.onExpConfChanged(self, expconf) activeMntGrp = expconf['ActiveMntGrp'] msg = 'Plotting scans for "%s" Measurement Group' % activeMntGrp self.parent().newShortMessage.emit(msg)
def __init__(self, parent): DynamicPlotManager.__init__(self, parent)
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
def __init__(self, parent): DynamicPlotManager.__init__(self, parent=parent) Qt.qApp.SDM.connectWriter("shortMessage", self, 'newShortMessage')