Minor updates and bug fixes:

- fixed handling of foreground colors in AxisItem
  - fixed rare crash caused in AxisItem 
  - fixed improper propagation of key events from SpinBox
  - many others
This commit is contained in:
Luke Campagnola 2012-08-31 17:18:06 -04:00
parent bb48f1cb36
commit 0402d08604
18 changed files with 101 additions and 39 deletions

View File

@ -182,7 +182,7 @@ def plot(*args, **kargs):
#if len(args)+len(kargs) > 0: #if len(args)+len(kargs) > 0:
#w.plot(*args, **kargs) #w.plot(*args, **kargs)
pwArgList = ['title', 'label', 'name', 'left', 'right', 'top', 'bottom'] pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom']
pwArgs = {} pwArgs = {}
dataArgs = {} dataArgs = {}
for k in kargs: for k in kargs:

View File

@ -122,7 +122,7 @@ params = [
] ]
## Create tree of Parameter objects ## Create tree of Parameter objects
p = Parameter(name='params', type='group', children=params) p = Parameter.create(name='params', type='group', children=params)
## If anything changes in the tree, print a message ## If anything changes in the tree, print a message
def change(param, changes): def change(param, changes):

View File

@ -160,7 +160,10 @@ class Flowchart(Node):
Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable())
def internalTerminalRemoved(self, node, term): def internalTerminalRemoved(self, node, term):
Node.removeTerminal(self, term.name()) try:
Node.removeTerminal(self, term.name())
except KeyError:
pass
def terminalRenamed(self, term, oldName): def terminalRenamed(self, term, oldName):
newName = term.name() newName = term.name()

View File

@ -294,6 +294,8 @@ class Node(QtCore.QObject):
self.removeTerminal(name) self.removeTerminal(name)
for name, opts in state.items(): for name, opts in state.items():
if name in self.terminals: if name in self.terminals:
term = self[name]
term.setOpts(**opts)
continue continue
try: try:
opts = strDict(opts) opts = strDict(opts)

View File

@ -85,6 +85,14 @@ class Terminal:
#if c.isInput(): #if c.isInput():
#c.inputChanged(self) #c.inputChanged(self)
self.recolor() self.recolor()
def setOpts(self, **opts):
self._renamable = opts.get('renamable', self._renamable)
self._removable = opts.get('removable', self._removable)
self._multiable = opts.get('multiable', self._multiable)
if 'multi' in opts:
self.setMultiValue(opts['multi'])
def connected(self, term): def connected(self, term):
"""Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)"""

View File

@ -235,6 +235,9 @@ class EvalNode(Node):
run = "\noutput=fn(**args)\n" run = "\noutput=fn(**args)\n"
text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run
exec(text) exec(text)
except:
print "Error processing node:", self.name()
raise
return output return output
def saveState(self): def saveState(self):
@ -282,7 +285,7 @@ class ColumnJoinNode(Node):
def addInput(self): def addInput(self):
#print "ColumnJoinNode.addInput called." #print "ColumnJoinNode.addInput called."
term = Node.addInput(self, 'input', renamable=True, removable=True) term = Node.addInput(self, 'input', renamable=True, removable=True, multiable=True)
#print "Node.addInput returned. term:", term #print "Node.addInput returned. term:", term
item = QtGui.QTreeWidgetItem([term.name()]) item = QtGui.QTreeWidgetItem([term.name()])
item.term = term item.term = term
@ -323,6 +326,14 @@ class ColumnJoinNode(Node):
def restoreState(self, state): def restoreState(self, state):
Node.restoreState(self, state) Node.restoreState(self, state)
inputs = self.inputs() inputs = self.inputs()
## Node.restoreState should have created all of the terminals we need
## However: to maintain support for some older flowchart files, we need
## to manually add any terminals that were not taken care of.
for name in [n for n in state['order'] if n not in inputs]:
Node.addInput(self, name, renamable=True, removable=True, multiable=True)
inputs = self.inputs()
order = [name for name in state['order'] if name in inputs] order = [name for name in state['order'] if name in inputs]
for name in inputs: for name in inputs:
if name not in order: if name not in order:
@ -343,4 +354,4 @@ class ColumnJoinNode(Node):
item.setText(0, term.name()) item.setText(0, term.name())
self.update() self.update()

