#!/usr/bin/env python # -*- coding: utf-8 -*- """ TASTE ASN.1 Value Editor Main GUI widget Copyright (c) 2012-2015 European Space Agency Designed and implemented by Maxime Perrotin Contact: maxime.perrotin@esa.int License is LGPLv3 - Check the LICENSE file """ __author__ = "Maxime Perrotin" __license__ = "LGPLv3" __version__ = "1.1.4" __url__ = "http://taste.tuxfamily.org" import sys try: from PySide import * from PySide.QtCore import * from PySide.QtGui import * from PySide.QtUiTools import * except ImportError as err: print 'Pyside not found (package python-pyside missing)...' + str(err) import vn MIN_RANGE = Qt.UserRole + 1 MAX_RANGE = Qt.UserRole + 2 CHOICE_LIST = Qt.UserRole + 1 PLOTTERS = Qt.UserRole + 4 ASN1TYPE = Qt.UserRole class myTextEdit(QTextEdit): ''' Customized text editor that contains a context menu for loading data from a file ''' def __init__(self, parent=None): super(myTextEdit, self).__init__(parent) def contextMenuEvent(self, event): ''' When the context menu is open, add the Load from file action and open the menu ''' myMenu = self.createStandardContextMenu() myAction = 'Load data from file' myMenu.addAction(myAction) action = myMenu.exec_(event.globalPos()) if action is not None and action.text() == myAction: filename = QFileDialog.getOpenFileName(self, "Load string from file", ".", "All (*)")[0] if len(filename) == 0: return stringFile = QFile(filename) stringFile.open(QIODevice.ReadOnly) stringData = str(stringFile.readData(stringFile.size())) self.setText(stringData) stringFile.close() class TreeDelegate(QStyledItemDelegate): '''Tree Delegate allows to define specific editors for each cell of the tree view. This way, e.g. ASN.1 ENUMERATED values can be selected using a combo box, while INTEGERS values can be set using a spin box. Three functions have been redefined: (1) createEditor, which sets an editor to a cell, using the cell attributes to learn about the ASN.1 basic type (stored in Qt.UserRole) (2) SetEditorData, which is used at runtime to put the value in the given editor when the user clicks on the cell (3) SetModelData, which is called when the user is done with the editing: it reads the value from the editor and place it back in the model ''' seqof = Signal(QModelIndex, int, int) choice = Signal(QModelIndex, int, int) def __init__(self, oParent=None): super(TreeDelegate, self).__init__(oParent) def createEditor(self, parent, option, index): ''' Define the delegate (editor) to use for a given cell. index.data() returns the actual value, and index.data(role) points to some user data when role >= 32. 32 is Qt.UserRole. ''' # Check the (user-defined) type associated with the cell asnType = index.data(ASN1TYPE) if asnType == 'INTEGER': editor = QSpinBox(parent) # Qt's int is 32-bits signed, so we may get overflows try: editor.setMinimum(index.data(MIN_RANGE)) except OverflowError: editor.setMinimum(sys.maxint) try: editor.setMaximum(index.data(MAX_RANGE)) except OverflowError: editor.setMaximum(sys.maxint) elif asnType == 'SEQOF': editor = QSpinBox(parent) minVal = index.data(MIN_RANGE) maxVal = index.data(MAX_RANGE) editor.setRange(minVal, maxVal) elif asnType in ('ENUMERATED', 'CHOICE'): editor = QComboBox(parent) enumVal = index.data(CHOICE_LIST) for val in enumVal: editor.addItem(val) elif asnType == 'REAL': editor = QDoubleSpinBox(parent) minVal = index.data(MIN_RANGE) maxVal = index.data(MAX_RANGE) editor.setRange(float(minVal), float(maxVal)) elif asnType == 'STRING': editor = myTextEdit(parent) # maxLen = index.data(MAX_RANGE) # editor.setMaxLength(maxLen) (not supported by QTextEdit, only QLineEdit) elif asnType == 'BOOLEAN': editor = QComboBox(parent) boolVal = index.data(CHOICE_LIST) for val in boolVal: editor.addItem(val) # I do not use QCheckBox because the position of the box changes when # the cell is edited, which is not good-looking #editor = QCheckBox(parent) #defState = index.data(Qt.CheckStateRole) #editor.setCheckState(Qt.CheckState(defState)) else: return # Non-editable cells return editor def setEditorData(self, editor, index): ''' Set the curent value of the editor (when cell is clicked) in principle, take it from the model (index.data() = value) ''' asnType = index.data(ASN1TYPE) if asnType in ('INTEGER', 'SEQOF'): editor. setValue(int(index.data())) elif asnType in ('ENUMERATED', 'CHOICE'): idx = editor.findText(index.data()) editor.setCurrentIndex(idx) #editor. setEditText(index.data()) elif asnType == 'REAL': editor.setValue(float(index.data())) elif asnType == 'STRING': editor.setText(index.data()) elif asnType == 'BOOLEAN': idx = editor.findText(index.data()) editor.setCurrentIndex(idx) #isChecked = index.model().data(index, Qt.CheckStateRole) #editor.setCheckState(Qt.CheckState(isChecked)) else: pass def setModelData(self, editor, model, index): ''' Once a choice has been made (by the user) take the value from the corresponding editor, and set it back in the model ''' asnType = index.data(ASN1TYPE) if asnType == 'INTEGER': val = editor.value() model.setData(index, val) elif asnType == 'SEQOF': val = editor.value() model.setData(index, val) # Sequence of: display only the corresponding number of elements self.seqof.emit(index.sibling(index.row(), 0), val, index.data(MAX_RANGE)) elif asnType == 'ENUMERATED': val = editor.currentText() model.setData(index, val) elif asnType == 'CHOICE': val = editor.currentText() model.setData(index, val) # display only the corresponding choice definition self.choice.emit(index.sibling(index.row(), 0), len(index.data(CHOICE_LIST)), editor.currentIndex()) elif asnType == 'REAL': val = editor.value() model.setData(index, val) elif asnType == 'STRING': val = editor.toPlainText() model.setData(index, val) elif asnType == 'BOOLEAN': val = editor.currentText() model.setData(index, val) #if editor.isChecked(): model.setData (index, 'True') #else: model.setData (index, 'False') #model.setData (index, editor.checkState(),Qt.CheckStateRole) else: pass def updateEditorGeometry(self, editor, option, index): ''' Set editor geometry (bigger box for string types) ''' asnType = index.data(ASN1TYPE) if asnType == 'STRING': r = QRect(option.rect.x(), option.rect.y(), option.rect.width(), max(option.rect.height(), 100)) else: r = option.rect editor.setGeometry(r) class asn1Editor(QTreeView): ''' ASN.1 Fields editor to define, load, save and/or send TC ''' expandTree = Signal() expandBranch = Signal(QModelIndex) statusBarMessage = Signal(str) msc = Signal(unicode, unicode) # Whenever a new TM is received, the new_tm signal is emitted new_tm = Signal() def __init__(self, parent=None): super(asn1Editor, self).__init__(parent) self.model = QStandardItemModel(1, 4) self.model.setHorizontalHeaderLabels(['Field', 'Type', 'Constraints', 'Value']) self.delegate = TreeDelegate() self.setItemDelegate(self.delegate) # Set selection mode and behavior to allow multiple rows to be selected with Ctrl/Shift keys self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setModel(self.model) self.setAlternatingRowColors(True) # Set Editing triggers so that a single click in a cell opens the editor self.setEditTriggers(QAbstractItemView.AllEditTriggers) # If we later want to drag & drop values (to plot, meter..): self.setDragEnabled(False) # When the number of elements for a sequence of is changed, signal "seqof" # is triggered by the delegate. Same for choice. self.delegate.seqof.connect(self.seqofDisplay) self.delegate.choice.connect(self.choiceDisplay) self.expandTree.connect(self.expandAll) self.expandBranch.connect(self.expand) self.plotterBackend = None self.backend = None self.plottedIdxs = [] self.log = None self.pendingTM = None # item is the ASN.1 node in Pyside format from datamodel.py self.item = None # List of classes that have an "update" method (user custom widgets) self.tm_clients = [] def hideExtraColumns(self): ''' Hide the columns containing the type and constraint ''' self.setColumnHidden(1, True) self.setColumnHidden(2, True) def showExtraColumns(self): ''' Show the columns containing the type and constraint ''' self.setColumnHidden(1, False) self.setColumnHidden(2, False) def setAsn1Model(self, dataview, row=0): self.item = dataview rootItem = self.addItem(self.item) self.treeItem = rootItem["item"] self.model.setItem(row, 1, QStandardItem(rootItem["type"])) self.model.setItem(row, 2, QStandardItem(rootItem["constraint"])) self.model.setItem(row, 3, QStandardItem(rootItem["value"])) # Add the item to the actual tree. 0,0 = 1st row, 1st column self.model.setItem(row, 0, self.treeItem) self.setWindowTitle('ASN.1 Variable editor') self.hideUnusedFields(self.treeItem, start=True, row=row) self.setColumnWidth(0, 200) self.setColumnWidth(1, 150) self.expandAll() return rootItem["item"] def hideUnusedFields(self, root, start=False, row=0): ''' Hide CHOICE unselected fields and SEQUENCE OF out-of-range fields ''' rootType = self.model.item(row, 1).text() if rootType == 'CHOICE' and start: choices = self.model.item(row, 3).data(CHOICE_LIST) currValue = self.model.item(row, 3).text() for j in range(len(choices)): if currValue == choices[j]: break rowIndex = self.model.item(row).index() self.choiceDisplay(rowIndex, len(choices), j) elif rootType == 'SEQOF' and start: maxValue = self.model.item(row, 3).data(MAX_RANGE) currValue = self.model.item(row, 3).text() rowIndex = self.model.item(row).index() self.seqofDisplay(rowIndex, currValue, maxValue) if root.hasChildren(): for i in range(root.rowCount()): # for each row self.hideUnusedFields(root.child(i)) # check recursively asnType = root.child(i, 1).text() if asnType == 'SEQOF': maxValue = root.child(i, 3).data(MAX_RANGE) currValue = root.child(i, 3).text() rowIndex = root.child(i).index() self.seqofDisplay(rowIndex, currValue, maxValue) elif asnType == 'CHOICE': choices = root.child(i, 3).data(CHOICE_LIST) currValue = root.child(i, 3).text() for j in range(len(choices)): if currValue == choices[j]: break rowIndex = root.child(i).index() self.choiceDisplay(rowIndex, len(choices), j) def parseModel(self, root, nbRows=-1): ''' Parse the model to get all field values recursively (root type is QStandardItem) ''' if root.hasChildren(): resValue = {} seqofValue = [] seqOf = False if nbRows == -1: nbRows = root.rowCount() else: seqOf = True for i in range(nbRows): # for each child row name = root.child(i, 0).text() asnType = root.child(i, 1).text() # Value appears with underscore in the GUI but keys use ASN.1 value = root.child(i, 3).text().replace('_', '-') if asnType in ('SEQUENCE', 'SET'): seqValue = self.parseModel(root.child(i)) # parse recursively resValue[name] = seqValue if seqOf: seqofValue.append(seqValue) elif asnType == 'SEQOF': seqOfValue = self.parseModel(root.child(i), nbRows=int(value)) resValue[name] = seqOfValue if seqOf: seqofValue.append(seqOfValue) elif asnType == 'CHOICE': choiceValue = self.parseModel(root.child(i)) # parse recursively resValue[name] = {"Choice": value, value: choiceValue[value]} if seqOf: seqofValue.append(resValue[name]) elif asnType == 'INTEGER': resValue[name] = int(value) if seqOf: seqofValue.append(int(value)) elif asnType == 'REAL': resValue[name] = float(value) if seqOf: seqofValue.append(float(value)) elif asnType == 'BOOLEAN': if value == 'True': boolValue = True else: boolValue = False resValue[name] = boolValue if seqOf: seqofValue.append(boolValue) elif asnType == 'ENUMERATED': resValue[name] = {'Enum': value} if seqOf: seqofValue.append(resValue[name]) else: # Strings resValue[name] = value if seqOf: seqofValue.append(value) if seqOf: return seqofValue else: return resValue else: # single elements (all but SeqOF, Choice, Sequence) row = root.row() try: value = self.model.item(row, 3).text() asnType = self.model.item(row, 1).text() except AttributeError: # Empty SEQUENCEs elems contain nothing value = '' asnType = None if asnType == 'INTEGER': value = int(value) elif asnType == 'REAL': value = float(value) elif asnType == 'BOOLEAN': value = True if value == 'True' else False elif asnType == 'ENUMERATED': value = {'Enum': value} else: pass return value def getVariable(self, root=None): ''' Read the ASN.1 variable from the tree editor ''' root = root or self.treeItem row = root.row() name = root.text() asnType = self.model.item(row, 1).text() if asnType == 'SEQOF': nbRows = int(self.model.item(row, 3).text()) value = self.parseModel(root, nbRows) return {name: value} elif asnType == 'CHOICE': value = self.model.item(row, 3).text() choiceValue = self.parseModel(root) return {name: {"Choice": value, value: choiceValue[value]}} else: value = self.parseModel(root) return {name: value} def updateModel(self, root, var, nbRows=-1): ''' Parse the model and update values ''' if root.hasChildren(): seqOf = False if nbRows == -1: nbRows = root.rowCount() else: seqOf = True for i in range(nbRows): name = root.child(i, 0).text() name_u = name.replace('-', '_') asnType = root.child(i, 1).text() child = root.child(i, 3) if seqOf: value = var[i] else: if name in var or name_u in var: value = var.get(name, var[name_u]) else: continue if asnType in ('INTEGER', 'REAL', 'SEQOF'): plotters = root.child(i, 3).data(PLOTTERS) if plotters is not None: self.log.debug("updateModel - updatePlot call") for plotter in plotters: self.plotterBackend.updatePlot(plotter, value) self.log.debug("updateModel - updatePlot end") if asnType in ('INTEGER', 'REAL', 'BOOLEAN', 'STRING'): child.setText(str(value)) elif asnType == 'ENUMERATED': child.setText(value['Enum']) elif asnType in ('SEQUENCE', 'SET'): self.updateModel(root.child(i), value) # update recursively elif asnType == 'CHOICE': child.setText(str(value['Choice'])) self.updateModel(root.child(i), value) # update recursively elif asnType == 'SEQOF': child.setText(str(len(value))) self.updateModel(root.child(i), value, len(value)) def updateVariable(self, var, root=None): ''' Update the variable value - used when loading a TC or receiving a TM ''' root = root or self.treeItem row = root.row() name = root.text() value = var[name] asnType = self.model.item(row, 1).text() if asnType == 'SEQOF': self.model.item(row, 3).setText(str(len(value))) self.updateModel(root, var[name], len(value)) elif asnType == 'CHOICE': self.model.item(row, 3).setText(str(value['Choice'])) self.updateModel(root, value) elif asnType in ('INTEGER', 'REAL', 'STRING', 'BOOLEAN'): self.model.item(row, 3).setText(str(value)) elif asnType == 'ENUMERATED': self.model.item(row, 3).setText(value['Enum']) else: # SEQUENCE or SET self.updateModel(root, value) if asnType in ('INTEGER', 'REAL', 'SEQOF'): plotters = self.model.item(row, 3).data(PLOTTERS) if plotters != None: for plotter in plotters: self.plotterBackend.updatePlot(plotter, value) self.hideUnusedFields(root, True, row=row) if self.plotterBackend is not None: self.plotterBackend.refresh() # Inform the thread to update the tree (expand all branches) self.expandTree.emit() def tmToEditor(self, pythonVar, emit_msc=True): ''' Check validity of a TM and update the viewer with the value ''' self.log.debug("Entering tmToEditor") if pythonVar == {}: self.log.error('Error decoding ' + self.treeItem.text() + ' TM') self.statusBarMessage.emit('Error decoding ' + self.treeItem.text() + ' TM') else: self.updateVariable(pythonVar) for name in pythonVar: # Send value to the MSC recorder asnVN = vn.toASN1ValueNotation(pythonVar[name]) msg = name + '(' + asnVN + ')' self.log.debug('Emitting input to MSC') # + msg) if emit_msc: self.msc.emit('in', msg) self.new_tm.emit() self.log.debug("Leaving tmToEditor") def ProcessTM(self, rawMsg): ''' Decode a TM received from the msgQ (not UDP) and update the model ''' self.log.debug("Entering ProcessTM") native_tm = self.backend.decode_TM(rawMsg) pythonVar = self.backend.fromASN1ToPyside(native_tm) self.tmToEditor(pythonVar) for each in self.tm_clients: each.update(native_tm) self.log.debug("Leaving ProcessTM") @Slot() def receivedTM(self): ''' TM received from another thread (polling queue) ''' if self.pendingTM: self.ProcessTM(self.pendingTM) self.pendingTM = None def ProcessUDPTM(self, uperMsg): ''' Decode an uPER-encoded TM (e.g. received from UDP sockets) and update the model ''' native_tm = self.backend.decode_uPER(uperMsg) pythonVar = self.backend.fromASN1ToPyside(native_tm) self.tmToEditor(pythonVar) def saveTC(self): ''' Encode the data using GSER (ASN.1 Value Notation) and save it to a file''' data = self.getVariable() for name in data: asnVN = vn.toASN1ValueNotation(data[name]).replace('_', '-') self.log.debug('Saving ' + asnVN) #uPER_buffer = self.backend.encode_uPER(native_tc) filename = QFileDialog.getSaveFileName(self, "Save TC", ".", "ASN.1 Variable (*.tc)")[0] try: if filename.split('.')[-1] != 'tc': filename += ".tc" tcFile = QFile(filename) tcFile.open(QIODevice.WriteOnly | QIODevice.Text) #tcFile.write(uPER_buffer) tcFile.write(str(asnVN)) tcFile.close() except: pass @property def gser(self, use_dash=False): ''' Helper API function: return the GSER representation of the data ''' data, = self.getVariable().values() raw = vn.toASN1ValueNotation(data).strip() return raw if not use_dash else raw.replace('_', '-') def to_asn1scc_swig(self, root, dest, ASN1Swig, sort, ASN1_AST): ''' Helper API function: read variable and set ASN.1 SWIG variable Inputs: root: select which variable to parse dest: Swig variable (will be modified) ASN1Swig: python module containing SWIG DV access sort: root ASN.1 typename (with dash, no underscores) ASN1_AST: full AST generated by ASN1SCC ''' var = self.getVariable(root).popitem()[1] vn.valueNotationToSwig(gser=None, var=var, dest=dest, ASN1Swig=ASN1Swig, sort=sort, ASN1_AST=ASN1_AST) def sendTC(self): ''' Encode and send the TC to the main TASTE binary using message queue or UDP ''' data = self.getVariable() for name in data: asnVN = vn.toASN1ValueNotation(data[name]).replace('_', '-').strip() msg = name + '(' + asnVN + ')' self.log.debug('out ' + msg) self.msc.emit('out', msg) asnVal = self.backend.fromPysideToASN1(data) self.backend.sendTC(asnVal) def loadTC(self): ''' Load/Decode a TC and populate the values in the model ''' filename = QFileDialog.getOpenFileName(self, "Open TC", ".", "ASN.1 Variable (*.tc)")[0] if len(filename) == 0: return try: tcFile = QFile(filename) tcFile.open(QIODevice.ReadOnly | QIODevice.Text) asnVN = str(tcFile.readData(tcFile.size())) tcFile.close() pythonVar = vn.fromValueNotationToPySide(self.treeItem.text(), asnVN) self.updateVariable(pythonVar) except: pass def dataPath(self, idx): ''' Create a textual representation of a field path (e.g. myTM.myChoice.position.x) ''' if not idx.isValid(): return '' subpath = self.dataPath(idx.parent()) if len(subpath)>0: subpath += '.' subpath += idx.data() return subpath def pathToIdx(self, path, root=None): ''' Find an index in the model based from a dot-splitted path representation ''' if root is None: root = self.treeItem.index() if len(path) == 1: return QPersistentModelIndex(root) if root.data() == path[0] else None else: # Recursive call for each child for i in range(self.model.itemFromIndex(root).rowCount()): result = self.pathToIdx(path[1:], root.child(i, 0)) if result is not None: return result def newPlot(self, fifoId=-1, meter=False): ''' Add a set of data (based on selected lines) to a new plotter or speedometer ''' selectedIdxs = self.selectedIndexes() # Count the number of eligible lines for plotting (discard non-numerical types) eligible=[] for idx in selectedIdxs: if idx.column() > 0: continue asntype = idx.sibling(idx.row(), 1).data() if asntype in ('INTEGER', 'REAL', 'SEQOF'): self.log.info('Candidate for plot: '+ self.dataPath(idx)) discard = False if fifoId > 0: currData=idx.sibling(idx.row(), 3).data(PLOTTERS) if currData is not None: for elem in currData: if elem['fifoId'] == fifoId: discard = True if not discard: eligible.append(idx.sibling(idx.row(), 3)) else: self.log.info('(but discarding because already in current plot)') else: self.log.warning('Cannot plot {data} (type {asntype} is not numerical)'.format(data=idx.data(), asntype=asntype)) if len(eligible) == 0: self.log.warning('No valid lines selected for the plot') self.statusBarMessage.emit('No valid lines selected for the plot') return if fifoId == -1 and not meter: fifoId = self.plotterBackend.newPlot() for idx in eligible: dataPath = self.dataPath(idx.sibling(idx.row(), 0)) currData = idx.data(PLOTTERS) minRange = idx.data(MIN_RANGE) maxRange = idx.data(MAX_RANGE) if meter: fifoId = self.plotterBackend.newMeter(dataPath, minRange, maxRange) if currData is None: currData = [] currData.append({'fifoId': int(fifoId), 'curveName': dataPath}) idx.model().setData(idx, currData, PLOTTERS) # Keep track of all plotted values - used when saving the model state persistentIdx = QPersistentModelIndex(idx) if persistentIdx not in self.plottedIdxs: self.plottedIdxs.append(persistentIdx) def addToPlot(self): ''' Add a new curve to an existing plot window ''' fifoId = self.sender().text().split()[-1] self.newPlot(int(fifoId)) def setPlotterBackend(self, backend): ''' Set a "pointer" to the GnuPlot/Meter backend module ''' self.plotterBackend = backend def meter(self): ''' Create speedometers for the selected values ''' self.newPlot(meter=True) def custom(self, custom_class): ''' Handle user-defined widgets: for TC, provide access to the Send and Update APIs. Defined differently for the asn1Viwer (TM) class ''' custom_handler = custom_class(self.item['nodeTypename'] .replace('-', '_'), parent=self) # Attach the docking widget to the GUI (XXX refactor) self.parent().parent().parent().addDockWidget(Qt.RightDockWidgetArea, custom_handler) custom_handler.setFloating(True) def displaytip(self, index): ''' Display a tip when the mouse moves above a cell ''' asnType = index.sibling(index.row(), 1).data() constraint = index.sibling(index.row(), 2).data() self.statusBarMessage.emit(asnType + ' ' + (constraint if constraint is not None else '')) @Slot(QModelIndex, int, int) def seqofDisplay(self, parent, val, max): ''' Slot updating the number of SEQOF elements displayed ''' for row in range(max): if int(row) < int(val): display = False else: display = True self.setRowHidden(row, parent, display) self.expandBranch.emit(parent) @Slot(QModelIndex, int, int) def choiceDisplay(self, parent, nbOfElem, choice): ''' Slot updating the current selected CHOICE element ''' for row in range(nbOfElem): if int(choice) == int(row): display = False else: display = True self.setRowHidden(row, parent, display) self.expandBranch.emit(parent) def addInteger(self, elem): # Set default value (min range): val = QStandardItem('%d' % elem["minR"]) # Define type attributes, later used to define the proper cell editor val.setData(elem["type"], ASN1TYPE) try: val.setData(elem["minR"], MIN_RANGE) except OverflowError: val.setData(-1000000, MIN_RANGE) try: val.setData(elem["maxR"], MAX_RANGE) except OverflowError: val.setData(1000000, MAX_RANGE) # Set the text for the constraint and add it to the 3rd column: constraint = QStandardItem('(%d..%d)' % (elem["minR"], elem["maxR"])) constraint.setData(QBrush(QColor("gray")), Qt.ForegroundRole) return {"value": val, "constraint": constraint} def addReal(self, elem): # Set default value (min range): val = QStandardItem('%.2f' % elem["minR"]) # Define type attributes, later used to define the proper cell editor val.setData(elem["type"], ASN1TYPE) try: val.setData(elem["minR"], MIN_RANGE) except OverflowError: # Python cannot handle 64bit double - reducing the range in case of overflow val.setData(-10000000.0, MIN_RANGE) try: val.setData(elem["maxR"], MAX_RANGE) except OverflowError: val.setData(10000000.0, MAX_RANGE) # Set the text for the constraint and add it to the 3rd column: constraint = QStandardItem('(%.2f..%.2f)' % (elem["minR"], elem["maxR"])) constraint.setData(QBrush(QColor("gray")), Qt.ForegroundRole) return {"value": val, "constraint": constraint} def addEnum(self, elem): # Set default value (first enum value) val = QStandardItem(elem["values"][0]) # Define type attributes, later used to define the proper cell editor val.setData(elem["type"], ASN1TYPE) # type (ENUMERATED) val.setData(elem["values"], CHOICE_LIST) # enum values constraint = QStandardItem() return {"value": val, "constraint": constraint} def addString(self, elem): # Set default value (empty string) if "default" in elem: defaultString = elem["default"] else: defaultString = "" val = QStandardItem(defaultString) # Define type attributes, later used to define the proper cell editor val.setData(elem["type"], ASN1TYPE) # type (IA5String) val.setData(elem["minSize"], MIN_RANGE) # size min val.setData(elem["maxSize"], MAX_RANGE) # size max # Set the text for the constraint and add it to the 3rd column: if elem["minSize"] == elem["maxSize"]: constraintStr = 'SIZE(%d)' % elem["minSize"] else: constraintStr = 'SIZE(%d..%d)' % (elem["minSize"], elem["maxSize"]) constraint = QStandardItem(constraintStr) constraint.setData(QBrush(QColor("gray")), Qt.ForegroundRole) return {"value": val, "constraint": constraint} def addBool(self, elem): # Set default value (True or False) val = QStandardItem(elem["default"]) # Define type attributes, later used to define the proper cell editor val.setData(elem["type"], ASN1TYPE) # type (BOOLEAN) val.setData(['True', 'False'], CHOICE_LIST) # enum values constraint = QStandardItem() return {"value": val, "constraint": constraint} def addSequence(self, elem, parent): types = [] values = [] constraints = [] for child in elem["children"]: field = self.addItem(child) parent.appendRow(field["item"]) types.append(field["type"]) values.append(field["value"]) constraints.append(field["constraint"]) parent.appendColumn(types) parent.appendColumn(constraints) parent.appendColumn(values) constraint = QStandardItem() val = QStandardItem() return {"value": val, "constraint": constraint} def addSeqOf(self, elem, parent): types = [] values = [] constraints = [] for i in range(elem["maxSize"]): elem["seqoftype"]["id"] = "elem_%d" % i field = self.addItem(elem["seqoftype"]) parent.appendRow(field["item"]) types.append(field["type"]) values.append(field["value"]) constraints.append(field["constraint"]) parent.appendColumn(types) parent.appendColumn(constraints) parent.appendColumn(values) if elem["minSize"] == elem["maxSize"]: constraintStr = 'SIZE(%d)' % elem["minSize"] else: constraintStr = 'SIZE(%d..%d)' % (elem["minSize"], elem["maxSize"]) constraint = QStandardItem(constraintStr) constraint.setData(QBrush(QColor("gray")), Qt.ForegroundRole) val = QStandardItem('%d' % elem["minSize"]) val.setData(elem["type"], ASN1TYPE) # type (SEQOF) val.setData(elem["minSize"], MIN_RANGE) # min number of elements val.setData(elem["maxSize"], MAX_RANGE) # max number of elements return {"value": val, "constraint": constraint} def addChoice(self, elem, parent): types = [] values = [] constraints = [] ids = [] for choice in elem["choices"]: field = self.addItem(choice) ids.append(choice["id"]) parent.appendRow(field["item"]) types.append(field["type"]) values.append(field["value"]) constraints.append(field["constraint"]) parent.appendColumn(types) parent.appendColumn(constraints) parent.appendColumn(values) val = QStandardItem(ids[0]) # default value: first choice # Define type attributes, later used to define the proper cell editor val.setData(elem["type"], ASN1TYPE) # type (CHOICE) val.setData(ids, CHOICE_LIST) # choice values constraint = QStandardItem() return {"value": val, "constraint": constraint} def addItem(self, elem): # Add field name on the first column: field = QStandardItem(elem["id"]) # Add type on the second column in gray: asnType = QStandardItem(elem["type"]) asnType.setData(QBrush(QColor("gray")), Qt.ForegroundRole) # get the Value and Contraints fields for the different types: if elem["type"] == 'INTEGER': data = self.addInteger(elem) elif elem["type"] == 'REAL': data = self.addReal(elem) elif elem["type"] == 'BOOLEAN': data = self.addBool(elem) elif elem["type"] == 'ENUMERATED': data = self.addEnum(elem) elif elem["type"] == 'STRING': data = self.addString(elem) elif elem["type"] in ('SEQUENCE', 'SET'): data = self.addSequence(elem, field) elif elem["type"] == 'SEQOF': data = self.addSeqOf(elem, field) elif elem["type"] == 'CHOICE': data = self.addChoice(elem, field) else: print 'You are using a non-supported type: ' + elem["type"] sys.exit(-1) return {"item": field, "type": asnType, "value": data["value"], "constraint": data["constraint"]} class asn1Viewer(asn1Editor): ''' TM Viewing class. Fields are not editable ''' def __init__(self, parent=None): super(asn1Viewer, self).__init__(parent) self.setEditTriggers(QAbstractItemView.NoEditTriggers) def custom(self, custom_class): ''' Handle user-defined widgets: for TM make sure a signal is emitted with ASN1 value when a TM is received ''' custom_handler = custom_class(parent=self) # Attach the docking widget to the GUI (XXX refactor) self.parent().parent().parent().addDockWidget(Qt.RightDockWidgetArea, custom_handler) self.tm_clients.append(custom_handler) self.new_tm.connect(custom_handler.new_tm) custom_handler.setFloating(True)