forked from M-Labs/artiq
1
0
Fork 0

scanwidget: apply changes as of 10439cb

This commit is contained in:
Robert Jördens 2016-03-11 18:29:21 +01:00
parent e4b854b8bf
commit 7f3e1c989d
1 changed files with 47 additions and 43 deletions

View File

@ -1,9 +1,14 @@
import logging
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
from numpy import linspace from numpy import linspace
from .ticker import Ticker from .ticker import Ticker
logger = logging.getLogger(__name__)
class ScanAxis(QtWidgets.QWidget): class ScanAxis(QtWidgets.QWidget):
sigZoom = QtCore.pyqtSignal(float, int) sigZoom = QtCore.pyqtSignal(float, int)
sigPoints = QtCore.pyqtSignal(int) sigPoints = QtCore.pyqtSignal(int)
@ -52,6 +57,10 @@ class ScanAxis(QtWidgets.QWidget):
if y: if y:
if ev.modifiers() & QtCore.Qt.ShiftModifier: if ev.modifiers() & QtCore.Qt.ShiftModifier:
# If shift+scroll, modify number of points. # 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.) z = int(y / 120.)
self.sigPoints.emit(z) self.sigPoints.emit(z)
else: else:
@ -319,14 +328,14 @@ class ScanProxy(QtCore.QObject):
sigStopMoved = QtCore.pyqtSignal(float) sigStopMoved = QtCore.pyqtSignal(float)
sigNumPoints = QtCore.pyqtSignal(int) sigNumPoints = QtCore.pyqtSignal(int)
def __init__(self, slider, axis, rangeFactor, dynamicRange): def __init__(self, slider, axis, zoomMargin, dynamicRange):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
self.axis = axis self.axis = axis
self.slider = slider self.slider = slider
self.realStart = 0 self.realStart = 0
self.realStop = 0 self.realStop = 0
self.numPoints = 10 self.numPoints = 10
self.rangeFactor = rangeFactor self.zoomMargin = zoomMargin
self.dynamicRange = dynamicRange self.dynamicRange = dynamicRange
# Transform that maps the spinboxes to a pixel position on the # Transform that maps the spinboxes to a pixel position on the
@ -335,13 +344,13 @@ class ScanProxy(QtCore.QObject):
# Because the axis's width will change when placed within a layout, # Because the axis's width will change when placed within a layout,
# the realToPixelTransform will initially be invalid. It will be set # the realToPixelTransform will initially be invalid. It will be set
# properly during the first resizeEvent, with the below transform. # properly during the first resizeEvent, with the below transform.
self.realToPixelTransform = self.axis.width()/2, 1. self.realToPixelTransform = -self.axis.width()/2, 1.
self.invalidOldSizeExpected = True self.invalidOldSizeExpected = True
# 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
rawVal = b*(val + a) rawVal = b*(val - a)
# Clamp pixel values to 32 bits, b/c Qt will otherwise wrap values. # Clamp pixel values to 32 bits, b/c Qt will otherwise wrap values.
rawVal = min(max(-(1 << 31), rawVal), (1 << 31) - 1) rawVal = min(max(-(1 << 31), rawVal), (1 << 31) - 1)
return rawVal return rawVal
@ -349,7 +358,7 @@ class ScanProxy(QtCore.QObject):
# Get a point from pixel units to what the sliders display. # Get a point from pixel units to what the sliders display.
def pixelToReal(self, val): def pixelToReal(self, val):
a, b = self.realToPixelTransform a, b = self.realToPixelTransform
return val/b - a return val/b + a
def rangeToReal(self, val): def rangeToReal(self, val):
pixelVal = self.slider.rangeValueToPixelPos(val) pixelVal = self.slider.rangeValueToPixelPos(val)
@ -387,26 +396,22 @@ class ScanProxy(QtCore.QObject):
def handleZoom(self, zoomFactor, mouseXPos): def handleZoom(self, zoomFactor, mouseXPos):
newScale = self.realToPixelTransform[1] * zoomFactor newScale = self.realToPixelTransform[1] * zoomFactor
refReal = self.pixelToReal(mouseXPos) refReal = self.pixelToReal(mouseXPos)
newLeft = mouseXPos/newScale - refReal newLeft = refReal - mouseXPos/newScale
if abs(newLeft*newScale) > self.dynamicRange: newZero = newLeft*newScale + self.slider.effectiveWidth()/2
if zoomFactor > 1 and abs(newZero) > self.dynamicRange:
return return
self.realToPixelTransform = newLeft, newScale self.realToPixelTransform = newLeft, newScale
self.moveStop(self.realStop) self.moveStop(self.realStop)
self.moveStart(self.realStart) self.moveStart(self.realStart)
def zoomToFit(self): def viewRange(self):
currRangeReal = abs(self.realStop - self.realStart) newScale = self.slider.effectiveWidth()/abs(
# Slider closest to the left should be used to find the new axis left. self.realStop - self.realStart)
if self.realStop < self.realStart: newScale *= 1 - 2*self.zoomMargin
refSlider = self.realStop newCenter = (self.realStop + self.realStart)/2
else: if newCenter:
refSlider = self.realStart newScale = min(newScale, self.dynamicRange/abs(newCenter))
if self.rangeFactor <= 2 or currRangeReal == 0: newLeft = newCenter - self.slider.effectiveWidth()/2/newScale
return # Ill-formed snap range- do nothing
proportion = self.rangeFactor/(self.rangeFactor - 2)
newScale = self.slider.effectiveWidth()/(proportion*currRangeReal)
newLeft = (self.slider.effectiveWidth()/(self.rangeFactor*newScale) -
refSlider)
self.realToPixelTransform = newLeft, newScale self.realToPixelTransform = newLeft, newScale
self.moveStop(self.realStop) self.moveStop(self.realStop)
self.moveStart(self.realStart) self.moveStart(self.realStart)
@ -419,9 +424,9 @@ class ScanProxy(QtCore.QObject):
# This function handles handles the slider positions. Slider and axis # This function handles handles the slider positions. Slider and axis
# handle its own width changes; proxy watches for axis width resizeEvent to # handle its own width changes; proxy watches for axis width resizeEvent to
# alter mapping from real to pixel space. # alter mapping from real to pixel space.
def zoomToFitInit(self): def viewRangeInit(self):
currRangeReal = abs(self.realStop - self.realStart) currRangeReal = abs(self.realStop - self.realStart)
if self.rangeFactor <= 2 or currRangeReal == 0: if currRangeReal == 0:
self.moveStop(self.realStop) self.moveStop(self.realStop)
self.moveStart(self.realStart) self.moveStart(self.realStart)
# Ill-formed snap range- move the sliders anyway, # Ill-formed snap range- move the sliders anyway,
@ -430,15 +435,15 @@ class ScanProxy(QtCore.QObject):
# This will force the sliders to have positions on the axis # This will force the sliders to have positions on the axis
# which reflect the start/stop values currently set. # which reflect the start/stop values currently set.
else: else:
self.zoomToFit() self.viewRange()
# Notify spinboxes manually, since slider wasn't clicked and will # Notify spinboxes manually, since slider wasn't clicked and will
# therefore not emit signals. # therefore not emit signals.
self.sigStopMoved.emit(self.realStop) self.sigStopMoved.emit(self.realStop)
self.sigStartMoved.emit(self.realStart) self.sigStartMoved.emit(self.realStart)
def fitToView(self): def snapRange(self):
lowRange = 1.0/self.rangeFactor lowRange = self.zoomMargin
highRange = (self.rangeFactor - 1)/self.rangeFactor 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() sliderRange = self.slider.maximum() - self.slider.minimum()
@ -463,21 +468,21 @@ class ScanProxy(QtCore.QObject):
newWidth = ev.size().width() - self.slider.handleWidth() newWidth = ev.size().width() - self.slider.handleWidth()
# assert refRight > oldLeft # assert refRight > oldLeft
newScale = newWidth/(refRight - oldLeft) newScale = newWidth/(refRight - oldLeft)
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
# construction. The width will change when placed in a # construction. The width will change when placed in a
# layout WITHOUT a resizeEvent. Why? # layout WITHOUT a resizeEvent. Why?
oldLeft = -ev.size().width()/2 oldLeft = -ev.size().width()/2
newScale = 1.0 newScale = 1.0
self.realToPixelTransform = -oldLeft, newScale self.realToPixelTransform = oldLeft, newScale
# We need to reinitialize the pixel transform b/c the old width # We need to reinitialize the pixel transform b/c the old width
# of the axis is no longer valid. When we have a valid transform, # of the axis is no longer valid. When we have a valid transform,
# we can then zoomToFit based on the desired real values. # we can then viewRange based on the desired real values.
# The slider handle values are invalid before this point as well; # The slider handle values are invalid before this point as well;
# we set them to the correct value here, regardless of whether # we set them to the correct value here, regardless of whether
# the slider has already resized itsef or not. # the slider has already resized itsef or not.
self.zoomToFitInit() self.viewRangeInit()
self.invalidOldSizeExpected = False self.invalidOldSizeExpected = False
# assert self.pixelToReal(0) == oldLeft, \ # assert self.pixelToReal(0) == oldLeft, \
# "{}, {}".format(self.pixelToReal(0), oldLeft) # "{}, {}".format(self.pixelToReal(0), oldLeft)
@ -493,13 +498,12 @@ 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, rangeFactor=6, dynamicRange=1e8): def __init__(self, zoomFactor=1.05, zoomMargin=.1, dynamicRange=1e8):
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(zoomFactor)
self.proxy = ScanProxy(slider, axis, rangeFactor, dynamicRange) self.proxy = ScanProxy(slider, axis, zoomMargin, dynamicRange)
axis.proxy = self.proxy axis.proxy = self.proxy
axis.slider = slider
slider.setMaximum(1023) slider.setMaximum(1023)
# Layout. # Layout.
@ -524,10 +528,10 @@ class ScanWidget(QtWidgets.QWidget):
slider.installEventFilter(axis) slider.installEventFilter(axis)
# Context menu entries # Context menu entries
self.zoomToFitAct = QtWidgets.QAction("&View Range", self) self.viewRangeAct = QtWidgets.QAction("&View Range", self)
self.fitToViewAct = QtWidgets.QAction("&Snap Range", self) self.snapRangeAct = QtWidgets.QAction("&Snap Range", self)
self.zoomToFitAct.triggered.connect(self.zoomToFit) self.viewRangeAct.triggered.connect(self.viewRange)
self.fitToViewAct.triggered.connect(self.fitToView) self.snapRangeAct.triggered.connect(self.snapRange)
# Spinbox and button slots. Any time the spinboxes change, ScanWidget # Spinbox and button slots. Any time the spinboxes change, ScanWidget
# mirrors it and passes the information to the proxy. # mirrors it and passes the information to the proxy.
@ -540,14 +544,14 @@ class ScanWidget(QtWidgets.QWidget):
def setNumPoints(self, val): def setNumPoints(self, val):
self.proxy.setNumPoints(val) self.proxy.setNumPoints(val)
def zoomToFit(self): def viewRange(self):
self.proxy.zoomToFit() self.proxy.viewRange()
def fitToView(self): def snapRange(self):
self.proxy.fitToView() self.proxy.snapRange()
def contextMenuEvent(self, ev): def contextMenuEvent(self, ev):
menu = QtWidgets.QMenu(self) menu = QtWidgets.QMenu(self)
menu.addAction(self.zoomToFitAct) menu.addAction(self.viewRangeAct)
menu.addAction(self.fitToViewAct) menu.addAction(self.snapRangeAct)
menu.exec(ev.globalPos()) menu.exec(ev.globalPos())