View File

@ -184,13 +184,16 @@ class HistogramDetrend(CtrlNode):
nodeName = 'HistogramDetrend' nodeName = 'HistogramDetrend'
uiTemplate = [ uiTemplate = [
('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}),
('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}),
('offsetOnly', 'check', {'checked': False}),
] ]
def processData(self, data): def processData(self, data):
ws = self.ctrls['windowSize'].value() s = self.stateGroup.state()
bn = self.ctrls['numBins'].value() #ws = self.ctrls['windowSize'].value()
return functions.histogramDetrend(data, window=ws, bins=bn) #bn = self.ctrls['numBins'].value()
#offset = self.ctrls['offsetOnly'].checked()
return functions.histogramDetrend(data, window=s['windowSize'], bins=s['numBins'], offsetOnly=s['offsetOnly'])

View File

@ -196,8 +196,10 @@ def adaptiveDetrend(data, x=None, threshold=3.0):
return d4 return d4
def histogramDetrend(data, window=500, bins=50, threshold=3.0): def histogramDetrend(data, window=500, bins=50, threshold=3.0, offsetOnly=False):
"""Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers.""" """Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers.
If offsetOnly is True, then only the offset from the beginning of the trace is subtracted.
"""
d1 = data.view(np.ndarray) d1 = data.view(np.ndarray)
d2 = [d1[:window], d1[-window:]] d2 = [d1[:window], d1[-window:]]
@ -211,8 +213,11 @@ def histogramDetrend(data, window=500, bins=50, threshold=3.0):
ind = np.argmax(y) ind = np.argmax(y)
v[i] = 0.5 * (x[ind] + x[ind+1]) v[i] = 0.5 * (x[ind] + x[ind+1])
base = np.linspace(v[0], v[1], len(data)) if offsetOnly:
d3 = data.view(np.ndarray) - base d3 = data.view(np.ndarray) - v[0]
else:
base = np.linspace(v[0], v[1], len(data))
d3 = data.view(np.ndarray) - base
if (hasattr(data, 'implements') and data.implements('MetaArray')): if (hasattr(data, 'implements') and data.implements('MetaArray')):
return MetaArray(d3, info=data.infoCopy()) return MetaArray(d3, info=data.infoCopy())

View File

@ -261,7 +261,7 @@ def mkPen(*args, **kargs):
if isinstance(arg, dict): if isinstance(arg, dict):
return mkPen(**arg) return mkPen(**arg)
if isinstance(arg, QtGui.QPen): if isinstance(arg, QtGui.QPen):
return arg return QtGui.QPen(arg) ## return a copy of this pen
elif arg is None: elif arg is None:
style = QtCore.Qt.NoPen style = QtCore.Qt.NoPen
else: else:

View File

@ -56,7 +56,7 @@ class AxisItem(GraphicsWidget):
self.labelText = '' self.labelText = ''
self.labelUnits = '' self.labelUnits = ''
self.labelUnitPrefix='' self.labelUnitPrefix=''
self.labelStyle = {'color': '#CCC'} self.labelStyle = {}
self.logMode = False self.logMode = False
self.textHeight = 18 self.textHeight = 18
@ -167,7 +167,7 @@ class AxisItem(GraphicsWidget):
s = asUnicode('%s %s') % (self.labelText, units) s = asUnicode('%s %s') % (self.labelText, units)
style = ';'.join(['%s: "%s"' % (k, self.labelStyle[k]) for k in self.labelStyle]) style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle])
return asUnicode("<span style='%s'>%s</span>") % (style, s) return asUnicode("<span style='%s'>%s</span>") % (style, s)
@ -192,7 +192,7 @@ class AxisItem(GraphicsWidget):
def pen(self): def pen(self):
if self._pen is None: if self._pen is None:
return fn.mkPen(pg.getConfigOption('foreground')) return fn.mkPen(pg.getConfigOption('foreground'))
return self._pen return pg.mkPen(self._pen)
def setPen(self, pen): def setPen(self, pen):
""" """
@ -202,6 +202,10 @@ class AxisItem(GraphicsWidget):
""" """
self._pen = pen self._pen = pen
self.picture = None self.picture = None
if pen is None:
pen = pg.getConfigOption('foreground')
self.labelStyle['color'] = '#' + pg.colorStr(pg.mkPen(pen).color())[:6]
self.setLabel()
self.update() self.update()
def setScale(self, scale=None): def setScale(self, scale=None):
@ -299,8 +303,8 @@ class AxisItem(GraphicsWidget):
self.drawPicture(painter) self.drawPicture(painter)
finally: finally:
painter.end() painter.end()
p.setRenderHint(p.Antialiasing, False) #p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ???
p.setRenderHint(p.TextAntialiasing, True) #p.setRenderHint(p.TextAntialiasing, True)
self.picture.play(p) self.picture.play(p)

