forked from M-Labs/artiq
scanwidget: apply changes as of 579bf5e
This commit is contained in:
parent
d34d83f35c
commit
78f2706aa8
@ -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())
|
|
||||||
|
Loading…
Reference in New Issue
Block a user