- Added workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616. (GraphicsItem.setParent needs to check for scene change first)

This _could_ cause other problems, but they will certainly be fewer than the existing problems.

- Fixed bugs with ViewBox linking to views which are subsequently deleted
This commit is contained in:
Luke Campagnola 2012-06-18 14:00:19 -04:00
parent cc94e15d1e
commit 6932c34126
9 changed files with 302 additions and 161 deletions

View File

@ -26,19 +26,19 @@ win.addLabel("Linked Views", colspan=2)
win.nextRow() win.nextRow()
p1 = win.addPlot(x=x, y=y, name="Plot1", title="Plot1") p1 = win.addPlot(x=x, y=y, name="Plot1", title="Plot1")
p2 = win.addPlot(x=x, y=y, name="Plot2", title="Plot2 - Y linked with Plot1") p2 = win.addPlot(x=x, y=y, name="Plot2", title="Plot2: Y linked with Plot1")
p2.setLabel('bottom', "Label to test offset") p2.setLabel('bottom', "Label to test offset")
p2.setYLink(p1) p2.setYLink('Plot1') ## test linking by name
win.nextRow()
p3 = win.addPlot(x=x, y=y, name="Plot3", title="Plot3 - X linked with Plot1") ## create plots 3 and 4 out of order
p4 = win.addPlot(x=x, y=y, name="Plot4", title="Plot4 - X and Y linked with Plot1") p4 = win.addPlot(x=x, y=y, name="Plot4", title="Plot4: X -> Plot3 (deferred), Y -> Plot1", row=2, col=1)
p4.setXLink('Plot3') ## Plot3 has not been created yet, but this should still work anyway.
p4.setYLink(p1)
p3 = win.addPlot(x=x, y=y, name="Plot3", title="Plot3: X linked with Plot1", row=2, col=0)
p3.setXLink(p1)
p3.setLabel('left', "Label to test offset") p3.setLabel('left', "Label to test offset")
#QtGui.QApplication.processEvents() #QtGui.QApplication.processEvents()
p3.setXLink(p1)
p4.setXLink(p1)
p4.setYLink(p1)
## Start Qt event loop unless running in interactive mode or using pyside. ## Start Qt event loop unless running in interactive mode or using pyside.

View File

@ -510,7 +510,7 @@ class NodeGraphicsItem(GraphicsObject):
if change == self.ItemPositionHasChanged: if change == self.ItemPositionHasChanged:
for k, t in self.terminals.items(): for k, t in self.terminals.items():
t[1].nodeMoved() t[1].nodeMoved()
return QtGui.QGraphicsItem.itemChange(self, change, val) return GraphicsObject.itemChange(self, change, val)
#def contextMenuEvent(self, ev): #def contextMenuEvent(self, ev):

View File

@ -342,26 +342,47 @@ class GraphicsItem(object):
## check for this item's current viewbox or view widget ## check for this item's current viewbox or view widget
view = self.getViewBox() view = self.getViewBox()
if view is None: #if view is None:
#print " no view" ##print " no view"
return #return
if self._connectedView is not None and view is self._connectedView(): oldView = None
if self._connectedView is not None:
oldView = self._connectedView()
if view is oldView:
#print " already have view", view #print " already have view", view
return return
## disconnect from previous view ## disconnect from previous view
if self._connectedView is not None: if oldView is not None:
cv = self._connectedView() #print "disconnect:", self, oldView
if cv is not None: oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
#print "disconnect:", self self._connectedView = None
cv.sigRangeChanged.disconnect(self.viewRangeChanged)
## connect to new view ## connect to new view
#print "connect:", self if view is not None:
view.sigRangeChanged.connect(self.viewRangeChanged) #print "connect:", self, view
self._connectedView = weakref.ref(view) view.sigRangeChanged.connect(self.viewRangeChanged)
self.viewRangeChanged() self._connectedView = weakref.ref(view)
self.viewRangeChanged()
## inform children that their view might have changed
self._replaceView(oldView)
def _replaceView(self, oldView, item=None):
if item is None:
item = self
for child in item.childItems():
if isinstance(child, GraphicsItem):
if child.getViewBox() is oldView:
child._updateView()
#self._replaceView(oldView, child)
else:
self._replaceView(oldView, child)
def viewRangeChanged(self): def viewRangeChanged(self):
""" """

View File