View File

@ -47,6 +47,8 @@ class GradientLegend(UIGraphicsItem):
UIGraphicsItem.paint(self, p, opt, widget) UIGraphicsItem.paint(self, p, opt, widget)
rect = self.boundingRect() ## Boundaries of visible area in scene coords. rect = self.boundingRect() ## Boundaries of visible area in scene coords.
unit = self.pixelSize() ## Size of one view pixel in scene coords. unit = self.pixelSize() ## Size of one view pixel in scene coords.
if unit[0] is None:
return
## determine max width of all labels ## determine max width of all labels
labelWidth = 0 labelWidth = 0

View File

@ -1017,7 +1017,7 @@ class PlotItem(GraphicsWidget):
(ie, use 'V' instead of 'mV'; 'm' will be added automatically) (ie, use 'V' instead of 'mV'; 'm' will be added automatically)
============= ================================================================= ============= =================================================================
""" """
self.getScale(axis).setLabel(text=text, units=units, **args) self.getAxis(axis).setLabel(text=text, units=units, **args)
def showLabel(self, axis, show=True): def showLabel(self, axis, show=True):
""" """

View File

@ -71,7 +71,6 @@ class ScatterPlotItem(GraphicsObject):
#sigPointClicked = QtCore.Signal(object, object) #sigPointClicked = QtCore.Signal(object, object)
sigClicked = QtCore.Signal(object, object) ## self, points sigClicked = QtCore.Signal(object, object) ## self, points
sigPlotChanged = QtCore.Signal(object) sigPlotChanged = QtCore.Signal(object)
def __init__(self, *args, **kargs): def __init__(self, *args, **kargs):
""" """
Accepts the same arguments as setData() Accepts the same arguments as setData()
@ -231,6 +230,9 @@ class ScatterPlotItem(GraphicsObject):
self.generateSpotItems() self.generateSpotItems()
self.sigPlotChanged.emit(self) self.sigPlotChanged.emit(self)
def getData(self):
return self.data['x'], self.data['y']
def setPoints(self, *args, **kargs): def setPoints(self, *args, **kargs):
##Deprecated; use setData ##Deprecated; use setData

View File

@ -310,6 +310,12 @@ class Parameter(QtCore.QObject):
""" """
return not self.opts.get('readonly', False) return not self.opts.get('readonly', False)
def setWritable(self, writable=True):
self.setOpts(readonly=not writable)
def setReadonly(self, readonly=True):
self.setOpts(readonly=readonly)
def setOpts(self, **opts): def setOpts(self, **opts):
""" """
Set any arbitrary options on this parameter. Set any arbitrary options on this parameter.

View File

