forked from M-Labs/artiq
1
0
Fork 0

scanwidget: apply changes as of 579bf5e

This commit is contained in:
Robert Jördens 2016-03-11 21:37:55 +01:00
parent d34d83f35c
commit 78f2706aa8
1 changed files with 77 additions and 90 deletions

View File

@ -10,15 +10,12 @@ logger = logging.getLogger(__name__)
class ScanAxis(QtWidgets.QWidget): class ScanAxis(QtWidgets.QWidget):
sigZoom = QtCore.pyqtSignal(float, int) def __init__(self):
sigPoints = QtCore.pyqtSignal(int)
def __init__(self, zoomFactor):
QtWidgets.QWidget.__init__(self) QtWidgets.QWidget.__init__(self)
self.proxy = None self.proxy = None
self.sizePolicy().setControlType(QtWidgets.QSizePolicy.ButtonBox) self.sizePolicy().setControlType(QtWidgets.QSizePolicy.ButtonBox)
self.ticker = Ticker() self.ticker = Ticker()
self.zoomFactor = zoomFactor self.setMinimumHeight(40)
def paintEvent(self, ev): def paintEvent(self, ev):
painter = QtGui.QPainter(self) painter = QtGui.QPainter(self)
@ -52,37 +49,6 @@ class ScanAxis(QtWidgets.QWidget):
painter.drawLine(p_int, 0, p_int, 5) painter.drawLine(p_int, 0, p_int, 5)
ev.accept() ev.accept()
def wheelEvent(self, ev):
y = ev.angleDelta().y()
if y:
if ev.modifiers() & QtCore.Qt.ShiftModifier:
# If shift+scroll, modify number of points.
# TODO: This is not perfect. For high-resolution touchpads you
# get many small events with y < 120 which should accumulate.
# That would also match the wheel behavior of an integer
# spinbox.
z = int(y / 120.)
self.sigPoints.emit(z)
else:
z = self.zoomFactor**(y / 120.)
# Remove the slider-handle shift correction, b/c none of the
# other widgets know about it. If we have the mouse directly
# over a tick during a zoom, it should appear as if we are
# doing zoom relative to the ticks which live in axis
# pixel-space, not slider pixel-space.
self.sigZoom.emit(
z, ev.x() - self.proxy.slider.handleWidth()/2)
self.update()
ev.accept()
def eventFilter(self, obj, ev):
if obj is not self.proxy.slider:
return False
if ev.type() != QtCore.QEvent.Wheel:
return False
self.wheelEvent(ev)
return True
# Basic ideas from https://gist.github.com/Riateche/27e36977f7d5ea72cf4f # Basic ideas from https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
class ScanSlider(QtWidgets.QSlider): class ScanSlider(QtWidgets.QSlider):
@ -103,6 +69,9 @@ class ScanSlider(QtWidgets.QSlider):
self.firstMovement = False # State var for handling slider overlap. self.firstMovement = False # State var for handling slider overlap.
self.blockTracking = False self.blockTracking = False
self.setMinimum(0)
self.setMaximum(4095)
# We need fake sliders to keep around so that we can dynamically # We need fake sliders to keep around so that we can dynamically
# set the stylesheets for drawing each slider later. See paintEvent. # set the stylesheets for drawing each slider later. See paintEvent.
self.dummyStartSlider = QtWidgets.QSlider() self.dummyStartSlider = QtWidgets.QSlider()
@ -201,10 +170,6 @@ class ScanSlider(QtWidgets.QSlider):
opt.subControls = QtWidgets.QStyle.SC_SliderHandle opt.subControls = QtWidgets.QStyle.SC_SliderHandle
painter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt) painter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt)
# def triggerAction(self, action, slider):
# if action == QtWidgets.QAbstractSlider.SliderSingleStepAdd:
# if
def setSpan(self, low, high): def setSpan(self, low, high):
# TODO: Is this necessary? QStyle::sliderPositionFromValue appears # TODO: Is this necessary? QStyle::sliderPositionFromValue appears
# to clamp already. # to clamp already.
@ -241,7 +206,7 @@ class ScanSlider(QtWidgets.QSlider):
self.setSpan(self.startVal, self.stopPos) self.setSpan(self.startVal, self.stopPos)
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
if self.minimum() == self.maximum() or (ev.buttons() ^ ev.button()): if ev.buttons() ^ ev.button():
ev.ignore() ev.ignore()
return return
@ -315,9 +280,9 @@ class ScanSlider(QtWidgets.QSlider):
# Qt will snap sliders to 0 or maximum() if given a desired pixel # Qt will snap sliders to 0 or maximum() if given a desired pixel
# location outside the mapped range. So we manually just don't draw # location outside the mapped range. So we manually just don't draw
# the handles if they are at 0 or max. # the handles if they are at 0 or max.
if self.startVal > 0 and self.startVal < self.maximum(): if self.minimum() < self.startVal < self.maximum():
self.drawHandle(startPainter, "start") self.drawHandle(startPainter, "start")
if self.stopVal > 0 and self.stopVal < self.maximum(): if self.minimum() < self.stopVal < self.maximum():
self.drawHandle(stopPainter, "stop") self.drawHandle(stopPainter, "stop")
@ -326,17 +291,19 @@ class ScanSlider(QtWidgets.QSlider):
class ScanProxy(QtCore.QObject): class ScanProxy(QtCore.QObject):
sigStartMoved = QtCore.pyqtSignal(float) sigStartMoved = QtCore.pyqtSignal(float)
sigStopMoved = QtCore.pyqtSignal(float) sigStopMoved = QtCore.pyqtSignal(float)
sigNumPoints = QtCore.pyqtSignal(int) sigNumChanged = QtCore.pyqtSignal(int)
def __init__(self, slider, axis, zoomMargin, dynamicRange): def __init__(self, slider, axis, zoomMargin, dynamicRange, zoomFactor):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
self.axis = axis self.axis = axis
axis.proxy = self
self.slider = slider self.slider = slider
self.realStart = 0 self.realStart = -1.
self.realStop = 0 self.realStop = 1.
self.numPoints = 10 self.numPoints = 11
self.zoomMargin = zoomMargin self.zoomMargin = zoomMargin
self.dynamicRange = dynamicRange self.dynamicRange = dynamicRange
self.zoomFactor = zoomFactor
# Transform that maps the spinboxes to a pixel position on the # Transform that maps the spinboxes to a pixel position on the
# axis. 0 to axis.width() exclusive indicate positions which will be # axis. 0 to axis.width() exclusive indicate positions which will be
@ -347,6 +314,12 @@ class ScanProxy(QtCore.QObject):
self.realToPixelTransform = -self.axis.width()/2, 1. self.realToPixelTransform = -self.axis.width()/2, 1.
self.invalidOldSizeExpected = True self.invalidOldSizeExpected = True
# Connect event observers.
axis.installEventFilter(self)
slider.installEventFilter(self)
slider.sigStopMoved.connect(self.handleStopMoved)
slider.sigStartMoved.connect(self.handleStartMoved)
# pixel vals for sliders: 0 to slider_width - 1 # pixel vals for sliders: 0 to slider_width - 1
def realToPixel(self, val): def realToPixel(self, val):
a, b = self.realToPixelTransform a, b = self.realToPixelTransform
@ -381,14 +354,15 @@ class ScanProxy(QtCore.QObject):
self.axis.update() self.axis.update()
def handleStopMoved(self, rangeVal): def handleStopMoved(self, rangeVal):
# FIXME: this relies on the event being fed back and ending up calling
# moveStop()
self.sigStopMoved.emit(self.rangeToReal(rangeVal)) self.sigStopMoved.emit(self.rangeToReal(rangeVal))
def handleStartMoved(self, rangeVal): def handleStartMoved(self, rangeVal):
# FIXME: this relies on the event being fed back and ending up calling
# moveStart()
self.sigStartMoved.emit(self.rangeToReal(rangeVal)) self.sigStartMoved.emit(self.rangeToReal(rangeVal))
def handleNumPoints(self, inc):
self.sigNumPoints.emit(self.numPoints + inc)
def setNumPoints(self, val): def setNumPoints(self, val):
self.numPoints = val self.numPoints = val
self.axis.update() self.axis.update()
@ -446,28 +420,55 @@ class ScanProxy(QtCore.QObject):
highRange = 1 - self.zoomMargin highRange = 1 - self.zoomMargin
newStart = self.pixelToReal(lowRange * self.slider.effectiveWidth()) newStart = self.pixelToReal(lowRange * self.slider.effectiveWidth())
newStop = self.pixelToReal(highRange * self.slider.effectiveWidth()) newStop = self.pixelToReal(highRange * self.slider.effectiveWidth())
sliderRange = self.slider.maximum() - self.slider.minimum()
# Signals won't fire unless slider was actually grabbed, so # Signals won't fire unless slider was actually grabbed, so
# manually update so the spinboxes know that knew values were set. # manually update so the spinboxes know that knew values were set.
# self.realStop/Start and the sliders themselves will be updated as a # self.realStop/Start and the sliders themselves will be updated as a
# consequence of ValueChanged signal in spinboxes. The slider widget # consequence of ValueChanged signal in spinboxes. The slider widget
# has guards against recursive signals in setSpan(). # has guards against recursive signals in setSpan().
if sliderRange > 0: # FIXME: this relies on the events being fed back and ending up
self.sigStopMoved.emit(newStop) # calling moveStart() and moveStop()
self.sigStartMoved.emit(newStart) self.sigStopMoved.emit(newStop)
self.sigStartMoved.emit(newStart)
def wheelEvent(self, ev):
y = ev.angleDelta().y()
if y:
if ev.modifiers() & QtCore.Qt.ShiftModifier:
# If shift+scroll, modify number of points.
# TODO: This is not perfect. For high-resolution touchpads you
# get many small events with y < 120 which should accumulate.
# That would also match the wheel behavior of an integer
# spinbox.
z = int(y / 120.)
# FIXME: this relies on the event being fed back and ending up
# calling setNumPoints()
self.sigNumChanged.emit(self.numPoints + z)
self.axis.update()
else:
z = self.zoomFactor**(y / 120.)
# Remove the slider-handle shift correction, b/c none of the
# other widgets know about it. If we have the mouse directly
# over a tick during a zoom, it should appear as if we are
# doing zoom relative to the ticks which live in axis
# pixel-space, not slider pixel-space.
self.handleZoom(z, ev.x() - self.slider.handleWidth()/2)
ev.accept()
def eventFilter(self, obj, ev): def eventFilter(self, obj, ev):
if obj != self.axis: if ev.type() == QtCore.QEvent.Wheel:
return False self.wheelEvent(ev)
if ev.type() != QtCore.QEvent.Resize: return True
if not (obj is self.axis and ev.type() == QtCore.QEvent.Resize):
return False return False
if ev.oldSize().isValid(): if ev.oldSize().isValid():
oldLeft = self.pixelToReal(0) oldLeft = self.pixelToReal(0)
refWidth = ev.oldSize().width() - self.slider.handleWidth() refWidth = ev.oldSize().width() - self.slider.handleWidth()
refRight = self.pixelToReal(refWidth) refRight = self.pixelToReal(refWidth)
newWidth = ev.size().width() - self.slider.handleWidth() newWidth = ev.size().width() - self.slider.handleWidth()
# assert refRight > oldLeft
newScale = newWidth/(refRight - oldLeft) newScale = newWidth/(refRight - oldLeft)
center = (self.realStop + self.realStart)/2
if center:
newScale = min(newScale, self.dynamicRange/abs(center))
self.realToPixelTransform = oldLeft, newScale self.realToPixelTransform = oldLeft, newScale
else: else:
# TODO: self.axis.width() is invalid during object # TODO: self.axis.width() is invalid during object
@ -484,8 +485,6 @@ class ScanProxy(QtCore.QObject):
# the slider has already resized itsef or not. # the slider has already resized itsef or not.
self.viewRangeInit() self.viewRangeInit()
self.invalidOldSizeExpected = False self.invalidOldSizeExpected = False
# assert self.pixelToReal(0) == oldLeft, \
# "{}, {}".format(self.pixelToReal(0), oldLeft)
# Slider will update independently, making sure that the old # Slider will update independently, making sure that the old
# slider positions are preserved. Because of this, we can be # slider positions are preserved. Because of this, we can be
# confident that the new slider position will still map to the # confident that the new slider position will still map to the
@ -498,43 +497,34 @@ class ScanWidget(QtWidgets.QWidget):
sigStopMoved = QtCore.pyqtSignal(float) sigStopMoved = QtCore.pyqtSignal(float)
sigNumChanged = QtCore.pyqtSignal(int) sigNumChanged = QtCore.pyqtSignal(int)
def __init__(self, zoomFactor=1.05, zoomMargin=.1, dynamicRange=1e8): def __init__(self, zoomFactor=1.05, zoomMargin=.1, dynamicRange=1e9):
QtWidgets.QWidget.__init__(self) QtWidgets.QWidget.__init__(self)
self.slider = slider = ScanSlider() self.slider = slider = ScanSlider()
self.axis = axis = ScanAxis(zoomFactor) self.axis = axis = ScanAxis()
self.proxy = ScanProxy(slider, axis, zoomMargin, dynamicRange) self.proxy = ScanProxy(slider, axis, zoomMargin, dynamicRange,
axis.proxy = self.proxy zoomFactor)
slider.setMaximum(1023)
# Layout. # Layout.
layout = QtWidgets.QGridLayout() layout = QtWidgets.QVBoxLayout()
# Default size will cause axis to disappear otherwise. layout.setSpacing(0)
layout.setRowMinimumHeight(0, 40) layout.addWidget(axis)
layout.addWidget(axis, 0, 0, 1, -1) layout.addWidget(slider)
layout.addWidget(slider, 1, 0, 1, -1)
self.setLayout(layout) self.setLayout(layout)
# Connect signals (minus context menu) # Connect signals (minus context menu)
slider.sigStopMoved.connect(self.proxy.handleStopMoved)
slider.sigStartMoved.connect(self.proxy.handleStartMoved)
self.proxy.sigStopMoved.connect(self.sigStopMoved) self.proxy.sigStopMoved.connect(self.sigStopMoved)
self.proxy.sigStartMoved.connect(self.sigStartMoved) self.proxy.sigStartMoved.connect(self.sigStartMoved)
self.proxy.sigNumPoints.connect(self.sigNumChanged) self.proxy.sigNumChanged.connect(self.sigNumChanged)
axis.sigZoom.connect(self.proxy.handleZoom)
axis.sigPoints.connect(self.proxy.handleNumPoints)
# Connect event observers.
axis.installEventFilter(self.proxy)
slider.installEventFilter(axis)
# Context menu entries # Context menu entries
self.viewRangeAct = QtWidgets.QAction("&View Range", self) self.menu = QtWidgets.QMenu(self)
self.snapRangeAct = QtWidgets.QAction("&Snap Range", self) viewRangeAct = QtWidgets.QAction("&View Range", self)
self.viewRangeAct.triggered.connect(self.viewRange) viewRangeAct.triggered.connect(self.viewRange)
self.snapRangeAct.triggered.connect(self.snapRange) self.menu.addAction(viewRangeAct)
snapRangeAct = QtWidgets.QAction("&Snap Range", self)
snapRangeAct.triggered.connect(self.snapRange)
self.menu.addAction(snapRangeAct)
# Spinbox and button slots. Any time the spinboxes change, ScanWidget
# mirrors it and passes the information to the proxy.
def setStop(self, val): def setStop(self, val):
self.proxy.moveStop(val) self.proxy.moveStop(val)
@ -551,7 +541,4 @@ class ScanWidget(QtWidgets.QWidget):
self.proxy.snapRange() self.proxy.snapRange()
def contextMenuEvent(self, ev): def contextMenuEvent(self, ev):
menu = QtWidgets.QMenu(self) self.menu.popup(ev.globalPos())
menu.addAction(self.viewRangeAct)
menu.addAction(self.snapRangeAct)
menu.exec(ev.globalPos())