@ -22,7 +22,8 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
def setParentItem(self, parent): def setParentItem(self, parent):
## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616
pscene = parent.scene() if parent is not None:
if pscene is not None and self.scene() is not pscene: pscene = parent.scene()
pscene.addItem(self) if pscene is not None and self.scene() is not pscene:
pscene.addItem(self)
return QtGui.QGraphicsObject.setParentItem(self, parent) return QtGui.QGraphicsObject.setParentItem(self, parent)

View File

@ -55,7 +55,8 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget):
def setParentItem(self, parent): def setParentItem(self, parent):
## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616
pscene = parent.scene() if parent is not None:
if pscene is not None and self.scene() is not pscene: pscene = parent.scene()
pscene.addItem(self) if pscene is not None and self.scene() is not pscene:
return QtGui.QGraphicsWidget.setParentItem(self, parent) pscene.addItem(self)
return QtGui.QGraphicsObject.setParentItem(self, parent)

View File

@ -62,7 +62,6 @@ class ViewBox(GraphicsWidget):
NamedViews = weakref.WeakValueDictionary() # name: ViewBox NamedViews = weakref.WeakValueDictionary() # name: ViewBox
AllViews = weakref.WeakKeyDictionary() # ViewBox: None AllViews = weakref.WeakKeyDictionary() # ViewBox: None
def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, name=None): def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, name=None):
""" """
============= ============================================================= ============= =============================================================
@ -99,7 +98,8 @@ class ViewBox(GraphicsWidget):
## otherwise float gives the fraction of data that is visible ## otherwise float gives the fraction of data that is visible
'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled 'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled
'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot 'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot
'linkedViews': [None, None], 'linkedViews': [None, None], ## may be None, "viewName", or weakref.ref(view)
## a name string indicates that the view *should* link to another, but no view with that name exists yet.
'mouseEnabled': [enableMouse, enableMouse], 'mouseEnabled': [enableMouse, enableMouse],
'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode, 'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode,
@ -160,6 +160,8 @@ class ViewBox(GraphicsWidget):
if name is not None: if name is not None:
ViewBox.NamedViews[name] = self ViewBox.NamedViews[name] = self
ViewBox.updateAllViewLists() ViewBox.updateAllViewLists()
self.destroyed.connect(lambda: ViewBox.forgetView(id(self), self.name))
#self.destroyed.connect(self.unregister)
def unregister(self): def unregister(self):
""" """
@ -177,14 +179,26 @@ class ViewBox(GraphicsWidget):
def getState(self, copy=True): def getState(self, copy=True):
"""Return the current state of the ViewBox.
Linked views are always converted to view names in the returned state."""
state = self.state.copy() state = self.state.copy()
state['linkedViews'] = [(None if v is None else v.name) for v in state['linkedViews']] views = []
for v in state['linkedViews']:
if isinstance(v, weakref.ref):
v = v()
if v is None or isinstance(v, basestring):
views.append(v)
else:
views.append(v.name)
state['linkedViews'] = views
if copy: if copy:
return deepcopy(self.state) return deepcopy(state)
else: else:
return self.state return state
def setState(self, state): def setState(self, state):
"""Restore the state of this ViewBox.
(see also getState)"""
state = state.copy() state = state.copy()
self.setXLink(state['linkedViews'][0]) self.setXLink(state['linkedViews'][0])
self.setYLink(state['linkedViews'][1]) self.setYLink(state['linkedViews'][1])
@ -368,7 +382,7 @@ class ViewBox(GraphicsWidget):
self.updateMatrix(changed) self.updateMatrix(changed)
for ax, range in changes.items(): for ax, range in changes.items():
link = self.state['linkedViews'][ax] link = self.linkedView(ax)
if link is not None: if link is not None:
link.linkedViewChanged(self, ax) link.linkedViewChanged(self, ax)
@ -572,7 +586,7 @@ class ViewBox(GraphicsWidget):
if view == '': if view == '':
view = None view = None
else: else:
view = ViewBox.NamedViews[view] view = ViewBox.NamedViews.get(view, view) ## convert view name to ViewBox if possible
if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'):
view = view.getViewBox() view = view.getViewBox()
@ -586,13 +600,19 @@ class ViewBox(GraphicsWidget):
slot = self.linkedYChanged slot = self.linkedYChanged
oldLink = self.state['linkedViews'][axis] oldLink = self.linkedView(axis)
if oldLink is not None: if oldLink is not None:
getattr(oldLink, signal).disconnect(slot) try:
getattr(oldLink, signal).disconnect(slot)
except TypeError:
## This can occur if the view has been deleted already
pass
self.state['linkedViews'][axis] = view
if view is not None: if view is None or isinstance(view, basestring):
self.state['linkedViews'][axis] = view
else:
self.state['linkedViews'][axis] = weakref.ref(view)
getattr(view, signal).connect(slot) getattr(view, signal).connect(slot)
if view.autoRangeEnabled()[axis] is not False: if view.autoRangeEnabled()[axis] is not False:
self.enableAutoRange(axis, False) self.enableAutoRange(axis, False)
@ -608,14 +628,22 @@ class ViewBox(GraphicsWidget):
def linkedXChanged(self): def linkedXChanged(self):
## called when x range of linked view has changed ## called when x range of linked view has changed
view = self.state['linkedViews'][0] view = self.linkedView(0)
self.linkedViewChanged(view, ViewBox.XAxis) self.linkedViewChanged(view, ViewBox.XAxis)
def linkedYChanged(self): def linkedYChanged(self):
## called when y range of linked view has changed ## called when y range of linked view has changed
view = self.state['linkedViews'][1] view = self.linkedView(1)
self.linkedViewChanged(view, ViewBox.YAxis) self.linkedViewChanged(view, ViewBox.YAxis)
def linkedView(self, ax):
## Return the linked view for axis *ax*.
## this method _always_ returns either a ViewBox or None.
v = self.state['linkedViews'][ax]
if v is None or isinstance(v, basestring):
return None
else:
return v() ## dereference weakref pointer. If the reference is dead, this returns None
def linkedViewChanged(self, view, axis): def linkedViewChanged(self, view, axis):
if self.linksBlocked or view is None: if self.linksBlocked or view is None:
@ -623,10 +651,9 @@ class ViewBox(GraphicsWidget):
vr = view.viewRect() vr = view.viewRect()
vg = view.screenGeometry() vg = view.screenGeometry()
if vg is None:
return
sg = self.screenGeometry() sg = self.screenGeometry()
if vg is None or sg is None:
return
view.blockLink(True) view.blockLink(True)
try: try:
@ -683,8 +710,11 @@ class ViewBox(GraphicsWidget):
By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis.
""" """
self.state['yInverted'] = b self.state['yInverted'] = b
self.updateMatrix() self.updateMatrix(changed=(False, True))
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
def yInverted(self):
return self.state['yInverted']
def setAspectLocked(self, lock=True, ratio=1): def setAspectLocked(self, lock=True, ratio=1):
""" """
@ -1030,6 +1060,7 @@ class ViewBox(GraphicsWidget):
def updateMatrix(self, changed=None): def updateMatrix(self, changed=None):
if changed is None: if changed is None:
changed = [False, False] changed = [False, False]
changed = list(changed)
#print "udpateMatrix:" #print "udpateMatrix:"
#print " range:", self.range #print " range:", self.range
tr = self.targetRect() tr = self.targetRect()
@ -1124,20 +1155,40 @@ class ViewBox(GraphicsWidget):
## make a sorted list of all named views ## make a sorted list of all named views
nv = list(ViewBox.NamedViews.values()) nv = list(ViewBox.NamedViews.values())
#print "new view list:", nv
sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList
if self in nv: if self in nv:
nv.remove(self) nv.remove(self)
names = [v.name for v in nv]
self.menu.setViewList(names) self.menu.setViewList(nv)
for ax in [0,1]:
link = self.state['linkedViews'][ax]
if isinstance(link, basestring): ## axis has not been linked yet; see if it's possible now
for v in nv:
if link == v.name:
self.linkView(ax, v)
#print "New view list:", nv
#print "linked views:", self.state['linkedViews']
@staticmethod @staticmethod
def updateAllViewLists(): def updateAllViewLists():
#print "Update:", ViewBox.AllViews.keys()
#print "Update:", ViewBox.NamedViews.keys()
for v in ViewBox.AllViews: for v in ViewBox.AllViews:
v.updateViewLists() v.updateViewLists()
@staticmethod
def forgetView(vid, name):
## Called with ID and name of view (the view itself is no longer available)
for v in ViewBox.AllViews.iterkeys():
if id(v) == vid:
ViewBox.AllViews.pop(v)
break
ViewBox.NamedViews.pop(name, None)
ViewBox.updateAllViewLists()
from .ViewBoxMenu import ViewBoxMenu from .ViewBoxMenu import ViewBoxMenu

