forked from M-Labs/artiq
scanwidget: apply changes as of 10439cb
This commit is contained in:
parent
e4b854b8bf
commit
7f3e1c989d
|
@ -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())
|
||||||
|
|
Loading…
Reference in New Issue