def updatepppDisplay(self): for pppTab in list(self.pppCodeEdits.values()): self.sourceTabs.removeTab( self.sourceTabs.indexOf(pppTab) ) self.pppCodeEdits = dict() if self.currentContext.pulseProgramMode == 'ppp': for name, text in [(self.pppSourceFile, self.pppSource)]: textEdit = PulseProgramSourceEdit(mode='ppp') encodingStrings = [encoding for encoding in EncodingDict.keys() if type(encoding) == str] textEdit.setupUi(textEdit, extraKeywords1=self.definitionWords+encodingStrings, extraKeywords2=self.builtinWords) textEdit.setPlainText(text) self.pppCodeEdits[name] = textEdit self.sourceTabs.addTab( textEdit, name )
def updateppDisplay(self): for pppTab in list(self.sourceCodeEdits.values()): self.sourceTabs.removeTab(self.sourceTabs.indexOf(pppTab)) self.sourceCodeEdits = dict() for name, text in self.pulseProgram.source.items(): textEdit = PulseProgramSourceEdit() textEdit.setupUi(textEdit, extraKeywords1=self.definitionWords, extraKeywords2=[key for key in OPS]) textEdit.setPlainText(text) self.sourceCodeEdits[name] = textEdit self.sourceTabs.addTab(textEdit, name) textEdit.setReadOnly(self.currentContext.pulseProgramMode != 'pp')
def updateppDisplay(self): for pppTab in list(self.sourceCodeEdits.values()): self.sourceTabs.removeTab( self.sourceTabs.indexOf(pppTab) ) self.sourceCodeEdits = dict() for name, text in self.pulseProgram.source.items(): textEdit = PulseProgramSourceEdit() textEdit.setupUi(textEdit, extraKeywords1=self.definitionWords, extraKeywords2=[key for key in OPS]) textEdit.setPlainText(text) self.sourceCodeEdits[name] = textEdit self.sourceTabs.addTab( textEdit, name ) textEdit.setReadOnly( self.currentContext.pulseProgramMode!='pp' )
def updatepppDisplay(self): for pppTab in list(self.pppCodeEdits.values()): self.sourceTabs.removeTab(self.sourceTabs.indexOf(pppTab)) self.pppCodeEdits = dict() if self.currentContext.pulseProgramMode == 'ppp': for name, text in [(self.pppSourceFile, self.pppSource)]: textEdit = PulseProgramSourceEdit(mode='ppp') encodingStrings = [ encoding for encoding in EncodingDict.keys() if type(encoding) == str ] textEdit.setupUi(textEdit, extraKeywords1=self.definitionWords + encodingStrings, extraKeywords2=self.builtinWords) textEdit.setPlainText(text) self.pppCodeEdits[name] = textEdit self.sourceTabs.addTab(textEdit, name)
def setupUi(self, parent): super(ScriptingUi, self).setupUi(parent) self.configname = 'Scripting' #setup console self.consoleMaximumLines = self.config.get(self.configname+'.consoleMaximumLinesNew', 100) self.consoleEnable = self.config.get(self.configname+'.consoleEnable', True) self.consoleClearButton.clicked.connect( self.onClearConsole ) self.linesSpinBox.valueChanged.connect( self.onConsoleMaximumLinesChanged ) self.linesSpinBox.setValue( self.consoleMaximumLines ) self.checkBoxEnableConsole.stateChanged.connect( self.onEnableConsole ) self.checkBoxEnableConsole.setChecked( self.consoleEnable ) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=scriptFunctions) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine(QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor(QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #setup documentation list self.getDocs() self.docTreeWidget.setHeaderLabels(['Available Script Functions']) for funcDef, funcDesc in list(self.docDict.items()): itemDef = QtWidgets.QTreeWidgetItem(self.docTreeWidget, [funcDef]) self.docTreeWidget.addTopLevelItem(itemDef) QtWidgets.QTreeWidgetItem(itemDef, [funcDesc]) self.docTreeWidget.setWordWrap(True) #load file self.script.fullname = self.config.get( self.configname+'.script.fullname', '' ) if self.script.fullname != '' and os.path.exists(self.script.fullname): with open(self.script.fullname, "r") as f: self.script.code = f.read() else: self.script.code = '' #setup filename combo box self.recentFiles = self.config.get( self.configname+'.recentFiles', dict() ) self.recentFiles = {k: v for k,v in self.recentFiles.items() if os.path.exists(v)} #removes files from dict if file paths no longer exist self.filenameComboBox.setInsertPolicy(1) self.filenameComboBox.setMaxCount(10) self.filenameComboBox.addItems( [shortname for shortname, fullname in list(self.recentFiles.items()) if os.path.exists(fullname)] ) self.filenameComboBox.currentIndexChanged[str].connect( self.onFilenameChange ) self.removeCurrent.clicked.connect( self.onRemoveCurrent ) self.filenameComboBox.setValidator( QtGui.QRegExpValidator() ) #verifies that files typed into combo box can be used self.updateValidator() #connect buttons self.script.repeat = self.config.get(self.configname+'.repeat',False) self.repeatButton.setChecked(self.script.repeat) self.repeatButton.clicked.connect( self.onRepeat ) self.script.slow = self.config.get(self.configname+'.slow',False) self.slowButton.setChecked(self.script.slow) self.slowButton.clicked.connect( self.onSlow ) self.revert = self.config.get(self.configname+'.revert',False) self.revertButton.setChecked(self.revert) self.revertButton.clicked.connect( self.onRevert ) #File control actions self.actionOpen.triggered.connect( self.onLoad ) self.actionSave.triggered.connect( self.onSave ) self.actionReset.triggered.connect(self.onReset) self.actionNew.triggered.connect( self.onNew ) #Script control actions self.actionStartScript.triggered.connect( self.onStartScript ) self.actionPauseScript.triggered.connect( self.onPauseScript ) self.actionStopScript.triggered.connect( self.onStopScript ) self.actionPauseScriptAndScan.triggered.connect( self.onPauseScriptAndScan ) self.actionStopScriptAndScan.triggered.connect( self.onStopScriptAndScan ) #Script finished signal self.script.finished.connect( self.onFinished ) self.loadFile(self.script.fullname) self.populateTree() #populates file explorer tree widget #Connect buttons for fileTreeWidget self.fileTreeWidget.itemDoubleClicked.connect(self.onDoubleClick) self.expandTree = QtWidgets.QAction("Expand All", self) self.collapseTree = QtWidgets.QAction("Collapse All", self) self.expandChild = QtWidgets.QAction("Expand Selected", self) self.collapseChild = QtWidgets.QAction("Collapse Selected", self) self.expandTree.triggered.connect(partial(self.onExpandOrCollapse, True, True)) self.collapseTree.triggered.connect(partial(self.onExpandOrCollapse, True, False)) self.expandChild.triggered.connect(partial(self.onExpandOrCollapse, False, True)) self.collapseChild.triggered.connect(partial(self.onExpandOrCollapse, False, False)) self.fileTreeWidget.addAction(self.expandTree) self.fileTreeWidget.addAction(self.collapseTree) self.fileTreeWidget.addAction(self.expandChild) self.fileTreeWidget.addAction(self.collapseChild) self.fileTreeWidget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/other/icons/Terminal-icon.png")) self.statusLabel.setText("Idle")
class ScriptingUi(ScriptingWidget, ScriptingBase): """Ui for the scripting interface.""" def __init__(self, experimentUi): ScriptingWidget.__init__(self) ScriptingBase.__init__(self) self.config = experimentUi.config self.experimentUi = experimentUi self.recentFiles = dict() #dict of form {shortname: fullname}, where fullname has path and shortname doesn't self.script = Script() #encapsulates the script self.scriptHandler = ScriptHandler(self.script, experimentUi) #handles interface to the script self.revert = False self.initcode = '' self.defaultDir = getProject().configDir+'/Scripts' if not os.path.exists(self.defaultDir): defaultScriptsDir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'config/Scripts')) #/IonControl/config/Scripts directory shutil.copytree(defaultScriptsDir, self.defaultDir) #Copy over all example scripts def setupUi(self, parent): super(ScriptingUi, self).setupUi(parent) self.configname = 'Scripting' #setup console self.consoleMaximumLines = self.config.get(self.configname+'.consoleMaximumLinesNew', 100) self.consoleEnable = self.config.get(self.configname+'.consoleEnable', True) self.consoleClearButton.clicked.connect( self.onClearConsole ) self.linesSpinBox.valueChanged.connect( self.onConsoleMaximumLinesChanged ) self.linesSpinBox.setValue( self.consoleMaximumLines ) self.checkBoxEnableConsole.stateChanged.connect( self.onEnableConsole ) self.checkBoxEnableConsole.setChecked( self.consoleEnable ) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=scriptFunctions) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine(QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor(QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #setup documentation list self.getDocs() self.docTreeWidget.setHeaderLabels(['Available Script Functions']) for funcDef, funcDesc in list(self.docDict.items()): itemDef = QtWidgets.QTreeWidgetItem(self.docTreeWidget, [funcDef]) self.docTreeWidget.addTopLevelItem(itemDef) QtWidgets.QTreeWidgetItem(itemDef, [funcDesc]) self.docTreeWidget.setWordWrap(True) #load file self.script.fullname = self.config.get( self.configname+'.script.fullname', '' ) if self.script.fullname != '' and os.path.exists(self.script.fullname): with open(self.script.fullname, "r") as f: self.script.code = f.read() else: self.script.code = '' #setup filename combo box self.recentFiles = self.config.get( self.configname+'.recentFiles', dict() ) self.recentFiles = {k: v for k,v in self.recentFiles.items() if os.path.exists(v)} #removes files from dict if file paths no longer exist self.filenameComboBox.setInsertPolicy(1) self.filenameComboBox.setMaxCount(10) self.filenameComboBox.addItems( [shortname for shortname, fullname in list(self.recentFiles.items()) if os.path.exists(fullname)] ) self.filenameComboBox.currentIndexChanged[str].connect( self.onFilenameChange ) self.removeCurrent.clicked.connect( self.onRemoveCurrent ) self.filenameComboBox.setValidator( QtGui.QRegExpValidator() ) #verifies that files typed into combo box can be used self.updateValidator() #connect buttons self.script.repeat = self.config.get(self.configname+'.repeat',False) self.repeatButton.setChecked(self.script.repeat) self.repeatButton.clicked.connect( self.onRepeat ) self.script.slow = self.config.get(self.configname+'.slow',False) self.slowButton.setChecked(self.script.slow) self.slowButton.clicked.connect( self.onSlow ) self.revert = self.config.get(self.configname+'.revert',False) self.revertButton.setChecked(self.revert) self.revertButton.clicked.connect( self.onRevert ) #File control actions self.actionOpen.triggered.connect( self.onLoad ) self.actionSave.triggered.connect( self.onSave ) self.actionReset.triggered.connect(self.onReset) self.actionNew.triggered.connect( self.onNew ) #Script control actions self.actionStartScript.triggered.connect( self.onStartScript ) self.actionPauseScript.triggered.connect( self.onPauseScript ) self.actionStopScript.triggered.connect( self.onStopScript ) self.actionPauseScriptAndScan.triggered.connect( self.onPauseScriptAndScan ) self.actionStopScriptAndScan.triggered.connect( self.onStopScriptAndScan ) #Script finished signal self.script.finished.connect( self.onFinished ) self.loadFile(self.script.fullname) self.populateTree() #populates file explorer tree widget #Connect buttons for fileTreeWidget self.fileTreeWidget.itemDoubleClicked.connect(self.onDoubleClick) self.expandTree = QtWidgets.QAction("Expand All", self) self.collapseTree = QtWidgets.QAction("Collapse All", self) self.expandChild = QtWidgets.QAction("Expand Selected", self) self.collapseChild = QtWidgets.QAction("Collapse Selected", self) self.expandTree.triggered.connect(partial(self.onExpandOrCollapse, True, True)) self.collapseTree.triggered.connect(partial(self.onExpandOrCollapse, True, False)) self.expandChild.triggered.connect(partial(self.onExpandOrCollapse, False, True)) self.collapseChild.triggered.connect(partial(self.onExpandOrCollapse, False, False)) self.fileTreeWidget.addAction(self.expandTree) self.fileTreeWidget.addAction(self.collapseTree) self.fileTreeWidget.addAction(self.expandChild) self.fileTreeWidget.addAction(self.collapseChild) self.fileTreeWidget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/other/icons/Terminal-icon.png")) self.statusLabel.setText("Idle") @QtCore.pyqtSlot() def onStartScript(self): """Start script button is clicked""" if not self.script.isRunning(): logger = logging.getLogger(__name__) message = "script {0} started at {1}".format(self.script.fullname, str(datetime.now())) logger.info(message) self.writeToConsole(message, color='blue') self.onSave() self.enableScriptChange(False) self.actionPauseScript.setChecked(False) self.statusLabel.setText("Script running") if self.revert: self.savedState = True self.saveSettingsState() else: self.savedState = False self.scriptHandler.onStartScript() @QtCore.pyqtSlot(bool) def onPauseScript(self, paused): """Pause script button is clicked""" logger = logging.getLogger(__name__) message = "Script is paused" if paused else "Script is unpaused" markerColor = QtGui.QColor("#c0c0ff") if paused else QtGui.QColor(0xd0, 0xff, 0xd0) self.textEdit.textEdit.setMarkerBackgroundColor(markerColor, self.textEdit.textEdit.currentLineMarkerNum) logger.info(message) self.writeToConsole(message, color='blue') self.actionPauseScript.setChecked(paused) self.scriptHandler.onPauseScript(paused) @QtCore.pyqtSlot() def onStopScript(self): """Stop script button is clicked""" self.actionPauseScript.setChecked(False) self.repeatButton.setChecked(False) self.scriptHandler.onStopScript() @QtCore.pyqtSlot() def onPauseScriptAndScan(self): """Pause script and scan button is clicked""" logger = logging.getLogger(__name__) message = "Script is paused" markerColor = QtGui.QColor("#c0c0ff") self.textEdit.textEdit.setMarkerBackgroundColor(markerColor, self.textEdit.textEdit.currentLineMarkerNum) logger.info(message) self.writeToConsole(message, color='blue') self.actionPauseScript.setChecked(True) self.scriptHandler.onPauseScriptAndScan() @QtCore.pyqtSlot() def onStopScriptAndScan(self): """Stop script and scan button is clicked""" self.actionPauseScript.setChecked(False) self.repeatButton.setChecked(False) self.scriptHandler.onStopScriptAndScan() @QtCore.pyqtSlot() def onFinished(self): """Runs when script thread finishes. re-enables script GUI.""" logger = logging.getLogger(__name__) self.statusLabel.setText("Idle") message = "script {0} finished at {1}".format(self.script.fullname, str(datetime.now())) logger.info(message) self.writeToConsole(message, color='blue') self.textEdit.textEdit.markerDeleteAll() self.enableScriptChange(True) if self.revert and self.savedState: self.restoreSettingsState() @QtCore.pyqtSlot() def onRepeat(self): """Repeat button is clicked.""" logger = logging.getLogger(__name__) repeat = self.repeatButton.isChecked() message = "Repeat is on" if repeat else "Repeat is off" logger.debug(message) self.writeToConsole(message) self.scriptHandler.onRepeat(repeat) @QtCore.pyqtSlot() def onRevert(self): """Revert button is clicked.""" self.revert = self.revertButton.isChecked() logging.getLogger(__name__).debug("Revert is on" if self.revert else "Revert is off") @QtCore.pyqtSlot() def onSlow(self): """Slow button is clicked.""" logger = logging.getLogger(__name__) slow = self.slowButton.isChecked() message = "Slow is on" if slow else "Slow is off" logger.debug(message) self.writeToConsole(message) self.scriptHandler.onSlow(slow) @QtCore.pyqtSlot() def onNew(self): """New button is clicked. Pop up dialog asking for new name, and create file.""" logger = logging.getLogger(__name__) shortname, ok = QtWidgets.QInputDialog.getText(self, 'New script name', 'Please enter a new script name: ') if ok: shortname = str(shortname) shortname = shortname.replace(' ', '_') #Replace spaces with underscores shortname = shortname.split('.')[0] #Take only what's before the '.' ensurePath(self.defaultDir + '/' + shortname) shortname += '.py' fullname = self.defaultDir + '/' + shortname if not os.path.exists(fullname): try: with open(fullname, 'w') as f: newFileText = '#' + shortname + ' created ' + str(datetime.now()) + '\n' f.write(newFileText) except Exception as e: message = "Unable to create new file {0}: {1}".format(shortname, e) logger.error(message) self.onConsoleSignal(message, False) return self.loadFile(fullname) self.populateTree(fullname) def enableScriptChange(self, enabled): """Enable or disable any changes to script editor""" color = QtGui.QColor("#ffe4e4") if enabled else QtGui.QColor('white') self.textEdit.textEdit.setCaretLineVisible(enabled) self.textEdit.textEdit.setCaretLineBackgroundColor(color) self.textEdit.setReadOnly(not enabled) self.filenameComboBox.setDisabled(not enabled) self.removeCurrent.setDisabled(not enabled) self.actionOpen.setEnabled(enabled) self.actionSave.setEnabled(enabled) self.actionReset.setEnabled(enabled) self.actionNew.setEnabled(enabled) self.actionStartScript.setEnabled(enabled) self.actionPauseScript.setEnabled(not enabled) self.actionStopScript.setEnabled(not enabled) self.actionPauseScriptAndScan.setEnabled(not enabled) self.actionStopScriptAndScan.setEnabled(not enabled) def onFilenameChange(self, shortname ): """A name is typed into the filename combo box.""" shortname = str(shortname) logger = logging.getLogger(__name__) if not shortname: self.script.fullname='' self.textEdit.setPlainText('') elif shortname not in self.recentFiles: logger.info('Use "open" or "new" commands to access a file not in the drop down menu') self.loadFile(self.recentFiles[self.script.shortname]) else: fullname = self.recentFiles[shortname] if os.path.isfile(fullname) and fullname != self.script.fullname: self.loadFile(fullname) if str(self.filenameComboBox.currentText())!=fullname: with BlockSignals(self.filenameComboBox) as w: w.setCurrentIndex( self.filenameComboBox.findText( shortname )) def onLoad(self): """The load button is clicked. Open file prompt for file.""" fullname, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open Script', self.defaultDir, 'Python scripts (*.py *.pyw)') if fullname!="": self.loadFile(fullname) def loadFile(self, fullname): """Load in a file.""" logger = logging.getLogger(__name__) if fullname: self.script.fullname = fullname with open(fullname, "r") as f: self.script.code = f.read() self.textEdit.setPlainText(self.script.code) if self.script.shortname not in self.recentFiles: self.recentFiles[self.script.shortname] = fullname self.filenameComboBox.addItem(self.script.shortname) self.updateValidator() with BlockSignals(self.filenameComboBox) as w: ind = w.findText(self.script.shortname) w.removeItem(ind) w.insertItem(0, self.script.shortname) w.setCurrentIndex(0) logger.info('{0} loaded'.format(self.script.fullname)) self.initcode = copy.copy(self.script.code) def confirmLoad(self): """pop up window to confirm loss of unsaved changes when loading new file""" reply = QtWidgets.QMessageBox.question(self, 'Message', "Are you sure you want to discard changes?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: return True return False def onExpandOrCollapse(self, expglobal=True, expand=True): """For expanding/collapsing file tree, expglobal=True will expand/collapse everything and False will collapse/expand only selected nodes. expand=True will expand, False will collapse""" if expglobal: root = self.fileTreeWidget.invisibleRootItem() self.recurseExpand(root, expand) else: selected = self.fileTreeWidget.selectedItems() if selected: for child in selected: child.setExpanded(expand) self.recurseExpand(child, expand) def recurseExpand(self, node, expand=True): """recursively descends into tree structure below node to expand/collapse all subdirectories. expand=True will expand, False will collapse.""" for childind in range(node.childCount()): node.child(childind).setExpanded(expand) self.recurseExpand(node.child(childind), expand) def onDoubleClick(self, *args): """open a file that is double clicked in file tree""" if self.script.code != str(self.textEdit.toPlainText()): if not self.confirmLoad(): return False self.loadFile(args[0].path) def populateTree(self, newfilepath=None): """constructs the file tree viewer""" genFileTree(self.fileTreeWidget.invisibleRootItem(), Path(self.defaultDir), newfilepath) def onReset(self): """Reset action. Reset file state saved on disk.""" if self.script.fullname: self.loadFile(self.script.fullname) def onRemoveCurrent(self): """Remove current button is clicked. Remove file from combo box.""" text = str(self.filenameComboBox.currentText()) ind = self.filenameComboBox.findText(text) self.filenameComboBox.setCurrentIndex(ind) self.filenameComboBox.removeItem(ind) if text in self.recentFiles: self.recentFiles.pop(text) self.updateValidator() def onSave(self): """Save action. Save file to disk, and clear any highlighted errors.""" logger = logging.getLogger(__name__) self.script.code = str(self.textEdit.toPlainText()) self.textEdit.clearHighlightError() if self.script.code and self.script.fullname: with open(self.script.fullname, 'w') as f: f.write(self.script.code) logger.info('{0} saved'.format(self.script.fullname)) def saveConfig(self): """Save configuration.""" self.config[self.configname+'.recentFiles'] = self.recentFiles self.config[self.configname+'.script.fullname'] = self.script.fullname self.config[self.configname+'.revert'] = self.revert self.config[self.configname+'.slow'] = self.script.slow self.config[self.configname+'.repeat'] = self.script.repeat self.config[self.configname+'.isVisible'] = self.isVisible() self.config[self.configname+'.ScriptingUi.pos'] = self.pos() self.config[self.configname+'.ScriptingUi.size'] = self.size() self.config[self.configname+".splitterHorizontal"] = self.splitterHorizontal.saveState() self.config[self.configname+".splitterVertical"] = self.splitterVertical.saveState() self.config[self.configname+'.consoleMaximumLinesNew'] = self.consoleMaximumLines self.config[self.configname+'.consoleEnable'] = self.consoleEnable def show(self): pos = self.config.get(self.configname+'.ScriptingUi.pos') size = self.config.get(self.configname+'.ScriptingUi.size') splitterHorizontalState = self.config.get(self.configname+".splitterHorizontal") splitterVerticalState = self.config.get(self.configname+".splitterVertical") if pos: self.move(pos) if size: self.resize(size) if splitterHorizontalState: self.splitterHorizontal.restoreState(splitterHorizontalState) if splitterVerticalState: self.splitterVertical.restoreState(splitterVerticalState) QtWidgets.QDialog.show(self) def onClose(self): self.saveConfig() self.hide() def onClearConsole(self): self.textEditConsole.clear() def onConsoleMaximumLinesChanged(self, maxlines): self.consoleMaximumLines = maxlines self.textEditConsole.document().setMaximumBlockCount(maxlines) def onEnableConsole(self, state): self.consoleEnable = state==QtCore.Qt.Checked def markLocation(self, lines): """mark a specified location""" if lines: self.textEdit.textEdit.markerDeleteAll() for line in lines: self.textEdit.textEdit.markerAdd(line-1, self.textEdit.textEdit.ARROW_MARKER_NUM) self.textEdit.textEdit.markerAdd(line-1, self.textEdit.textEdit.currentLineMarkerNum) def markError(self, lines, message): """mark error at specified lines, and show message""" if lines != []: for line in lines: self.textEdit.highlightError(message, line) def writeToConsole(self, message, error=False, color=''): if self.consoleEnable: message = str(message) cursor = self.textEditConsole.textCursor() cursor.movePosition(QtGui.QTextCursor.End) textColor = ('red' if error else 'black') if color=='' else color self.textEditConsole.setUpdatesEnabled(False) if textColor == 'black': self.textEditConsole.insertPlainText(message+'\n') else: self.textEditConsole.insertHtml(str('<p><font color='+textColor+'>'+message+'</font><br></p>')) self.textEditConsole.setUpdatesEnabled(True) self.textEditConsole.setTextCursor(cursor) self.textEditConsole.ensureCursorVisible() def getDocs(self): """Assemble the script function documentation into a dictionary""" self.docDict = OrderedDict() for doc in scriptDocs: docsplit = doc.splitlines() defLine = docsplit.pop(0) docsplit = [line.strip() for line in docsplit] docsplit = '\n'.join(docsplit) self.docDict[defLine] = docsplit def updateValidator(self): """Make the validator match the recentFiles list. Uses regExp \\b(f1|f2|f3...)\\b, where fn are filenames.""" regExp = '\\b(' for shortname in self.recentFiles: if shortname: regExp += shortname + '|' regExp = regExp[:-1] #drop last pipe symbol regExp += ')\\b' self.filenameComboBox.validator().setRegExp(QtCore.QRegExp(regExp)) def saveSettingsState(self): """Save the state of the scan, evaluation, and analysis""" self.originalState = dict() self.originalState['scan'] = self.experimentUi.tabDict['Scan'].scanControlWidget.settingsName self.originalState['evaluation'] = self.experimentUi.tabDict['Scan'].evaluationControlWidget.settingsName self.originalState['analysis'] = self.experimentUi.tabDict['Scan'].analysisControlWidget.currentAnalysisName def restoreSettingsState(self): """Restore the settings to their original values""" for name, value in self.scriptHandler.globalVariablesRevertDict.items(): self.experimentUi.globalVariablesUi.model.update([('Global', name, value)]) self.experimentUi.tabDict['Scan'].scanControlWidget.loadSetting(self.originalState['scan']) self.experimentUi.tabDict['Scan'].evaluationControlWidget.loadSetting(self.originalState['evaluation']) self.experimentUi.tabDict['Scan'].analysisControlWidget.onLoadAnalysisConfiguration(self.originalState['analysis'])
def setupUi(self, parent): super(UserFunctionsEditor, self).setupUi(parent) self.tableModel = EvalTableModel(self.globalDict) self.tableView.setModel(self.tableModel) self.tableView.setSortingEnabled(True) # triggers sorting self.delegate = MagnitudeSpinBoxDelegate(self.globalDict) self.tableView.setItemDelegateForColumn(1, self.delegate) self.addEvalRow.clicked.connect(self.onAddRow) self.removeEvalRow.clicked.connect(self.onRemoveRow) #initialize default options self.optionsWindow = OptionsWindow(self.config, 'UserFunctionsEditorOptions') self.optionsWindow.setupUi(self.optionsWindow) self.actionOptions.triggered.connect(self.onOpenOptions) self.optionsWindow.OptionsChangedSignal.connect(self.updateOptions) self.updateOptions() if self.optionsWindow.defaultExpand: onExpandOrCollapse(self.fileTreeWidget, True, True) #hot keys for copy/past and sorting self.filter = KeyListFilter( [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]) self.filter.keyPressed.connect(self.onReorder) self.tableView.installEventFilter(self.filter) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Copy), self, self.copy_to_clipboard) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Paste), self, self.paste_from_clipboard) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=[]) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine( QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum ) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor( QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #load recent files, also checks if data was saved correctly and if files still exist savedfiles = self.config.get(self.configname + '.recentFiles', OrderedList()) self.initRecentFiles(savedfiles) self.initComboBox() self.tableModel.exprList = self.config.get( self.configname + '.evalstr', [ExpressionValue(None, self.globalDict)]) if not isinstance(self.tableModel.exprList, list) or not isinstance( self.tableModel.exprList[0], ExpressionValue): self.tableModel.exprList = [ExpressionValue(None, self.globalDict)] self.tableModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) self.tableModel.layoutChanged.emit() self.tableModel.connectAllExprVals() #load last opened file self.script.fullname = self.config.get( self.configname + '.script.fullname', '') self.initLoad() #connect buttons self.actionOpen.triggered.connect(self.onLoad) self.actionSave.triggered.connect(self.onSave) self.actionNew.triggered.connect(self.onNew) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/latex/icons/FuncIcon2.png")) self.statusLabel.setText("") self.tableModel.updateData()
class UserFunctionsEditor(FileTreeMixin, EditorWidget, EditorBase): """Ui for the user function interface.""" def __init__(self, experimentUi, globalDict): super().__init__() self.config = experimentUi.config self.experimentUi = experimentUi self.globalDict = globalDict self.configDirFolder = 'UserFunctions' self.configname = 'UserFunctionsEditor' self.defaultDir = Path(getProject().configDir + '/' + self.configDirFolder) self.displayFullPathNames = True self.script = UserCode( self.displayFullPathNames, self.defaultDir) #carries around code body and filepath info if not self.defaultDir.exists(): defaultScriptsDir = os.path.realpath( os.path.join(os.path.dirname(__file__), '..', 'config/' + self.configDirFolder) ) #/IonControl/config/UserFunctions directory shutil.copytree(defaultScriptsDir, str( self.defaultDir)) #Copy over all example scripts def setupUi(self, parent): super(UserFunctionsEditor, self).setupUi(parent) self.tableModel = EvalTableModel(self.globalDict) self.tableView.setModel(self.tableModel) self.tableView.setSortingEnabled(True) # triggers sorting self.delegate = MagnitudeSpinBoxDelegate(self.globalDict) self.tableView.setItemDelegateForColumn(1, self.delegate) self.addEvalRow.clicked.connect(self.onAddRow) self.removeEvalRow.clicked.connect(self.onRemoveRow) #initialize default options self.optionsWindow = OptionsWindow(self.config, 'UserFunctionsEditorOptions') self.optionsWindow.setupUi(self.optionsWindow) self.actionOptions.triggered.connect(self.onOpenOptions) self.optionsWindow.OptionsChangedSignal.connect(self.updateOptions) self.updateOptions() if self.optionsWindow.defaultExpand: onExpandOrCollapse(self.fileTreeWidget, True, True) #hot keys for copy/past and sorting self.filter = KeyListFilter( [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]) self.filter.keyPressed.connect(self.onReorder) self.tableView.installEventFilter(self.filter) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Copy), self, self.copy_to_clipboard) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Paste), self, self.paste_from_clipboard) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=[]) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine( QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum ) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor( QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #load recent files, also checks if data was saved correctly and if files still exist savedfiles = self.config.get(self.configname + '.recentFiles', OrderedList()) self.initRecentFiles(savedfiles) self.initComboBox() self.tableModel.exprList = self.config.get( self.configname + '.evalstr', [ExpressionValue(None, self.globalDict)]) if not isinstance(self.tableModel.exprList, list) or not isinstance( self.tableModel.exprList[0], ExpressionValue): self.tableModel.exprList = [ExpressionValue(None, self.globalDict)] self.tableModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) self.tableModel.layoutChanged.emit() self.tableModel.connectAllExprVals() #load last opened file self.script.fullname = self.config.get( self.configname + '.script.fullname', '') self.initLoad() #connect buttons self.actionOpen.triggered.connect(self.onLoad) self.actionSave.triggered.connect(self.onSave) self.actionNew.triggered.connect(self.onNew) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/latex/icons/FuncIcon2.png")) self.statusLabel.setText("") self.tableModel.updateData() def onOpenOptions(self): self.optionsWindow.show() self.optionsWindow.setWindowState(QtCore.Qt.WindowActive) self.optionsWindow.raise_() def updateOptions(self): self.filenameComboBox.setMaxCount(self.optionsWindow.lineno) self.displayFullPathNames = self.optionsWindow.displayPath self.script.dispfull = self.optionsWindow.displayPath self.defaultExpandAll = self.optionsWindow.defaultExpand self.updateFileComboBoxNames(self.displayFullPathNames) @QtCore.pyqtSlot() def onNew(self): """New button is clicked. Pop up dialog asking for new name, and create file.""" logger = logging.getLogger(__name__) shortname, ok = QtWidgets.QInputDialog.getText( self, 'New script name', 'Enter new file name (optional path specified by localpath/filename): ' ) if ok: shortname = str(shortname) shortname = shortname.replace( ' ', '_') #Replace spaces with underscores shortname = shortname.split( '.')[0] + '.py' #Take only what's before the '.' fullname = self.defaultDir.joinpath(shortname) ensurePath(fullname.parent) if not fullname.exists(): try: with fullname.open('w') as f: newFileText = '#' + shortname + ' created ' + str( datetime.now()) + '\n\n' f.write(newFileText) defaultImportText = 'from expressionFunctions.ExprFuncDecorator import userfunc\n\n' f.write(defaultImportText) except Exception as e: message = "Unable to create new file {0}: {1}".format( shortname, e) logger.error(message) return self.loadFile(fullname) self.populateTree(fullname) def onComboIndexChange(self, ind): """A name is typed into the filename combo box.""" if ind == 0: return False if self.script.code != str(self.textEdit.toPlainText()): if not self.confirmLoad(): self.filenameComboBox.setCurrentIndex(0) return False self.loadFile(self.filenameComboBox.itemData(ind)) def onLoad(self): """The load button is clicked. Open file prompt for file.""" fullname, _ = QtWidgets.QFileDialog.getOpenFileName( self, 'Open Script', self.defaultDir, 'Python scripts (*.py *.pyw)') if fullname != "": self.loadFile(fullname) def loadFile(self, fullname): """Load in a file.""" logger = logging.getLogger(__name__) if fullname: self.script.fullname = fullname with fullname.open("r") as f: self.script.code = f.read() self.textEdit.setPlainText(self.script.code) if self.script.fullname not in self.recentFiles: self.filenameComboBox.addItem(self.script.shortname) self.recentFiles.add(fullname) with BlockSignals(self.filenameComboBox) as w: ind = w.findText( str(self.script.shortname )) #having issues with findData Path object comparison w.removeItem( ind ) #these two lines just push the loaded filename to the top of the combobox w.insertItem(0, str(self.script.shortname)) w.setItemData(0, self.script.fullname) w.setCurrentIndex(0) logger.info('{0} loaded'.format(self.script.fullname)) self.initcode = copy.copy(self.script.code) def onRemoveCurrent(self): """Remove current button is clicked. Remove file from combo box.""" path = self.filenameComboBox.currentData() if path in self.recentFiles: self.recentFiles.remove(path) self.filenameComboBox.removeItem(0) self.loadFile(self.filenameComboBox.currentData()) def onSave(self): """Save action. Save file to disk, and clear any highlighted errors.""" logger = logging.getLogger(__name__) self.script.code = str(self.textEdit.toPlainText()) self.textEdit.clearHighlightError() if self.script.code and self.script.fullname: with self.script.fullname.open('w') as f: f.write(self.script.code) logger.info('{0} saved'.format(self.script.fullname)) self.initcode = copy.copy(self.script.code) try: importlib.machinery.SourceFileLoader( "UserFunctions", str(self.script.fullname)).load_module() self.tableModel.updateData() ExprFunUpdate.dataChanged.emit('__exprfunc__') self.statusLabel.setText("Successfully updated {0}".format( self.script.fullname.name)) self.statusLabel.setStyleSheet('color: green') except SyntaxError as e: self.statusLabel.setText("Failed to execute {0}: {1}".format( self.script.fullname.name, e)) self.statusLabel.setStyleSheet('color: red') def saveConfig(self): """Save configuration.""" self.config[self.configname + '.recentFiles'] = self.recentFiles self.config[self.configname + '.script.fullname'] = self.script.fullname self.config[self.configname + '.isVisible'] = self.isVisible() self.config[self.configname + '.ScriptingUi.pos'] = self.pos() self.config[self.configname + '.ScriptingUi.size'] = self.size() self.config[ self.configname + ".splitterHorizontal"] = self.splitterHorizontal.saveState() self.config[self.configname + ".splitterVertical"] = self.splitterVertical.saveState() self.config[self.configname + ".evalstr"] = self.tableModel.exprList def show(self): pos = self.config.get(self.configname + '.ScriptingUi.pos') size = self.config.get(self.configname + '.ScriptingUi.size') splitterHorizontalState = self.config.get(self.configname + ".splitterHorizontal") splitterVerticalState = self.config.get(self.configname + ".splitterVertical") if pos: self.move(pos) if size: self.resize(size) if splitterHorizontalState: self.splitterHorizontal.restoreState(splitterHorizontalState) if splitterVerticalState: self.splitterVertical.restoreState(splitterVerticalState) QtWidgets.QDialog.show(self) def onAddRow(self): """add a row in expression tests""" self.tableModel.insertRow() def onRemoveRow(self): """remove row(s) in expression tests""" zeroColSelInd = self.tableView.selectedIndexes() if len(zeroColSelInd): initRow = zeroColSelInd[0].row() finRow = zeroColSelInd[-1].row() - initRow + 1 self.tableModel.removeRows(initRow, finRow) else: self.tableModel.removeRows(len(self.tableModel.exprList) - 1) def copy_to_clipboard(self): """ Copy the list of selected rows to the clipboard as a string. """ clip = QtWidgets.QApplication.clipboard() rows = sorted( unique([i.row() for i in self.tableView.selectedIndexes()])) clip.setText(str(rows)) def paste_from_clipboard(self): """ Append the string of rows from the clipboard to the end of the TODO list. """ clip = QtWidgets.QApplication.clipboard() row_string = str(clip.text()) try: row_list = list(map(int, row_string.strip('[]').split(','))) except ValueError: raise ValueError( "Invalid data on clipboard. Cannot paste into eval list") zeroColSelInd = self.tableView.selectedIndexes() initRow = zeroColSelInd[-1].row() self.tableModel.copy_rows(row_list, initRow) def onReorder(self, key): """reorder expression tests with pgup and pgdn""" if key in [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]: indexes = self.tableView.selectedIndexes() up = key == QtCore.Qt.Key_PageUp delta = -1 if up else 1 rows = sorted(unique([i.row() for i in indexes]), reverse=not up) if self.tableModel.moveRow(rows, delta): selectionModel = self.tableView.selectionModel() selectionModel.clearSelection() for index in indexes: selectionModel.select( self.tableModel.createIndex(index.row() + delta, index.column()), QtCore.QItemSelectionModel.Select) def onClose(self): self.saveConfig() self.hide()
def setupUi(self, parent): super(UserFunctionsEditor, self).setupUi(parent) self.configname = 'UserFunctionsEditor' self.fileTreeWidget.setHeaderLabels(['User Function Files']) self.populateTree() self.tableModel = EvalTableModel(self.globalDict) self.tableView.setModel(self.tableModel) self.tableView.setSortingEnabled(True) # triggers sorting self.delegate = MagnitudeSpinBoxDelegate(self.globalDict) self.tableView.setItemDelegateForColumn(1, self.delegate) self.addEvalRow.clicked.connect(self.onAddRow) self.removeEvalRow.clicked.connect(self.onRemoveRow) #hot keys for copy/past and sorting self.filter = KeyListFilter( [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]) self.filter.keyPressed.connect(self.onReorder) self.tableView.installEventFilter(self.filter) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Copy), self, self.copy_to_clipboard) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Paste), self, self.paste_from_clipboard) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=[]) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine( QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum ) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor( QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #load file self.script.fullname = self.config.get( self.configname + '.script.fullname', '') self.tableModel.exprList = self.config.get( self.configname + '.evalstr', [ExpressionValue(None, self.globalDict)]) if not isinstance(self.tableModel.exprList, list) or not isinstance( self.tableModel.exprList[0], ExpressionValue): self.tableModel.exprList = [ExpressionValue(None, self.globalDict)] self.tableModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) self.tableModel.layoutChanged.emit() self.tableModel.connectAllExprVals() if self.script.fullname != '' and os.path.exists(self.script.fullname): with open(self.script.fullname, "r") as f: self.script.code = f.read() else: self.script.code = '' #setup filename combo box self.recentFiles = self.config.get(self.configname + '.recentFiles', dict()) self.recentFiles = { k: v for k, v in self.recentFiles.items() if os.path.exists(v) } #removes files from dict if file paths no longer exist self.filenameComboBox.setInsertPolicy(1) self.filenameComboBox.setMaxCount(10) self.filenameComboBox.addItems([ shortname for shortname, fullname in list(self.recentFiles.items()) if os.path.exists(fullname) ]) self.filenameComboBox.currentIndexChanged[str].connect( self.onFilenameChange) self.removeCurrent.clicked.connect(self.onRemoveCurrent) self.filenameComboBox.setValidator(QtGui.QRegExpValidator( )) #verifies that files typed into combo box can be used self.updateValidator() #connect buttons self.actionOpen.triggered.connect(self.onLoad) self.actionSave.triggered.connect(self.onSave) self.actionNew.triggered.connect(self.onNew) self.fileTreeWidget.itemDoubleClicked.connect(self.onDoubleClick) self.loadFile(self.script.fullname) self.expandTree = QtWidgets.QAction("Expand All", self) self.collapseTree = QtWidgets.QAction("Collapse All", self) self.expandChild = QtWidgets.QAction("Expand Selected", self) self.collapseChild = QtWidgets.QAction("Collapse Selected", self) self.expandTree.triggered.connect( partial(self.onExpandOrCollapse, True, True)) self.collapseTree.triggered.connect( partial(self.onExpandOrCollapse, True, False)) self.expandChild.triggered.connect( partial(self.onExpandOrCollapse, False, True)) self.collapseChild.triggered.connect( partial(self.onExpandOrCollapse, False, False)) self.fileTreeWidget.addAction(self.expandTree) self.fileTreeWidget.addAction(self.collapseTree) self.fileTreeWidget.addAction(self.expandChild) self.fileTreeWidget.addAction(self.collapseChild) self.fileTreeWidget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/latex/icons/FuncIcon2.png")) self.statusLabel.setText("") self.tableModel.updateData()
class UserFunctionsEditor(EditorWidget, EditorBase): """Ui for the user function interface.""" def __init__(self, experimentUi, globalDict): super().__init__() self.config = experimentUi.config self.experimentUi = experimentUi self.globalDict = globalDict self.recentFiles = dict( ) #dict of form {shortname: fullname}, where fullname has path and shortname doesn't self.script = UserCode() #carries around code body and filepath info self.defaultDir = getProject().configDir + '/UserFunctions' if not os.path.exists(self.defaultDir): defaultScriptsDir = os.path.realpath( os.path.join(os.path.dirname(__file__), '..', 'config/UserFunctions') ) #/IonControl/config/UserFunctions directory shutil.copytree(defaultScriptsDir, self.defaultDir) #Copy over all example scripts def setupUi(self, parent): super(UserFunctionsEditor, self).setupUi(parent) self.configname = 'UserFunctionsEditor' self.fileTreeWidget.setHeaderLabels(['User Function Files']) self.populateTree() self.tableModel = EvalTableModel(self.globalDict) self.tableView.setModel(self.tableModel) self.tableView.setSortingEnabled(True) # triggers sorting self.delegate = MagnitudeSpinBoxDelegate(self.globalDict) self.tableView.setItemDelegateForColumn(1, self.delegate) self.addEvalRow.clicked.connect(self.onAddRow) self.removeEvalRow.clicked.connect(self.onRemoveRow) #hot keys for copy/past and sorting self.filter = KeyListFilter( [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]) self.filter.keyPressed.connect(self.onReorder) self.tableView.installEventFilter(self.filter) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Copy), self, self.copy_to_clipboard) QtWidgets.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Paste), self, self.paste_from_clipboard) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=[]) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine( QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum ) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor( QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #load file self.script.fullname = self.config.get( self.configname + '.script.fullname', '') self.tableModel.exprList = self.config.get( self.configname + '.evalstr', [ExpressionValue(None, self.globalDict)]) if not isinstance(self.tableModel.exprList, list) or not isinstance( self.tableModel.exprList[0], ExpressionValue): self.tableModel.exprList = [ExpressionValue(None, self.globalDict)] self.tableModel.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) self.tableModel.layoutChanged.emit() self.tableModel.connectAllExprVals() if self.script.fullname != '' and os.path.exists(self.script.fullname): with open(self.script.fullname, "r") as f: self.script.code = f.read() else: self.script.code = '' #setup filename combo box self.recentFiles = self.config.get(self.configname + '.recentFiles', dict()) self.recentFiles = { k: v for k, v in self.recentFiles.items() if os.path.exists(v) } #removes files from dict if file paths no longer exist self.filenameComboBox.setInsertPolicy(1) self.filenameComboBox.setMaxCount(10) self.filenameComboBox.addItems([ shortname for shortname, fullname in list(self.recentFiles.items()) if os.path.exists(fullname) ]) self.filenameComboBox.currentIndexChanged[str].connect( self.onFilenameChange) self.removeCurrent.clicked.connect(self.onRemoveCurrent) self.filenameComboBox.setValidator(QtGui.QRegExpValidator( )) #verifies that files typed into combo box can be used self.updateValidator() #connect buttons self.actionOpen.triggered.connect(self.onLoad) self.actionSave.triggered.connect(self.onSave) self.actionNew.triggered.connect(self.onNew) self.fileTreeWidget.itemDoubleClicked.connect(self.onDoubleClick) self.loadFile(self.script.fullname) self.expandTree = QtWidgets.QAction("Expand All", self) self.collapseTree = QtWidgets.QAction("Collapse All", self) self.expandChild = QtWidgets.QAction("Expand Selected", self) self.collapseChild = QtWidgets.QAction("Collapse Selected", self) self.expandTree.triggered.connect( partial(self.onExpandOrCollapse, True, True)) self.collapseTree.triggered.connect( partial(self.onExpandOrCollapse, True, False)) self.expandChild.triggered.connect( partial(self.onExpandOrCollapse, False, True)) self.collapseChild.triggered.connect( partial(self.onExpandOrCollapse, False, False)) self.fileTreeWidget.addAction(self.expandTree) self.fileTreeWidget.addAction(self.collapseTree) self.fileTreeWidget.addAction(self.expandChild) self.fileTreeWidget.addAction(self.collapseChild) self.fileTreeWidget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/latex/icons/FuncIcon2.png")) self.statusLabel.setText("") self.tableModel.updateData() def onExpandOrCollapse(self, expglobal=True, expand=True): """For expanding/collapsing file tree, expglobal=True will expand/collapse everything and False will collapse/expand only selected nodes. expand=True will expand, False will collapse""" if expglobal: root = self.fileTreeWidget.invisibleRootItem() self.recurseExpand(root, expand) else: selected = self.fileTreeWidget.selectedItems() if selected: for child in selected: child.setExpanded(expand) self.recurseExpand(child, expand) def recurseExpand(self, node, expand=True): """recursively descends into tree structure below node to expand/collapse all subdirectories. expand=True will expand, False will collapse.""" for childind in range(node.childCount()): node.child(childind).setExpanded(expand) self.recurseExpand(node.child(childind), expand) def confirmLoad(self): """pop up window to confirm loss of unsaved changes when loading new file""" reply = QtWidgets.QMessageBox.question( self, 'Message', "Are you sure you want to discard changes?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: return True return False def onDoubleClick(self, *args): """open a file that is double clicked in file tree""" if self.script.code != str(self.textEdit.toPlainText()): if not self.confirmLoad(): return False if not args[0].isdir: self.loadFile(args[0].path) def populateTree(self, newfilepath=None): """constructs the file tree viewer""" genFileTree(self.fileTreeWidget.invisibleRootItem(), Path(self.defaultDir), newfilepath) @QtCore.pyqtSlot() def onNew(self): """New button is clicked. Pop up dialog asking for new name, and create file.""" logger = logging.getLogger(__name__) shortname, ok = QtWidgets.QInputDialog.getText( self, 'New script name', 'Enter new file name (optional path specified by localpath/filename): ' ) if ok: shortname = str(shortname) shortname = shortname.replace( ' ', '_') #Replace spaces with underscores shortname = shortname.split('.')[ 0] #Take only what's before the '.' ensurePath(self.defaultDir + '/' + shortname) shortname += '.py' fullname = self.defaultDir + '/' + shortname if not os.path.exists(fullname): try: with open(fullname, 'w') as f: newFileText = '#' + shortname + ' created ' + str( datetime.now()) + '\n\n' f.write(newFileText) defaultImportText = 'from expressionFunctions.ExprFuncDecorator import userfunc\n\n' f.write(defaultImportText) except Exception as e: message = "Unable to create new file {0}: {1}".format( shortname, e) logger.error(message) return self.loadFile(fullname) self.populateTree(fullname) def onFilenameChange(self, shortname): """A name is typed into the filename combo box.""" shortname = str(shortname) logger = logging.getLogger(__name__) if not shortname: self.script.fullname = '' self.textEdit.setPlainText('') elif shortname not in self.recentFiles: logger.info( 'Use "open" or "new" commands to access a file not in the drop down menu' ) self.loadFile(self.recentFiles[self.script.shortname]) else: fullname = self.recentFiles[shortname] if os.path.isfile(fullname) and fullname != self.script.fullname: self.loadFile(fullname) if str(self.filenameComboBox.currentText()) != fullname: with BlockSignals(self.filenameComboBox) as w: w.setCurrentIndex( self.filenameComboBox.findText(shortname)) def onLoad(self): """The load button is clicked. Open file prompt for file.""" fullname, _ = QtWidgets.QFileDialog.getOpenFileName( self, 'Open Script', self.defaultDir, 'Python scripts (*.py *.pyw)') if fullname != "": self.loadFile(fullname) def loadFile(self, fullname): """Load in a file.""" logger = logging.getLogger(__name__) if fullname: self.script.fullname = fullname with open(fullname, "r") as f: self.script.code = f.read() self.textEdit.setPlainText(self.script.code) if self.script.shortname not in self.recentFiles: self.recentFiles[self.script.shortname] = fullname self.filenameComboBox.addItem(self.script.shortname) self.updateValidator() with BlockSignals(self.filenameComboBox) as w: ind = w.findText(self.script.shortname) w.removeItem(ind) w.insertItem(0, self.script.shortname) w.setCurrentIndex(0) logger.info('{0} loaded'.format(self.script.fullname)) self.initcode = copy.copy(self.script.code) def onRemoveCurrent(self): """Remove current button is clicked. Remove file from combo box.""" text = str(self.filenameComboBox.currentText()) ind = self.filenameComboBox.findText(text) self.filenameComboBox.setCurrentIndex(ind) self.filenameComboBox.removeItem(ind) if text in self.recentFiles: self.recentFiles.pop(text) self.updateValidator() def onSave(self): """Save action. Save file to disk, and clear any highlighted errors.""" logger = logging.getLogger(__name__) self.script.code = str(self.textEdit.toPlainText()) self.textEdit.clearHighlightError() if self.script.code and self.script.fullname: with open(self.script.fullname, 'w') as f: f.write(self.script.code) logger.info('{0} saved'.format(self.script.fullname)) self.initcode = copy.copy(self.script.code) try: importlib.machinery.SourceFileLoader( "UserFunctions", self.script.fullname).load_module() self.tableModel.updateData() ExprFunUpdate.dataChanged.emit('__exprfunc__') self.statusLabel.setText("Successfully updated {0}".format( self.script.fullname.split('\\')[-1])) self.statusLabel.setStyleSheet('color: green') except SyntaxError as e: self.statusLabel.setText("Failed to execute {0}: {1}".format( self.script.fullname.split('\\')[-1], e)) self.statusLabel.setStyleSheet('color: red') def saveConfig(self): """Save configuration.""" self.config[self.configname + '.recentFiles'] = self.recentFiles self.config[self.configname + '.script.fullname'] = self.script.fullname self.config[self.configname + '.isVisible'] = self.isVisible() self.config[self.configname + '.ScriptingUi.pos'] = self.pos() self.config[self.configname + '.ScriptingUi.size'] = self.size() self.config[ self.configname + ".splitterHorizontal"] = self.splitterHorizontal.saveState() self.config[self.configname + ".splitterVertical"] = self.splitterVertical.saveState() self.config[self.configname + ".evalstr"] = self.tableModel.exprList def show(self): pos = self.config.get(self.configname + '.ScriptingUi.pos') size = self.config.get(self.configname + '.ScriptingUi.size') splitterHorizontalState = self.config.get(self.configname + ".splitterHorizontal") splitterVerticalState = self.config.get(self.configname + ".splitterVertical") if pos: self.move(pos) if size: self.resize(size) if splitterHorizontalState: self.splitterHorizontal.restoreState(splitterHorizontalState) if splitterVerticalState: self.splitterVertical.restoreState(splitterVerticalState) QtWidgets.QDialog.show(self) def onAddRow(self): """add a row in expression tests""" self.tableModel.insertRow() def onRemoveRow(self): """remove row(s) in expression tests""" zeroColSelInd = self.tableView.selectedIndexes() if len(zeroColSelInd): initRow = zeroColSelInd[0].row() finRow = zeroColSelInd[-1].row() - initRow + 1 self.tableModel.removeRows(initRow, finRow) else: self.tableModel.removeRows(len(self.tableModel.exprList) - 1) def copy_to_clipboard(self): """ Copy the list of selected rows to the clipboard as a string. """ clip = QtWidgets.QApplication.clipboard() rows = sorted( unique([i.row() for i in self.tableView.selectedIndexes()])) clip.setText(str(rows)) def paste_from_clipboard(self): """ Append the string of rows from the clipboard to the end of the TODO list. """ clip = QtWidgets.QApplication.clipboard() row_string = str(clip.text()) try: row_list = list(map(int, row_string.strip('[]').split(','))) except ValueError: raise ValueError( "Invalid data on clipboard. Cannot paste into eval list") zeroColSelInd = self.tableView.selectedIndexes() initRow = zeroColSelInd[-1].row() self.tableModel.copy_rows(row_list, initRow) def onReorder(self, key): """reorder expression tests with pgup and pgdn""" if key in [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]: indexes = self.tableView.selectedIndexes() up = key == QtCore.Qt.Key_PageUp delta = -1 if up else 1 rows = sorted(unique([i.row() for i in indexes]), reverse=not up) if self.tableModel.moveRow(rows, delta): selectionModel = self.tableView.selectionModel() selectionModel.clearSelection() for index in indexes: selectionModel.select( self.tableModel.createIndex(index.row() + delta, index.column()), QtCore.QItemSelectionModel.Select) def updateValidator(self): """Make the validator match the recentFiles list. Uses regExp \\b(f1|f2|f3...)\\b, where fn are filenames.""" regExp = '\\b(' for shortname in self.recentFiles: if shortname: regExp += shortname + '|' regExp = regExp[:-1] #drop last pipe symbol regExp += ')\\b' self.filenameComboBox.validator().setRegExp(QtCore.QRegExp(regExp)) def onClose(self): self.saveConfig() self.hide()
def setupUi(self, parent): super(ScriptingUi, self).setupUi(parent) self.configname = 'Scripting' #initialize default options self.optionsWindow = OptionsWindow(self.config, 'ScriptingEditorOptions') self.optionsWindow.setupUi(self.optionsWindow) self.actionOptions.triggered.connect(self.onOpenOptions) self.optionsWindow.OptionsChangedSignal.connect(self.updateOptions) self.updateOptions() if self.optionsWindow.defaultExpand: onExpandOrCollapse(self.fileTreeWidget, True, True) #setup console self.consoleMaximumLines = self.config.get(self.configname+'.consoleMaximumLinesNew', 100) self.consoleEnable = self.config.get(self.configname+'.consoleEnable', True) self.consoleClearButton.clicked.connect( self.onClearConsole ) self.linesSpinBox.valueChanged.connect( self.onConsoleMaximumLinesChanged ) self.linesSpinBox.setValue( self.consoleMaximumLines ) self.checkBoxEnableConsole.stateChanged.connect( self.onEnableConsole ) self.checkBoxEnableConsole.setChecked( self.consoleEnable ) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=scriptFunctions) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine(QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor(QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #setup documentation list self.getDocs() self.docTreeWidget.setHeaderLabels(['Available Script Functions']) for funcDef, funcDesc in list(self.docDict.items()): itemDef = QtWidgets.QTreeWidgetItem(self.docTreeWidget, [funcDef]) self.docTreeWidget.addTopLevelItem(itemDef) QtWidgets.QTreeWidgetItem(itemDef, [funcDesc]) self.docTreeWidget.setWordWrap(True) #load recent files, also checks if data was saved correctly and if files still exist savedfiles = self.config.get( self.configname+'.recentFiles', OrderedList()) self.initRecentFiles(savedfiles) self.initComboBox() #load last opened file self.script.fullname = self.config.get( self.configname+'.script.fullname', '' ) self.initLoad() #connect buttons self.script.repeat = self.config.get(self.configname+'.repeat',False) self.repeatButton.setChecked(self.script.repeat) self.repeatButton.clicked.connect( self.onRepeat ) self.script.slow = self.config.get(self.configname+'.slow',False) self.slowButton.setChecked(self.script.slow) self.slowButton.clicked.connect( self.onSlow ) self.revert = self.config.get(self.configname+'.revert',False) self.revertButton.setChecked(self.revert) self.revertButton.clicked.connect( self.onRevert ) #File control actions self.actionOpen.triggered.connect( self.onLoad ) self.actionSave.triggered.connect( self.onSave ) self.actionReset.triggered.connect(self.onReset) self.actionNew.triggered.connect( self.onNew ) #Script control actions self.actionStartScript.triggered.connect( self.onStartScript ) self.actionPauseScript.triggered.connect( self.onPauseScript ) self.actionStopScript.triggered.connect( self.onStopScript ) self.actionPauseScriptAndScan.triggered.connect( self.onPauseScriptAndScan ) self.actionStopScriptAndScan.triggered.connect( self.onStopScriptAndScan ) #Script finished signal self.script.finished.connect( self.onFinished ) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/other/icons/Terminal-icon.png")) self.statusLabel.setText("Idle")
class ScriptingUi(FileTreeMixin, ScriptingWidget, ScriptingBase): """Ui for the scripting interface.""" def __init__(self, experimentUi): ScriptingWidget.__init__(self) ScriptingBase.__init__(self) self.config = experimentUi.config self.experimentUi = experimentUi self.recentFiles = dict() #dict of form {shortname: fullname}, where fullname has path and shortname doesn't self.defaultDir = Path(getProject().configDir+'/Scripts') self.script = Script(homeDir=self.defaultDir) #encapsulates the script self.scriptHandler = ScriptHandler(self.script, experimentUi) #handles interface to the script self.revert = False self.allowFileViewerLoad = True self.initcode = '' if not self.defaultDir.exists(): defaultScriptsDir = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'config/Scripts')) #/IonControl/config/Scripts directory shutil.copytree(defaultScriptsDir, str(self.defaultDir)) #Copy over all example scripts def setupUi(self, parent): super(ScriptingUi, self).setupUi(parent) self.configname = 'Scripting' #initialize default options self.optionsWindow = OptionsWindow(self.config, 'ScriptingEditorOptions') self.optionsWindow.setupUi(self.optionsWindow) self.actionOptions.triggered.connect(self.onOpenOptions) self.optionsWindow.OptionsChangedSignal.connect(self.updateOptions) self.updateOptions() if self.optionsWindow.defaultExpand: onExpandOrCollapse(self.fileTreeWidget, True, True) #setup console self.consoleMaximumLines = self.config.get(self.configname+'.consoleMaximumLinesNew', 100) self.consoleEnable = self.config.get(self.configname+'.consoleEnable', True) self.consoleClearButton.clicked.connect( self.onClearConsole ) self.linesSpinBox.valueChanged.connect( self.onConsoleMaximumLinesChanged ) self.linesSpinBox.setValue( self.consoleMaximumLines ) self.checkBoxEnableConsole.stateChanged.connect( self.onEnableConsole ) self.checkBoxEnableConsole.setChecked( self.consoleEnable ) #setup editor self.textEdit = PulseProgramSourceEdit() self.textEdit.setupUi(self.textEdit, extraKeywords1=[], extraKeywords2=scriptFunctions) self.textEdit.textEdit.currentLineMarkerNum = 9 self.textEdit.textEdit.markerDefine(QsciScintilla.Background, self.textEdit.textEdit.currentLineMarkerNum) #This is a marker that highlights the background self.textEdit.textEdit.setMarkerBackgroundColor(QtGui.QColor(0xd0, 0xff, 0xd0), self.textEdit.textEdit.currentLineMarkerNum) self.textEdit.setPlainText(self.script.code) self.splitterVertical.insertWidget(0, self.textEdit) #setup documentation list self.getDocs() self.docTreeWidget.setHeaderLabels(['Available Script Functions']) for funcDef, funcDesc in list(self.docDict.items()): itemDef = QtWidgets.QTreeWidgetItem(self.docTreeWidget, [funcDef]) self.docTreeWidget.addTopLevelItem(itemDef) QtWidgets.QTreeWidgetItem(itemDef, [funcDesc]) self.docTreeWidget.setWordWrap(True) #load recent files, also checks if data was saved correctly and if files still exist savedfiles = self.config.get( self.configname+'.recentFiles', OrderedList()) self.initRecentFiles(savedfiles) self.initComboBox() #load last opened file self.script.fullname = self.config.get( self.configname+'.script.fullname', '' ) self.initLoad() #connect buttons self.script.repeat = self.config.get(self.configname+'.repeat',False) self.repeatButton.setChecked(self.script.repeat) self.repeatButton.clicked.connect( self.onRepeat ) self.script.slow = self.config.get(self.configname+'.slow',False) self.slowButton.setChecked(self.script.slow) self.slowButton.clicked.connect( self.onSlow ) self.revert = self.config.get(self.configname+'.revert',False) self.revertButton.setChecked(self.revert) self.revertButton.clicked.connect( self.onRevert ) #File control actions self.actionOpen.triggered.connect( self.onLoad ) self.actionSave.triggered.connect( self.onSave ) self.actionReset.triggered.connect(self.onReset) self.actionNew.triggered.connect( self.onNew ) #Script control actions self.actionStartScript.triggered.connect( self.onStartScript ) self.actionPauseScript.triggered.connect( self.onPauseScript ) self.actionStopScript.triggered.connect( self.onStopScript ) self.actionPauseScriptAndScan.triggered.connect( self.onPauseScriptAndScan ) self.actionStopScriptAndScan.triggered.connect( self.onStopScriptAndScan ) #Script finished signal self.script.finished.connect( self.onFinished ) self.setWindowTitle(self.configname) self.setWindowIcon(QtGui.QIcon(":/other/icons/Terminal-icon.png")) self.statusLabel.setText("Idle") def onOpenOptions(self): self.optionsWindow.show() self.optionsWindow.setWindowState(QtCore.Qt.WindowActive) self.optionsWindow.raise_() def updateOptions(self): self.filenameComboBox.setMaxCount(self.optionsWindow.lineno) self.displayFullPathNames = self.optionsWindow.displayPath self.script.dispfull = self.optionsWindow.displayPath self.defaultExpandAll = self.optionsWindow.defaultExpand self.updateFileComboBoxNames(self.displayFullPathNames) @QtCore.pyqtSlot() def onStartScript(self): """Start script button is clicked""" if not self.script.isRunning(): logger = logging.getLogger(__name__) message = "script {0} started at {1}".format(self.script.fullname, str(datetime.now())) logger.info(message) self.writeToConsole(message, color='blue') self.onSave() self.enableScriptChange(False) self.actionPauseScript.setChecked(False) self.statusLabel.setText("Script running") if self.revert: self.savedState = True self.saveSettingsState() else: self.savedState = False self.scriptHandler.onStartScript() @QtCore.pyqtSlot(bool) def onPauseScript(self, paused): """Pause script button is clicked""" logger = logging.getLogger(__name__) message = "Script is paused" if paused else "Script is unpaused" markerColor = QtGui.QColor("#c0c0ff") if paused else QtGui.QColor(0xd0, 0xff, 0xd0) self.textEdit.textEdit.setMarkerBackgroundColor(markerColor, self.textEdit.textEdit.currentLineMarkerNum) logger.info(message) self.writeToConsole(message, color='blue') self.actionPauseScript.setChecked(paused) self.scriptHandler.onPauseScript(paused) @QtCore.pyqtSlot() def onStopScript(self): """Stop script button is clicked""" self.actionPauseScript.setChecked(False) self.repeatButton.setChecked(False) self.scriptHandler.onStopScript() @QtCore.pyqtSlot() def onPauseScriptAndScan(self): """Pause script and scan button is clicked""" logger = logging.getLogger(__name__) message = "Script is paused" markerColor = QtGui.QColor("#c0c0ff") self.textEdit.textEdit.setMarkerBackgroundColor(markerColor, self.textEdit.textEdit.currentLineMarkerNum) logger.info(message) self.writeToConsole(message, color='blue') self.actionPauseScript.setChecked(True) self.scriptHandler.onPauseScriptAndScan() @QtCore.pyqtSlot() def onStopScriptAndScan(self): """Stop script and scan button is clicked""" self.actionPauseScript.setChecked(False) self.repeatButton.setChecked(False) self.scriptHandler.onStopScriptAndScan() @QtCore.pyqtSlot() def onFinished(self): """Runs when script thread finishes. re-enables script GUI.""" logger = logging.getLogger(__name__) self.statusLabel.setText("Idle") message = "script {0} finished at {1}".format(self.script.fullname, str(datetime.now())) logger.info(message) self.writeToConsole(message, color='blue') self.textEdit.textEdit.markerDeleteAll() self.enableScriptChange(True) if self.revert and self.savedState: self.restoreSettingsState() @QtCore.pyqtSlot() def onRepeat(self): """Repeat button is clicked.""" logger = logging.getLogger(__name__) repeat = self.repeatButton.isChecked() message = "Repeat is on" if repeat else "Repeat is off" logger.debug(message) self.writeToConsole(message) self.scriptHandler.onRepeat(repeat) @QtCore.pyqtSlot() def onRevert(self): """Revert button is clicked.""" self.revert = self.revertButton.isChecked() logging.getLogger(__name__).debug("Revert is on" if self.revert else "Revert is off") @QtCore.pyqtSlot() def onSlow(self): """Slow button is clicked.""" logger = logging.getLogger(__name__) slow = self.slowButton.isChecked() message = "Slow is on" if slow else "Slow is off" logger.debug(message) self.writeToConsole(message) self.scriptHandler.onSlow(slow) @QtCore.pyqtSlot() def onNew(self): """New button is clicked. Pop up dialog asking for new name, and create file.""" logger = logging.getLogger(__name__) shortname, ok = QtWidgets.QInputDialog.getText(self, 'New script name', 'Enter new file name (optional path specified by localpath/filename): ') if ok: shortname = str(shortname) shortname = shortname.replace(' ', '_') #Replace spaces with underscores shortname = shortname.split('.')[0] + '.py'#Take only what's before the '.' fullname = self.defaultDir.joinpath(shortname) ensurePath(fullname.parent) if not fullname.exists(): try: with fullname.open('w') as f: newFileText = '#' + shortname + ' created ' + str(datetime.now()) + '\n\n' f.write(newFileText) except Exception as e: message = "Unable to create new file {0}: {1}".format(shortname, e) logger.error(message) return self.loadFile(fullname) self.populateTree(fullname) def enableScriptChange(self, enabled): """Enable or disable any changes to script editor""" color = QtGui.QColor("#ffe4e4") if enabled else QtGui.QColor('white') self.textEdit.textEdit.setCaretLineVisible(enabled) self.textEdit.textEdit.setCaretLineBackgroundColor(color) self.textEdit.setReadOnly(not enabled) self.filenameComboBox.setDisabled(not enabled) self.removeCurrent.setDisabled(not enabled) self.actionOpen.setEnabled(enabled) self.actionSave.setEnabled(enabled) self.actionReset.setEnabled(enabled) self.actionNew.setEnabled(enabled) self.actionStartScript.setEnabled(enabled) self.actionPauseScript.setEnabled(not enabled) self.actionStopScript.setEnabled(not enabled) self.actionPauseScriptAndScan.setEnabled(not enabled) self.actionStopScriptAndScan.setEnabled(not enabled) self.allowFileViewerLoad = enabled def onComboIndexChange(self, ind): """A name is typed into the filename combo box.""" if ind == 0: return False if self.script.code != str(self.textEdit.toPlainText()): if not self.confirmLoad(): self.filenameComboBox.setCurrentIndex(0) return False self.loadFile(self.filenameComboBox.itemData(ind)) def onLoad(self): """The load button is clicked. Open file prompt for file.""" fullname, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open Script', self.defaultDir, 'Python scripts (*.py *.pyw)') if fullname!="": self.loadFile(fullname) def loadFile(self, fullname): """Load in a file.""" logger = logging.getLogger(__name__) if fullname: self.script.fullname = fullname with fullname.open("r") as f: self.script.code = f.read() self.textEdit.setPlainText(self.script.code) if self.script.fullname not in self.recentFiles: self.filenameComboBox.addItem(self.script.shortname) self.recentFiles.add(fullname) with BlockSignals(self.filenameComboBox) as w: ind = w.findText(str(self.script.shortname)) #having issues with findData Path object comparison w.removeItem(ind) #these two lines just push the loaded filename to the top of the combobox w.insertItem(0, str(self.script.shortname)) w.setItemData(0, self.script.fullname) w.setCurrentIndex(0) logger.info('{0} loaded'.format(self.script.fullname)) self.initcode = copy.copy(self.script.code) def onReset(self): """Reset action. Reset file state saved on disk.""" if self.script.fullname: self.loadFile(self.script.fullname) def onRemoveCurrent(self): """Remove current button is clicked. Remove file from combo box.""" path = self.filenameComboBox.currentData() if path in self.recentFiles: self.recentFiles.remove(path) self.filenameComboBox.removeItem(0) self.loadFile(self.filenameComboBox.currentData()) def onSave(self): """Save action. Save file to disk, and clear any highlighted errors.""" logger = logging.getLogger(__name__) self.script.code = str(self.textEdit.toPlainText()) self.textEdit.clearHighlightError() if self.script.code and self.script.fullname: with self.script.fullname.open('w') as f: f.write(self.script.code) logger.info('{0} saved'.format(self.script.fullname)) def saveConfig(self): """Save configuration.""" self.config[self.configname+'.recentFiles'] = self.recentFiles self.config[self.configname+'.script.fullname'] = self.script.fullname self.config[self.configname+'.revert'] = self.revert self.config[self.configname+'.slow'] = self.script.slow self.config[self.configname+'.repeat'] = self.script.repeat self.config[self.configname+'.isVisible'] = self.isVisible() self.config[self.configname+'.ScriptingUi.pos'] = self.pos() self.config[self.configname+'.ScriptingUi.size'] = self.size() self.config[self.configname+".splitterHorizontal"] = self.splitterHorizontal.saveState() self.config[self.configname+".splitterVertical"] = self.splitterVertical.saveState() self.config[self.configname+'.consoleMaximumLinesNew'] = self.consoleMaximumLines self.config[self.configname+'.consoleEnable'] = self.consoleEnable def show(self): pos = self.config.get(self.configname+'.ScriptingUi.pos') size = self.config.get(self.configname+'.ScriptingUi.size') splitterHorizontalState = self.config.get(self.configname+".splitterHorizontal") splitterVerticalState = self.config.get(self.configname+".splitterVertical") if pos: self.move(pos) if size: self.resize(size) if splitterHorizontalState: self.splitterHorizontal.restoreState(splitterHorizontalState) if splitterVerticalState: self.splitterVertical.restoreState(splitterVerticalState) QtWidgets.QDialog.show(self) def onClose(self): self.saveConfig() self.hide() def onClearConsole(self): self.textEditConsole.clear() def onConsoleMaximumLinesChanged(self, maxlines): self.consoleMaximumLines = maxlines self.textEditConsole.document().setMaximumBlockCount(maxlines) def onEnableConsole(self, state): self.consoleEnable = state==QtCore.Qt.Checked def markLocation(self, lines): """mark a specified location""" if lines: self.textEdit.textEdit.markerDeleteAll() for line in lines: self.textEdit.textEdit.markerAdd(line-1, self.textEdit.textEdit.ARROW_MARKER_NUM) self.textEdit.textEdit.markerAdd(line-1, self.textEdit.textEdit.currentLineMarkerNum) def markError(self, lines, message): """mark error at specified lines, and show message""" if lines != []: for line in lines: self.textEdit.highlightError(message, line) def writeToConsole(self, message, error=False, color=''): if self.consoleEnable: message = str(message) cursor = self.textEditConsole.textCursor() cursor.movePosition(QtGui.QTextCursor.End) textColor = ('red' if error else 'black') if color=='' else color self.textEditConsole.setUpdatesEnabled(False) if textColor == 'black': self.textEditConsole.insertPlainText(message+'\n') else: self.textEditConsole.insertHtml(str('<p><font color='+textColor+'>'+message+'</font><br></p>')) self.textEditConsole.setUpdatesEnabled(True) self.textEditConsole.setTextCursor(cursor) self.textEditConsole.ensureCursorVisible() def getDocs(self): """Assemble the script function documentation into a dictionary""" self.docDict = OrderedDict() for doc in scriptDocs: docsplit = doc.splitlines() defLine = docsplit.pop(0) docsplit = [line.strip() for line in docsplit] docsplit = '\n'.join(docsplit) self.docDict[defLine] = docsplit def updateValidator(self): """Make the validator match the recentFiles list. Uses regExp \\b(f1|f2|f3...)\\b, where fn are filenames.""" regExp = '\\b(' for shortname in self.recentFiles: if shortname: regExp += shortname + '|' regExp = regExp[:-1] #drop last pipe symbol regExp += ')\\b' self.filenameComboBox.validator().setRegExp(QtCore.QRegExp(regExp)) def saveSettingsState(self): """Save the state of the scan, evaluation, and analysis""" self.originalState = dict() self.originalState['scan'] = self.experimentUi.tabDict['Scan'].scanControlWidget.settingsName self.originalState['evaluation'] = self.experimentUi.tabDict['Scan'].evaluationControlWidget.settingsName self.originalState['analysis'] = self.experimentUi.tabDict['Scan'].analysisControlWidget.currentAnalysisName def restoreSettingsState(self): """Restore the settings to their original values""" for name, value in self.scriptHandler.globalVariablesRevertDict.items(): self.experimentUi.globalVariablesUi.model.update([('Global', name, value)]) self.experimentUi.tabDict['Scan'].scanControlWidget.loadSetting(self.originalState['scan']) self.experimentUi.tabDict['Scan'].evaluationControlWidget.loadSetting(self.originalState['evaluation']) self.experimentUi.tabDict['Scan'].analysisControlWidget.onLoadAnalysisConfiguration(self.originalState['analysis'])