View File

@ -1,13 +1,15 @@
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.WidgetGroup import WidgetGroup from pyqtgraph.WidgetGroup import WidgetGroup
from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate
import weakref
class ViewBoxMenu(QtGui.QMenu): class ViewBoxMenu(QtGui.QMenu):
def __init__(self, view): def __init__(self, view):
QtGui.QMenu.__init__(self) QtGui.QMenu.__init__(self)
self.view = view self.view = weakref.ref(view) ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected)
self.valid = False ## tells us whether the ui needs to be updated self.valid = False ## tells us whether the ui needs to be updated
self.viewMap = weakref.WeakValueDictionary() ## weakrefs to all views listed in the link combos
self.setTitle("ViewBox options") self.setTitle("ViewBox options")
self.viewAll = QtGui.QAction("View All", self) self.viewAll = QtGui.QAction("View All", self)
@ -47,7 +49,9 @@ class ViewBoxMenu(QtGui.QMenu):
for sig, fn in connects: for sig, fn in connects:
sig.connect(getattr(self, axis.lower()+fn)) sig.connect(getattr(self, axis.lower()+fn))
self.ctrl[0].invertCheck.hide() ## no invert for x-axis
self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled)
## exporting is handled by GraphicsScene now ## exporting is handled by GraphicsScene now
#self.export = QtGui.QMenu("Export") #self.export = QtGui.QMenu("Export")
#self.setExportMethods(view.exportMethods) #self.setExportMethods(view.exportMethods)
@ -64,7 +68,7 @@ class ViewBoxMenu(QtGui.QMenu):
self.mouseModes = [pan, zoom] self.mouseModes = [pan, zoom]
self.addMenu(self.leftMenu) self.addMenu(self.leftMenu)
self.view.sigStateChanged.connect(self.viewStateChanged) self.view().sigStateChanged.connect(self.viewStateChanged)
self.updateState() self.updateState()
@ -97,14 +101,15 @@ class ViewBoxMenu(QtGui.QMenu):
self.updateState() self.updateState()
def updateState(self): def updateState(self):
state = self.view.getState(copy=False) ## Something about the viewbox has changed; update the menu GUI
state = self.view().getState(copy=False)
if state['mouseMode'] == ViewBox.PanMode: if state['mouseMode'] == ViewBox.PanMode:
self.mouseModes[0].setChecked(True) self.mouseModes[0].setChecked(True)
else: else:
self.mouseModes[1].setChecked(True) self.mouseModes[1].setChecked(True)
for i in [0,1]: # x, y
for i in [0,1]:
tr = state['targetRange'][i] tr = state['targetRange'][i]
self.ctrl[i].minText.setText("%0.5g" % tr[0]) self.ctrl[i].minText.setText("%0.5g" % tr[0])
self.ctrl[i].maxText.setText("%0.5g" % tr[1]) self.ctrl[i].maxText.setText("%0.5g" % tr[1])
@ -116,17 +121,15 @@ class ViewBoxMenu(QtGui.QMenu):
self.ctrl[i].manualRadio.setChecked(True) self.ctrl[i].manualRadio.setChecked(True)
self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i])
## Update combo to show currently linked view
c = self.ctrl[i].linkCombo c = self.ctrl[i].linkCombo
c.blockSignals(True) c.blockSignals(True)
try: try:
view = state['linkedViews'][i] view = state['linkedViews'][i] ## will always be string or None
if view is None: if view is None:
view = '' view = ''
if isinstance(view, basestring): ind = c.findText(view)
ind = c.findText(view)
else:
ind = c.findText(view.name)
if ind == -1: if ind == -1:
ind = 0 ind = 0
@ -136,76 +139,79 @@ class ViewBoxMenu(QtGui.QMenu):
self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i]) self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i])
self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i]) self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i])
self.ctrl[1].invertCheck.setChecked(state['yInverted'])
self.valid = True self.valid = True
def autoRange(self): def autoRange(self):
self.view.autoRange() ## don't let signal call this directly--it'll add an unwanted argument self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument
def xMouseToggled(self, b): def xMouseToggled(self, b):
self.view.setMouseEnabled(x=b) self.view().setMouseEnabled(x=b)
def xManualClicked(self): def xManualClicked(self):
self.view.enableAutoRange(ViewBox.XAxis, False) self.view().enableAutoRange(ViewBox.XAxis, False)
def xMinTextChanged(self): def xMinTextChanged(self):
self.ctrl[0].manualRadio.setChecked(True) self.ctrl[0].manualRadio.setChecked(True)
self.view.setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0)
def xMaxTextChanged(self): def xMaxTextChanged(self):
self.ctrl[0].manualRadio.setChecked(True) self.ctrl[0].manualRadio.setChecked(True)
self.view.setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0)
def xAutoClicked(self): def xAutoClicked(self):
val = self.ctrl[0].autoPercentSpin.value() * 0.01 val = self.ctrl[0].autoPercentSpin.value() * 0.01
self.view.enableAutoRange(ViewBox.XAxis, val) self.view().enableAutoRange(ViewBox.XAxis, val)
def xAutoSpinChanged(self, val): def xAutoSpinChanged(self, val):
self.ctrl[0].autoRadio.setChecked(True) self.ctrl[0].autoRadio.setChecked(True)
self.view.enableAutoRange(ViewBox.XAxis, val*0.01) self.view().enableAutoRange(ViewBox.XAxis, val*0.01)
def xLinkComboChanged(self, ind): def xLinkComboChanged(self, ind):
self.view.setXLink(str(self.ctrl[0].linkCombo.currentText())) self.view().setXLink(str(self.ctrl[0].linkCombo.currentText()))
def xAutoPanToggled(self, b): def xAutoPanToggled(self, b):
self.view.setAutoPan(x=b) self.view().setAutoPan(x=b)
def xVisibleOnlyToggled(self, b): def xVisibleOnlyToggled(self, b):
self.view.setAutoVisible(x=b) self.view().setAutoVisible(x=b)
def yMouseToggled(self, b): def yMouseToggled(self, b):
self.view.setMouseEnabled(y=b) self.view().setMouseEnabled(y=b)
def yManualClicked(self): def yManualClicked(self):
self.view.enableAutoRange(ViewBox.YAxis, False) self.view().enableAutoRange(ViewBox.YAxis, False)
def yMinTextChanged(self): def yMinTextChanged(self):
self.ctrl[1].manualRadio.setChecked(True) self.ctrl[1].manualRadio.setChecked(True)
self.view.setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0)
def yMaxTextChanged(self): def yMaxTextChanged(self):
self.ctrl[1].manualRadio.setChecked(True) self.ctrl[1].manualRadio.setChecked(True)
self.view.setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0)
def yAutoClicked(self): def yAutoClicked(self):
val = self.ctrl[1].autoPercentSpin.value() * 0.01 val = self.ctrl[1].autoPercentSpin.value() * 0.01
self.view.enableAutoRange(ViewBox.YAxis, val) self.view().enableAutoRange(ViewBox.YAxis, val)
def yAutoSpinChanged(self, val): def yAutoSpinChanged(self, val):
self.ctrl[1].autoRadio.setChecked(True) self.ctrl[1].autoRadio.setChecked(True)
self.view.enableAutoRange(ViewBox.YAxis, val*0.01) self.view().enableAutoRange(ViewBox.YAxis, val*0.01)
def yLinkComboChanged(self, ind): def yLinkComboChanged(self, ind):
self.view.setYLink(str(self.ctrl[1].linkCombo.currentText())) self.view().setYLink(str(self.ctrl[1].linkCombo.currentText()))
def yAutoPanToggled(self, b): def yAutoPanToggled(self, b):
self.view.setAutoPan(y=b) self.view().setAutoPan(y=b)
def yVisibleOnlyToggled(self, b): def yVisibleOnlyToggled(self, b):
self.view.setAutoVisible(y=b) self.view().setAutoVisible(y=b)
def yInvertToggled(self, b):
self.view().invertY(b)
def exportMethod(self): def exportMethod(self):
@ -214,14 +220,24 @@ class ViewBoxMenu(QtGui.QMenu):
def set3ButtonMode(self): def set3ButtonMode(self):
self.view.setLeftButtonAction('pan') self.view().setLeftButtonAction('pan')
def set1ButtonMode(self): def set1ButtonMode(self):
self.view.setLeftButtonAction('rect') self.view().setLeftButtonAction('rect')
def setViewList(self, views): def setViewList(self, views):
views = [''] + 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]: for i in [0,1]:
c = self.ctrl[i].linkCombo c = self.ctrl[i].linkCombo
current = asUnicode(c.currentText()) current = asUnicode(c.currentText())
@ -229,9 +245,9 @@ class ViewBoxMenu(QtGui.QMenu):
changed = True changed = True
try: try:
c.clear() c.clear()
for v in views: for name in names:
c.addItem(v) c.addItem(name)
if v == current: if name == current:
changed = False changed = False
c.setCurrentIndex(c.count()-1) c.setCurrentIndex(c.count()-1)
finally: finally:

View File

@ -2,8 +2,8 @@
# Form implementation generated from reading ui file 'axisCtrlTemplate.ui' # Form implementation generated from reading ui file 'axisCtrlTemplate.ui'
# #
# Created: Wed Mar 28 23:29:45 2012 # Created: Fri Jun 1 17:38:02 2012
# by: PyQt4 UI code generator 4.8.3 # by: PyQt4 UI code generator 4.9.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -17,12 +17,31 @@ except AttributeError:
class Ui_Form(object): class Ui_Form(object):
def setupUi(self, Form): def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form")) Form.setObjectName(_fromUtf8("Form"))
Form.resize(186, 137) Form.resize(186, 154)
Form.setMaximumSize(QtCore.QSize(200, 16777215)) Form.setMaximumSize(QtCore.QSize(200, 16777215))
self.gridLayout = QtGui.QGridLayout(Form) self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setMargin(0) self.gridLayout.setMargin(0)
self.gridLayout.setSpacing(0) self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.label = QtGui.QLabel(Form)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 7, 0, 1, 2)
self.linkCombo = QtGui.QComboBox(Form)
self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.linkCombo.setObjectName(_fromUtf8("linkCombo"))
self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2)
self.autoPercentSpin = QtGui.QSpinBox(Form)
self.autoPercentSpin.setEnabled(True)
self.autoPercentSpin.setMinimum(1)
self.autoPercentSpin.setMaximum(100)
self.autoPercentSpin.setSingleStep(1)
self.autoPercentSpin.setProperty("value", 100)
self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin"))
self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2)
self.autoRadio = QtGui.QRadioButton(Form)
self.autoRadio.setChecked(True)
self.autoRadio.setObjectName(_fromUtf8("autoRadio"))
self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2)
self.manualRadio = QtGui.QRadioButton(Form) self.manualRadio = QtGui.QRadioButton(Form)
self.manualRadio.setObjectName(_fromUtf8("manualRadio")) self.manualRadio.setObjectName(_fromUtf8("manualRadio"))
self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2)
@ -32,48 +51,43 @@ class Ui_Form(object):
self.maxText = QtGui.QLineEdit(Form) self.maxText = QtGui.QLineEdit(Form)
self.maxText.setObjectName(_fromUtf8("maxText")) self.maxText.setObjectName(_fromUtf8("maxText"))
self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1)
self.autoRadio = QtGui.QRadioButton(Form) self.invertCheck = QtGui.QCheckBox(Form)
self.autoRadio.setChecked(True) self.invertCheck.setObjectName(_fromUtf8("invertCheck"))
self.autoRadio.setObjectName(_fromUtf8("autoRadio")) self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4)
self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2)
self.autoPercentSpin = QtGui.QSpinBox(Form)
self.autoPercentSpin.setEnabled(True)
self.autoPercentSpin.setMinimum(1)
self.autoPercentSpin.setMaximum(100)
self.autoPercentSpin.setSingleStep(1)
self.autoPercentSpin.setProperty(_fromUtf8("value"), 100)
self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin"))
self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2)
self.visibleOnlyCheck = QtGui.QCheckBox(Form)
self.visibleOnlyCheck.setObjectName(_fromUtf8("visibleOnlyCheck"))
self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 1, 1, 3)
self.autoPanCheck = QtGui.QCheckBox(Form)
self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck"))
self.gridLayout.addWidget(self.autoPanCheck, 4, 1, 1, 3)
self.label = QtGui.QLabel(Form)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 5, 0, 1, 2)
self.linkCombo = QtGui.QComboBox(Form)
self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
self.linkCombo.setObjectName(_fromUtf8("linkCombo"))
self.gridLayout.addWidget(self.linkCombo, 5, 2, 1, 2)
self.mouseCheck = QtGui.QCheckBox(Form) self.mouseCheck = QtGui.QCheckBox(Form)
self.mouseCheck.setChecked(True) self.mouseCheck.setChecked(True)
self.mouseCheck.setObjectName(_fromUtf8("mouseCheck")) self.mouseCheck.setObjectName(_fromUtf8("mouseCheck"))
self.gridLayout.addWidget(self.mouseCheck, 0, 0, 1, 4) self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4)
self.visibleOnlyCheck = QtGui.QCheckBox(Form)
self.visibleOnlyCheck.setObjectName(_fromUtf8("visibleOnlyCheck"))
self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2)
self.autoPanCheck = QtGui.QCheckBox(Form)
self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck"))
self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2)
self.retranslateUi(Form) self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8))
self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8))
self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8))
self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8))
self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8))
self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8))
self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8))
self.autoRadio.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Automatically resize this axis whenever the displayed data is changed.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
self.manualRadio.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Set the range for this axis manually. This disables automatic scaling. </p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8))
self.minText.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Minimum value to display for this axis.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8))
self.maxText.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Maximum value to display for this axis.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8))
self.invertCheck.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Inverts the display of this axis. (+y points downward instead of upward)</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.invertCheck.setText(QtGui.QApplication.translate("Form", "Invert Axis", None, QtGui.QApplication.UnicodeUTF8))
self.mouseCheck.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Enables mouse interaction (panning, scaling) for this axis.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8))
self.visibleOnlyCheck.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8))
self.autoPanCheck.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>186</width> <width>186</width>
<height>137</height> <height>154</height>
</rect> </rect>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
@ -26,34 +26,20 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<item row="1" column="0" colspan="2"> <item row="7" column="0" colspan="2">
<widget class="QRadioButton" name="manualRadio"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Manual</string> <string>Link Axis:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="7" column="2" colspan="2">
<widget class="QLineEdit" name="minText"> <widget class="QComboBox" name="linkCombo">
<property name="text"> <property name="toolTip">
<string>0</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Links this axis with another view. When linked, both views will display the same data range.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> <property name="sizeAdjustPolicy">
</item> <enum>QComboBox::AdjustToContents</enum>
<item row="1" column="3">
<widget class="QLineEdit" name="maxText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QRadioButton" name="autoRadio">
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -62,6 +48,9 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="suffix"> <property name="suffix">
<string>%</string> <string>%</string>
</property> </property>
@ -79,36 +68,64 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" colspan="3"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="visibleOnlyCheck"> <widget class="QRadioButton" name="autoRadio">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Automatically resize this axis whenever the displayed data is changed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Visible Data Only</string> <string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" colspan="3"> <item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="autoPanCheck"> <widget class="QRadioButton" name="manualRadio">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the range for this axis manually. This disables automatic scaling. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Auto Pan Only</string> <string>Manual</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="2"> <item row="1" column="2">
<widget class="QLabel" name="label"> <widget class="QLineEdit" name="minText">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Minimum value to display for this axis.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Link Axis:</string> <string>0</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="2" colspan="2"> <item row="1" column="3">
<widget class="QComboBox" name="linkCombo"> <widget class="QLineEdit" name="maxText">
<property name="sizeAdjustPolicy"> <property name="toolTip">
<enum>QComboBox::AdjustToContents</enum> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Maximum value to display for this axis.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>0</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0" colspan="4"> <item row="5" column="0" colspan="4">
<widget class="QCheckBox" name="invertCheck">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Inverts the display of this axis. (+y points downward instead of upward)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Invert Axis</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="4">
<widget class="QCheckBox" name="mouseCheck"> <widget class="QCheckBox" name="mouseCheck">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enables mouse interaction (panning, scaling) for this axis.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Mouse Enabled</string> <string>Mouse Enabled</string>
</property> </property>
@ -117,6 +134,26 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2" colspan="2">
<widget class="QCheckBox" name="visibleOnlyCheck">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Visible Data Only</string>
</property>
</widget>
</item>
<item row="4" column="2" colspan="2">
<widget class="QCheckBox" name="autoPanCheck">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Auto Pan Only</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>