def update(self): ''' uodate the model of the MENU (header)''' N_index = len(self.df.index) for name in self.df.columns: # append a table with three columns (Name, Data-Type and N_NaNs). Each row represents # a data-column in the input dataframe object item_name = QtGui.QStandardItem(asUnicode('{0}'.format(name.encode('UTF-8')))) item_name.setCheckable(True) item_name.setEditable(False) item_name.setCheckState(Qt.Checked) dt = self.df[name].dtype if isinstance(self.df, pd.DataFrame) else self.df.dtype item_type = QtGui.QStandardItem(asUnicode('{0}'.format(dt))) item_type.setEditable(False) if dt == type(object): # if the data-type of the column is not numeric or datetime, then it's propabply has not been # loaded successfully. Therefore, explicitly change the font color to red, to inform the user. item_type.setForeground(RED_FONT) n_nans = N_index - (self.df[name].count() if isinstance(self.df, pd.DataFrame) else self.df.count()) item_nans = QtGui.QStandardItem(asUnicode('{0}'.format(n_nans))) item_nans.setEditable(False) if n_nans > 0: item_nans.setForeground(RED_FONT) self.appendRow([item_name, item_type, item_nans]) self.setHorizontalHeaderLabels(['Name', 'Data-type', 'Number of NaNs']) self.endResetModel()
def buildTree(self, data, parent, name='', hideRoot=False, path=()): if hideRoot: node = parent else: node = QtGui.QTreeWidgetItem([name, "", ""]) parent.addChild(node) # record the path to the node so it can be retrieved later # (this is used by DiffTreeWidget) self.nodes[path] = node typeStr, desc, childs, widget = self.parse(data) node.setText(1, typeStr) node.setText(2, desc) # Truncate description and add text box if needed if len(desc) > 100: desc = desc[:97] + '...' if widget is None: widget = QtGui.QPlainTextEdit(asUnicode(data)) widget.setMaximumHeight(200) widget.setReadOnly(True) # Add widget to new subnode if widget is not None: self.widgets.append(widget) subnode = QtGui.QTreeWidgetItem(["", "", ""]) node.addChild(subnode) self.setItemWidget(subnode, 0, widget) self.setFirstItemColumnSpanned(subnode, True) # recurse to children for key, data in childs.items(): self.buildTree(data, node, asUnicode(key), path=path+(key,))
def update(self): ''' uodate the model of the MENU (header)''' N_index = len(self.df.index) for name in self.df.columns: # append a table with three columns (Name, Data-Type and N_NaNs). Each row represents # a data-column in the input dataframe object item_name = QtGui.QStandardItem( asUnicode('{0}'.format(name.encode('UTF-8')))) item_name.setCheckable(True) item_name.setEditable(False) item_name.setCheckState(Qt.Checked) dt = self.df[name].dtype if isinstance( self.df, pd.DataFrame) else self.df.dtype item_type = QtGui.QStandardItem(asUnicode('{0}'.format(dt))) item_type.setEditable(False) if dt == type(object): # if the data-type of the column is not numeric or datetime, then it's propabply has not been # loaded successfully. Therefore, explicitly change the font color to red, to inform the user. item_type.setForeground(RED_FONT) n_nans = N_index - (self.df[name].count() if isinstance( self.df, pd.DataFrame) else self.df.count()) item_nans = QtGui.QStandardItem(asUnicode('{0}'.format(n_nans))) item_nans.setEditable(False) if n_nans > 0: item_nans.setForeground(RED_FONT) self.appendRow([item_name, item_type, item_nans]) self.setHorizontalHeaderLabels(['Name', 'Data-type', 'Number of NaNs']) self.endResetModel()
def updateDisplayLabel(self, value=None): """Update the display label to reflect the value of the parameter.""" if value is None: value = self.param.value() opts = self.param.opts if isinstance(self.widget, QtGui.QAbstractSpinBox): text = asUnicode(self.widget.lineEdit().text()) elif isinstance(self.widget, QtGui.QComboBox): text = self.widget.currentText() else: text = asUnicode(value) self.displayLabel.setText(text)
def parse(self, data): """ Given any python object, return: * type * a short string representation * a dict of sub-objects to be parsed * optional widget to display as sub-node """ # defaults for all objects typeStr = type(data).__name__ if typeStr == 'instance': typeStr += ": " + data.__class__.__name__ widget = None desc = "" childs = {} # type-specific changes if isinstance(data, dict): desc = "length=%d" % len(data) if isinstance(data, OrderedDict): childs = data else: childs = OrderedDict(sorted(data.items())) elif isinstance(data, (list, tuple)): desc = "length=%d" % len(data) childs = OrderedDict(enumerate(data)) elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): childs = OrderedDict([ ('data', data.view(np.ndarray)), ('meta', data.infoCopy()) ]) elif isinstance(data, np.ndarray): desc = "shape=%s dtype=%s" % (data.shape, data.dtype) table = TableWidget() table.setData(data) table.setMaximumHeight(200) widget = table elif isinstance(data, types.TracebackType): ## convert traceback to a list of strings frames = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) #childs = OrderedDict([ #(i, {'file': child[0], 'line': child[1], 'function': child[2], 'code': child[3]}) #for i, child in enumerate(frames)]) #childs = OrderedDict([(i, ch) for i,ch in enumerate(frames)]) widget = QtGui.QPlainTextEdit(asUnicode('\n'.join(frames))) widget.setMaximumHeight(200) widget.setReadOnly(True) else: desc = asUnicode(data) return typeStr, desc, childs, widget
def on_selectFile_valueChanged(self, value): button = self.param('Select File').items.items()[0][0].button fname = asUnicode(self.param('Select File').value()).encode('UTF-8') self._parent.sigUIStateChanged.emit(self) if fname is not None and os.path.isfile(fname): #print fname, type(fname) #print u'File is selected: {0}'.format(fname) #print type(asUnicode('File is selected: {0}'.format(fname))) button.setToolTip(asUnicode('File is selected: {0}'.format(fname))) button.setStatusTip(asUnicode('File is selected: {0}'.format(fname))) else: button.setToolTip(asUnicode('Select File')) button.setStatusTip(asUnicode('Select File'))
def fileSaveFinished(self, fileName): fileName = asUnicode(fileName) global LastExportDirectory LastExportDirectory = os.path.split(fileName)[0] ## If file name does not match selected extension, append it now ext = os.path.splitext(fileName)[1].lower().lstrip('.') selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter())) if selectedExt is not None: selectedExt = selectedExt.groups()[0].lower() if ext != selectedExt: fileName = fileName + '.' + selectedExt.lstrip('.') self.export(fileName=fileName, **self.fileDialog.opts)
def labelString(self): if self.labelUnits == "": if self.scale == 1.0: units = "" else: units = asUnicode("(x%g)") % (1.0 / self.scale) else: # print repr(self.labelUnitPrefix), repr(self.labelUnits) units = asUnicode("(%s%s)") % (self.labelUnitPrefix, self.labelUnits) s = asUnicode("%s %s") % (self.labelText, units) style = ";".join(["%s: %s" % (k, self.labelStyle[k]) for k in self.labelStyle]) return asUnicode("<span style='%s'>%s</span>") % (style, s)
def labelString(self): if self.labelUnits == '': if self.scale == 1.0: units = '' else: units = asUnicode('(x%g)') % (1.0/self.scale) else: #print repr(self.labelUnitPrefix), repr(self.labelUnits) units = asUnicode('(%s%s)') % (self.labelUnitPrefix, self.labelUnits) s = asUnicode('%s %s') % (self.labelText, units) style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) return asUnicode("<span style='%s'>%s</span>") % (style, s)
def setViewList(self, views): names = [''] self.viewMap.clear() ## generate list of views to show in the link combo for v in views: name = v.name if name is None: ## unnamed views do not show up in the view list (although they are linkable) continue names.append(name) self.viewMap[name] = v for i in [0,1]: c = self.ctrl[i].linkCombo current = asUnicode(c.currentText()) c.blockSignals(True) changed = True try: c.clear() for name in names: c.addItem(name) if name == current: changed = False c.setCurrentIndex(c.count()-1) finally: c.blockSignals(False) if changed: c.setCurrentIndex(0) c.currentIndexChanged.emit(c.currentIndex())
def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return self.df.columns[section] elif orientation == Qt.Vertical: return asUnicode('{0} | {1}'.format(section, self.df.index.tolist()[section])) return QtCore.QVariant()
def iteratorFn(self, data): ## Return 1) a function that will provide an iterator for data and 2) a list of header strings if isinstance(data, list) or isinstance(data, tuple): return lambda d: d.__iter__(), None elif isinstance(data, dict): return lambda d: iter(d.values()), list(map( asUnicode, data.keys())) elif (hasattr(data, 'implements') and data.implements('MetaArray')): if data.axisHasColumns(0): header = [ asUnicode(data.columnName(0, i)) for i in range(data.shape[0]) ] elif data.axisHasValues(0): header = list(map(asUnicode, data.xvals(0))) else: header = None return self.iterFirstAxis, header elif isinstance(data, np.ndarray): return self.iterFirstAxis, None elif isinstance(data, np.void): return self.iterate, list(map(asUnicode, data.dtype.names)) elif data is None: return (None, None) elif np.isscalar(data): return self.iterateScalar, None else: msg = "Don't know how to iterate over data type: {!s}".format( type(data)) raise TypeError(msg)
def setMinimum(self, m, update=True): """Set the minimum allowed value (or None for no limit)""" if m is not None: m = D(asUnicode(m)) self.opts['bounds'][0] = m if update: self.setValue()
def setViewList(self, views): names = [''] self.viewMap.clear() ## generate list of views to show in the link combo for v in views: name = v.name if name is None: ## unnamed views do not show up in the view list (although they are linkable) continue names.append(name) self.viewMap[name] = v for i in [0, 1]: c = self.ctrl[i].linkCombo current = asUnicode(c.currentText()) c.blockSignals(True) changed = True try: c.clear() for name in names: c.addItem(name) if name == current: changed = False c.setCurrentIndex(c.count() - 1) finally: c.blockSignals(False) if changed: c.setCurrentIndex(0) c.currentIndexChanged.emit(c.currentIndex())
def labelString(self): if self.labelUnits == "": if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: units = "" else: units = asUnicode("[x%g]") % (1.0/self.autoSIPrefixScale) else: units = asUnicode("[%s%s]") % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits)) s_label = asUnicode("%s %s") % (asUnicode(self.labelText), asUnicode(units)) s_style = ";".join(["%s: %s" % (k, self.labelStyle[k]) for k in self.labelStyle]) return asUnicode("<span style='%s'>%s</span>") % (s_style, asUnicode(s_label))
def execCmd(self): cmd = asUnicode(self.text()) if len(self.history) == 1 or cmd != self.history[1]: self.history.insert(1, cmd) #self.lastCmd = cmd self.history[0] = "" self.setHistory(0) self.sigExecuteCmd.emit(cmd)
def labelString(self): if self.labelUnits == '': if self.scale == 1.0: units = '' else: units = asUnicode('(x%g)') % (1.0 / self.scale) else: #print repr(self.labelUnitPrefix), repr(self.labelUnits) units = asUnicode('(%s%s)') % (self.labelUnitPrefix, self.labelUnits) s = asUnicode('%s %s') % (self.labelText, units) style = ';'.join( ['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) return asUnicode("<span style='%s'>%s</span>") % (style, s)
def setOpts(self, **opts): """ Changes the behavior of the SpinBox. Accepts most of the arguments allowed in :func:`__init__ <pyqtgraph.SpinBox.__init__>`. """ #print opts for k in opts: if k == 'bounds': #print opts[k] self.setMinimum(opts[k][0], update=False) self.setMaximum(opts[k][1], update=False) #for i in [0,1]: #if opts[k][i] is None: #self.opts[k][i] = None #else: #self.opts[k][i] = D(unicode(opts[k][i])) elif k in ['step', 'minStep']: self.opts[k] = D(asUnicode(opts[k])) elif k == 'value': pass ## don't set value until bounds have been set else: self.opts[k] = opts[k] if 'value' in opts: self.setValue(opts['value']) ## If bounds have changed, update value to match if 'bounds' in opts and 'value' not in opts: self.setValue() ## sanity checks: if self.opts['int']: if 'step' in opts: step = opts['step'] ## not necessary.. #if int(step) != step: #raise Exception('Integer SpinBox must have integer step size.') else: self.opts['step'] = int(self.opts['step']) if 'minStep' in opts: step = opts['minStep'] if int(step) != step: raise Exception( 'Integer SpinBox must have integer minStep size.') else: ms = int(self.opts.get('minStep', 1)) if ms < 1: ms = 1 self.opts['minStep'] = ms if 'delay' in opts: self.proxy.setDelay(opts['delay']) if 'readonly' in opts: self.opts['readonly'] = opts['readonly'] self.updateText()
def addReagent(self, item): names = self.db.reagents.names() i = 0 while True: name = 'new_reagent_%d' % i if name not in names: break i += 1 self.db.reagents.add(name=name, group=asUnicode(item.text(0)))
def labelString(self): # if self.labelUnits == '': # if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: # units = '' # else: # units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale) # else: # #print repr(self.labelUnitPrefix), repr(self.labelUnits) # units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits)) # s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units)) s = asUnicode(self.labelText) style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) return asUnicode("<span style='%s'>%s</span>") % (style, asUnicode(s))
def headerData(self, section, orientation, role=Qt.DisplayRole): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return self.df.columns[section] elif orientation == Qt.Vertical: return asUnicode('{0} | {1}'.format( section, self.df.index.tolist()[section])) return QtCore.QVariant()
def setOpts(self, **opts): """ Changes the behavior of the SpinBox. Accepts most of the arguments allowed in :func:`__init__ <pyqtgraph.SpinBox.__init__>`. """ #print opts for k in opts: if k == 'bounds': #print opts[k] self.setMinimum(opts[k][0], update=False) self.setMaximum(opts[k][1], update=False) #for i in [0,1]: #if opts[k][i] is None: #self.opts[k][i] = None #else: #self.opts[k][i] = D(unicode(opts[k][i])) elif k in ['step', 'minStep']: self.opts[k] = D(asUnicode(opts[k])) elif k == 'value': pass ## don't set value until bounds have been set else: self.opts[k] = opts[k] if 'value' in opts: self.setValue(opts['value']) ## If bounds have changed, update value to match if 'bounds' in opts and 'value' not in opts: self.setValue() ## sanity checks: if self.opts['int']: if 'step' in opts: step = opts['step'] ## not necessary.. #if int(step) != step: #raise Exception('Integer SpinBox must have integer step size.') else: self.opts['step'] = int(self.opts['step']) if 'minStep' in opts: step = opts['minStep'] if int(step) != step: raise Exception('Integer SpinBox must have integer minStep size.') else: ms = int(self.opts.get('minStep', 1)) if ms < 1: ms = 1 self.opts['minStep'] = ms if 'delay' in opts: self.proxy.setDelay(opts['delay']) if 'readonly' in opts: self.opts['readonly'] = opts['readonly'] self.updateText()
def addChanged(self): """Called when "add new" combo is changed The parameter MUST have an 'addNew' method defined. """ if self.addWidget.currentIndex() == 0: return typ = asUnicode(self.addWidget.currentText()) self.param.addNew(typ) self.addWidget.setCurrentIndex(0)
def format(self): if callable(self._format): return self._format(self) if isinstance(self.value, (float, np.floating)): if self._format is None: return self._defaultFormat % self.value else: return self._format % self.value else: return asUnicode(self.value)
def value(self): #vals = self.param.opts['limits'] key = asUnicode(self.widget.currentText()) #if isinstance(vals, dict): #return vals[key] #else: #return key #print key, self.forward return self.forward.get(key, None)
def validate(self, strn, pos): if self.skipValidate: #print "skip validate" #self.textValid = False ret = QtGui.QValidator.Acceptable else: try: ## first make sure we didn't mess with the suffix suff = self.opts.get('suffix', '') if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff: #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) ret = QtGui.QValidator.Invalid ## next see if we actually have an interpretable value else: val = self.interpret() if val is False: #print "can't interpret" #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') #self.textValid = False ret = QtGui.QValidator.Intermediate else: if self.valueInRange(val): if not self.opts['delayUntilEditFinished']: self.setValue(val, update=False) #print " OK:", self.val #self.setStyleSheet('') #self.textValid = True ret = QtGui.QValidator.Acceptable else: ret = QtGui.QValidator.Intermediate except: #print " BAD" #import sys #sys.excepthook(*sys.exc_info()) #self.textValid = False #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') ret = QtGui.QValidator.Intermediate ## draw / clear border if ret == QtGui.QValidator.Intermediate: self.textValid = False elif ret == QtGui.QValidator.Acceptable: self.textValid = True ## note: if text is invalid, we don't change the textValid flag ## since the text will be forced to its previous state anyway self.update() ## support 2 different pyqt APIs. Bleh. if hasattr(QtCore, 'QString'): return (ret, pos) else: return (ret, strn, pos)
def export(self, fileName=None, toBytes=False, copy=False): if toBytes is False and copy is False and fileName is None: self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") return #self.svg = QtSvg.QSvgGenerator() #self.svg.setFileName(fileName) #dpi = QtGui.QDesktopWidget().physicalDpiX() ### not really sure why this works, but it seems to be important: #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) #self.svg.setResolution(dpi) ##self.svg.setViewBox() #targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) #sourceRect = self.getSourceRect() #painter = QtGui.QPainter(self.svg) #try: #self.setExportMode(True) #self.render(painter, QtCore.QRectF(targetRect), sourceRect) #finally: #self.setExportMode(False) #painter.end() ## Workaround to set pen widths correctly #data = open(fileName).readlines() #for i in range(len(data)): #line = data[i] #m = re.match(r'(<g .*)stroke-width="1"(.*transform="matrix\(([^\)]+)\)".*)', line) #if m is not None: ##print "Matched group:", line #g = m.groups() #matrix = list(map(float, g[2].split(','))) ##print "matrix:", matrix #scale = max(abs(matrix[0]), abs(matrix[3])) #if scale == 0 or scale == 1.0: #continue #data[i] = g[0] + ' stroke-width="%0.2g" ' % (1.0/scale) + g[1] + '\n' ##print "old line:", line ##print "new line:", data[i] #open(fileName, 'w').write(''.join(data)) ## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Instead, we will use Qt to generate SVG for each item independently, ## then manually reconstruct the entire document. xml = generateSvg(self.item) if toBytes: return xml.encode('UTF-8') elif copy: md = QtCore.QMimeData() md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8'))) QtGui.QApplication.clipboard().setMimeData(md) else: with open(fileName, 'wb') as fh: fh.write(asUnicode(xml).encode('utf-8'))
def setModelData(self, editor, model, col): fname, ftype = self.fields.items()[col] # string / float types need to be handled differently if ftype.kind in 'uif': v = float(str(editor.text())) self.setText(col, '%0.2f' % v) else: v = asUnicode(editor.text()) self.setText(col, v) self.sigChanged.emit(self, fname, v)
def setMinimum(self, m, update=True): """Set the minimum allowed value (or None for no limit)""" if m is not None: #FIX: insert the integer functionality: if self.opts['int']: m = int(m) m = D(asUnicode(m)) self.opts['bounds'][0] = m if update: self.setValue()
def serialize(self, useSelection=False): """Convert entire table (or just selected area) into tab-separated text values""" if useSelection: selection = self.selectedRanges()[0] rows = list(range(selection.topRow(), selection.bottomRow() + 1)) columns = list(range(selection.leftColumn(), selection.rightColumn() + 1)) else: rows = list(range(self.rowCount())) columns = list(range(self.columnCount())) data = [] if self.horizontalHeadersSet: row = [] if self.verticalHeadersSet: row.append(asUnicode('')) for c in columns: row.append(asUnicode(self.horizontalHeaderItem(c).text())) data.append(row) for r in rows: row = [] if self.verticalHeadersSet: row.append(asUnicode(self.verticalHeaderItem(r).text())) for c in columns: item = self.item(r, c) if item is not None: row.append(asUnicode(item.value)) else: row.append(asUnicode('')) data.append(row) s = '' for row in data: s += ('\t'.join(row) + '\n') return s
def serialize(self, useSelection=False): """Convert entire table (or just selected area) into tab-separated text values""" if useSelection: selection = self.selectedRanges()[0] rows = list(range(selection.topRow(), selection.bottomRow() + 1)) columns = list( range(selection.leftColumn(), selection.rightColumn() + 1)) else: rows = list(range(self.rowCount())) columns = list(range(self.columnCount())) data = [] if self.horizontalHeadersSet: row = [] if self.verticalHeadersSet: row.append(asUnicode('')) for c in columns: row.append(asUnicode(self.horizontalHeaderItem(c).text())) data.append(row) for r in rows: row = [] if self.verticalHeadersSet: row.append(asUnicode(self.verticalHeaderItem(r).text())) for c in columns: item = self.item(r, c) if item is not None: row.append(asUnicode(item.value)) else: row.append(asUnicode('')) data.append(row) s = '' for row in data: s += ('\t'.join(row) + '\n') return s
def selectNumber(self): """ Select the numerical portion of the text to allow quick editing by the user. """ le = self.lineEdit() text = asUnicode(le.text()) if self.opts['suffix'] == '': le.setSelection(0, len(text)) else: try: index = text.index(' ') except ValueError: return le.setSelection(0, index)
def mapping(limits): ## Return forward and reverse mapping dictionaries given a limit specification forward = OrderedDict() ## name: value reverse = OrderedDict() ## value: name if isinstance(limits, dict): for k, v in limits.items(): forward[k] = v reverse[v] = k else: for v in limits: n = asUnicode(v) forward[n] = v reverse[v] = n return forward, reverse
def keyPressEvent(self, ev): #print "press:", ev.key(), QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_Enter if ev.key() == QtCore.Qt.Key_Up and self.ptr < len(self.history) - 1: self.setHistory(self.ptr+1) ev.accept() return elif ev.key() == QtCore.Qt.Key_Down and self.ptr > 0: self.setHistory(self.ptr-1) ev.accept() return elif ev.key() == QtCore.Qt.Key_Return: self.execCmd() else: QtGui.QLineEdit.keyPressEvent(self, ev) self.history[0] = asUnicode(self.text())
def mapping(limits): ## Return forward and reverse mapping objects given a limit specification forward = OrderedDict() ## {name: value, ...} reverse = ([], []) ## ([value, ...], [name, ...]) if isinstance(limits, dict): for k, v in limits.items(): forward[k] = v reverse[0].append(v) reverse[1].append(k) else: for v in limits: n = asUnicode(v) forward[n] = v reverse[0].append(v) reverse[1].append(n) return forward, reverse
def test_types(): paramSpec = [ dict(name='float', type='float'), dict(name='int', type='int'), dict(name='str', type='str'), dict(name='list', type='list', values=['x','y','z']), dict(name='dict', type='list', values={'x':1, 'y':3, 'z':7}), dict(name='bool', type='bool'), dict(name='color', type='color'), ] param = pt.Parameter.create(name='params', type='group', children=paramSpec) tree = pt.ParameterTree() tree.setParameters(param) all_objs = { 'int0': 0, 'int':7, 'float': -0.35, 'bigfloat': 1e129, 'npfloat': np.float(5), 'npint': np.int(5),'npinf': np.inf, 'npnan': np.nan, 'bool': True, 'complex': 5+3j, 'str': 'xxx', 'unicode': asUnicode('µ'), 'list': [1,2,3], 'dict': {'1': 2}, 'color': pg.mkColor('k'), 'brush': pg.mkBrush('k'), 'pen': pg.mkPen('k'), 'none': None } if hasattr(QtCore, 'QString'): all_objs['qstring'] = QtCore.QString('xxxµ') # float types = ['int0', 'int', 'float', 'bigfloat', 'npfloat', 'npint', 'npinf', 'npnan', 'bool'] check_param_types(param.child('float'), float, float, 0.0, all_objs, types) # int types = ['int0', 'int', 'float', 'bigfloat', 'npfloat', 'npint', 'bool'] inttyps = int if sys.version[0] >= '3' else (int, long) check_param_types(param.child('int'), inttyps, int, 0, all_objs, types) # str (should be able to make a string out of any type) types = all_objs.keys() strtyp = str if sys.version[0] >= '3' else unicode check_param_types(param.child('str'), strtyp, asUnicode, '', all_objs, types) # bool (should be able to make a boolean out of any type?) types = all_objs.keys() check_param_types(param.child('bool'), bool, bool, False, all_objs, types) # color types = ['color', 'int0', 'int', 'float', 'npfloat', 'npint', 'list'] init = QtGui.QColor(128, 128, 128, 255) check_param_types(param.child('color'), QtGui.QColor, pg.mkColor, init, all_objs, types)
def __init__(self, reagent): class SigProxy(QtCore.QObject): sigChanged = QtCore.Signal(object, object, object) # self, field, value self._sigprox = SigProxy() self.sigChanged = self._sigprox.sigChanged self.reagent = reagent fields = reagent.fields self.fields = OrderedDict([(f, fields[f]) for f in fields if f != 'group']) strs = [asUnicode(reagent[f]) for f in self.fields] QtGui.QTreeWidgetItem.__init__(self, strs) self.setFlags(self.flags() | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable)
def editingFinishedEvent(self): """Edit has finished; set value.""" #print "Edit finished." if asUnicode(self.lineEdit().text()) == self.lastText: #print "no text change." return try: val = self.interpret() except: return if val is False: #print "value invalid:", str(self.lineEdit().text()) return if val == self.val: #print "no value change:", val, self.val return self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like
def export(self, fileName=None, toBytes=False, copy=False): if toBytes is False and copy is False and fileName is None: self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") return ## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Instead, we will use Qt to generate SVG for each item independently, ## then manually reconstruct the entire document. xml = generateSvg(self.item) if toBytes: return xml.encode('UTF-8') elif copy: md = QtCore.QMimeData() md.setData('image/svg+xml', QtCore.QByteArray(xml.encode('UTF-8'))) QtGui.QApplication.clipboard().setMimeData(md) else: with open(fileName, 'wb') as fh: fh.write(asUnicode(xml).encode('utf-8'))
def setValue(self, value=None, update=True, delaySignal=False): """ Set the value of this spin. If the value is out of bounds, it will be clipped to the nearest boundary. If the spin is integer type, the value will be coerced to int. Returns the actual value set. If value is None, then the current value is used (this is for resetting the value after bounds, etc. have changed) """ if value is None: value = self.value() bounds = self.opts['bounds'] if bounds[0] is not None and value < bounds[0]: value = bounds[0] if bounds[1] is not None and value > bounds[1]: value = bounds[1] if self.opts['int']: value = int(value) value = D(asUnicode(value)) if value == self.val: return prev = self.val self.val = value if update: self.updateText(prev=prev) self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. if not delaySignal: self.emitChanged() return value
def siEval(s): """ Convert a value written in SI notation to its equivalent prefixless value Example:: siEval("100 μV") # returns 0.0001 """ s = asUnicode(s) m = re.match(r'(-?((\d+(\.\d*)?)|(\.\d+))([eE][-+]?\d+)?)\s*([u' + SI_PREFIXES + r']?).*$', s) if m is None: raise Exception("Can't convert string '%s' to number." % s) v = float(m.groups()[0]) p = m.groups()[6] #if p not in SI_PREFIXES: #raise Exception("Can't convert string '%s' to number--unknown prefix." % s) if p == '': n = 0 elif p == 'u': n = -2 else: n = SI_PREFIXES.index(p) - 8 return v * 1000**n
def makeWidget(self): """ Return a single widget that should be placed in the second tree column. The widget must be given three attributes: ========== ============================================================ sigChanged a signal that is emitted when the widget's value is changed value a function that returns the value setValue a function that sets the value ========== ============================================================ This is a good function to override in subclasses. """ opts = self.param.opts t = opts['type'] if t == 'int': defs = { 'value': 0, 'min': None, 'max': None, 'int': True, 'step': 1.0, 'minStep': 1.0, 'dec': False, 'siPrefix': False, 'suffix': '' } defs.update(opts) if 'limits' in opts: defs['bounds'] = opts['limits'] w = SpinBox() w.setOpts(**defs) w.sigChanged = w.sigValueChanged w.sigChanging = w.sigValueChanging elif t == 'float': defs = { 'value': 0, 'min': None, 'max': None, 'step': 1.0, 'dec': False, 'siPrefix': False, 'suffix': '' } defs.update(opts) if 'limits' in opts: defs['bounds'] = opts['limits'] w = SpinBox() w.setOpts(**defs) w.sigChanged = w.sigValueChanged w.sigChanging = w.sigValueChanging elif t == 'bool': w = QtGui.QCheckBox() w.sigChanged = w.toggled w.value = w.isChecked w.setValue = w.setChecked self.hideWidget = False elif t == 'str': w = QtGui.QLineEdit() w.sigChanged = w.editingFinished w.value = lambda: asUnicode(w.text()) w.setValue = lambda v: w.setText(asUnicode(v)) w.sigChanging = w.textChanged elif t == 'color': w = ColorButton() w.sigChanged = w.sigColorChanged w.sigChanging = w.sigColorChanging w.value = w.color w.setValue = w.setColor self.hideWidget = False w.setFlat(True) elif t == 'colormap': from pyqtgraph.widgets.GradientWidget import GradientWidget ## need this here to avoid import loop w = GradientWidget(orientation='bottom') w.sigChanged = w.sigGradientChangeFinished w.sigChanging = w.sigGradientChanged w.value = w.colorMap w.setValue = w.setColorMap self.hideWidget = False else: raise Exception("Unknown type '%s'" % asUnicode(t)) return w
def validate(self, strn, pos): # print('validate', strn, pos) if self.skipValidate: # print("skip validate") #self.textValid = False ret = QtGui.QValidator.Acceptable else: try: ## first make sure we didn't mess with the suffix suff = self.opts.get('suffix', '') # fix: if the whole text is selected and one needs to typ in a # new number, then a single integer character is ignored. if len(strn) == 1 and strn.isdigit(): scl_str = fn.siScale(self.val)[1] strn = '{0} {1}{2}'.format(strn, scl_str, suff) if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff: #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) ret = QtGui.QValidator.Invalid # print('invalid input', 'suff:', suff, '{0} != {1}'.format(asUnicode(strn)[-len(suff):], suff)) ## next see if we actually have an interpretable value else: val = self.interpret() if val is False: #print "can't interpret" #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') #self.textValid = False ret = QtGui.QValidator.Intermediate else: if self.valueInRange(val): if not self.opts['delayUntilEditFinished']: self.setValue(val, update=False) #print " OK:", self.val #self.setStyleSheet('') #self.textValid = True ret = QtGui.QValidator.Acceptable else: ret = QtGui.QValidator.Intermediate except: #print " BAD" #import sys #sys.excepthook(*sys.exc_info()) #self.textValid = False #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') ret = QtGui.QValidator.Intermediate ## draw / clear border if ret == QtGui.QValidator.Intermediate: self.textValid = False elif ret == QtGui.QValidator.Acceptable: self.textValid = True ## note: if text is invalid, we don't change the textValid flag ## since the text will be forced to its previous state anyway self.update() ## support 2 different pyqt APIs. Bleh. if hasattr(QtCore, 'QString'): return (ret, pos) else: return (ret, strn, pos)
def __init__(self, parent=None, value=0.0, **kwargs): """ ============== ======================================================================== **Arguments:** parent Sets the parent widget for this SpinBox (optional). Default is None. value (float/int) initial value. Default is 0.0. bounds (min,max) Minimum and maximum values allowed in the SpinBox. Either may be None to leave the value unbounded. By default, values are unbounded. suffix (str) suffix (units) to display after the numerical value. By default, suffix is an empty str. siPrefix (bool) If True, then an SI prefix is automatically prepended to the units and the value is scaled accordingly. For example, if value=0.003 and suffix='V', then the SpinBox will display "300 mV" (but a call to SpinBox.value will still return 0.003). Default is False. step (float) The size of a single step. This is used when clicking the up/ down arrows, when rolling the mouse wheel, or when pressing keyboard arrows while the widget has keyboard focus. Note that the interpretation of this value is different when specifying the 'dec' argument. Default is 0.01. dec (bool) If True, then the step value will be adjusted to match the current size of the variable (for example, a value of 15 might step in increments of 1 whereas a value of 1500 would step in increments of 100). In this case, the 'step' argument is interpreted *relative* to the current value. The most common 'step' values when dec=True are 0.1, 0.2, 0.5, and 1.0. Default is False. minStep (float) When dec=True, this specifies the minimum allowable step size. int (bool) if True, the value is forced to integer type. Default is False decimals (int) Number of decimal values to display. Default is 2. readonly (bool) If True, then mouse and keyboard interactions are caught and will not produce a valueChanged signal, but the value can be still changed programmatic via the setValue method. Default is False. ============== ======================================================================== """ QtGui.QAbstractSpinBox.__init__(self, parent) self.lastValEmitted = None self.lastText = '' self.textValid = True ## If false, we draw a red border self.setMinimumWidth(0) self.setMaximumHeight(20) self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) self.opts = { 'bounds': [None, None], ## Log scaling options #### Log mode is no longer supported. #'step': 0.1, #'minStep': 0.001, #'log': True, #'dec': False, ## decimal scaling option - example #'step': 0.1, #'minStep': .001, #'log': False, #'dec': True, ## normal arithmetic step 'step': D('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time ## if 'dec' is True, the step size is relative to the value ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) 'log': False, 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. ## if true, minStep must be set in order to cross zero. 'int': False, ## Set True to force value to be integer 'suffix': '', 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) 'delay': 0.3, ## delay sending wheel update signals for 300ms 'delayUntilEditFinished': True, ## do not send signals until text editing has finished ## for compatibility with QDoubleSpinBox and QSpinBox 'decimals': 2, 'readonly': False, } self.decOpts = ['step', 'minStep'] self.val = D(asUnicode(value)) ## Value is precise decimal. Ordinary math not allowed. self.updateText() self.skipValidate = False self.setCorrectionMode(self.CorrectToPreviousValue) self.setKeyboardTracking(False) self.setOpts(**kwargs) self.editingFinished.connect(self.editingFinishedEvent) self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay'])