@ -26,6 +26,7 @@ class ParameterItem(QtGui.QTreeWidgetItem):
param.sigLimitsChanged.connect(self.limitsChanged) param.sigLimitsChanged.connect(self.limitsChanged)
param.sigDefaultChanged.connect(self.defaultChanged) param.sigDefaultChanged.connect(self.defaultChanged)
param.sigOptionsChanged.connect(self.optsChanged) param.sigOptionsChanged.connect(self.optsChanged)
param.sigParentChanged.connect(self.parentChanged)
opts = param.opts opts = param.opts
@ -93,6 +94,10 @@ class ParameterItem(QtGui.QTreeWidgetItem):
self.takeChild(i) self.takeChild(i)
break break
def parentChanged(self, param, parent):
## called when the parameter's parent has changed.
pass
def contextMenuEvent(self, ev): def contextMenuEvent(self, ev):
if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False): if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False):
return return

View File

@ -290,23 +290,7 @@ class GroupParameterItem(ParameterItem):
""" """
def __init__(self, param, depth): def __init__(self, param, depth):
ParameterItem.__init__(self, param, depth) ParameterItem.__init__(self, param, depth)
if depth == 0: self.updateDepth(depth)
for c in [0,1]:
self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100)))
self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255)))
font = self.font(c)
font.setBold(True)
font.setPointSize(font.pointSize()+1)
self.setFont(c, font)
self.setSizeHint(0, QtCore.QSize(0, 25))
else:
for c in [0,1]:
self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220)))
font = self.font(c)
font.setBold(True)
#font.setPointSize(font.pointSize()+1)
self.setFont(c, font)
self.setSizeHint(0, QtCore.QSize(0, 20))
self.addItem = None self.addItem = None
if 'addText' in param.opts: if 'addText' in param.opts:
@ -331,6 +315,27 @@ class GroupParameterItem(ParameterItem):
self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) self.addItem.setFlags(QtCore.Qt.ItemIsEnabled)
ParameterItem.addChild(self, self.addItem) ParameterItem.addChild(self, self.addItem)
def updateDepth(self, depth):
## Change item's appearance based on its depth in the tree
## This allows highest-level groups to be displayed more prominently.
if depth == 0:
for c in [0,1]:
self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100)))
self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255)))
font = self.font(c)
font.setBold(True)
font.setPointSize(font.pointSize()+1)
self.setFont(c, font)
self.setSizeHint(0, QtCore.QSize(0, 25))
else:
for c in [0,1]:
self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220)))
font = self.font(c)
font.setBold(True)
#font.setPointSize(font.pointSize()+1)
self.setFont(c, font)
self.setSizeHint(0, QtCore.QSize(0, 20))
def addClicked(self): def addClicked(self):
"""Called when "add new" button is clicked """Called when "add new" button is clicked
The parameter MUST have an 'addNew' method defined. The parameter MUST have an 'addNew' method defined.

View File

@ -90,4 +90,4 @@ class CheckTable(QtGui.QWidget):
rowNum = self.rowNames.index(r[0]) rowNum = self.rowNames.index(r[0])
for i in range(1, len(r)): for i in range(1, len(r)):
self.rowWidgets[rowNum][i].setChecked(r[i]) self.rowWidgets[rowNum][i].setChecked(r[i])

View File

@ -127,6 +127,12 @@ class SpinBox(QtGui.QAbstractSpinBox):
self.editingFinished.connect(self.editingFinishedEvent) self.editingFinished.connect(self.editingFinishedEvent)
self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange) self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange)
def event(self, ev):
ret = QtGui.QAbstractSpinBox.event(self, ev)
if ev.type() == QtCore.QEvent.KeyPress and ev.key() == QtCore.Qt.Key_Return:
ret = True ## For some reason, spinbox pretends to ignore return key press
return ret
##lots of config options, just gonna stuff 'em all in here rather than do the get/set crap. ##lots of config options, just gonna stuff 'em all in here rather than do the get/set crap.
def setOpts(self, **opts): def setOpts(self, **opts):
""" """