forked from M-Labs/artiq
scanwidget: changes as of 19a1d40 (closes #330)
This commit is contained in:
parent
8ad799a850
commit
1dc72635a0
|
@ -62,23 +62,20 @@ class ScanSlider(QtWidgets.QSlider):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QSlider.__init__(self, QtCore.Qt.Horizontal)
|
QtWidgets.QSlider.__init__(self, QtCore.Qt.Horizontal)
|
||||||
self.startPos = 0 # Pos and Val can differ in event handling.
|
self.startVal = None
|
||||||
# perhaps prevPos and currPos is more accurate.
|
self.stopVal = None
|
||||||
self.stopPos = 99
|
self.offset = None
|
||||||
self.startVal = 0 # lower
|
self.position = None
|
||||||
self.stopVal = 99 # upper
|
self.pressed = None
|
||||||
self.offset = 0
|
|
||||||
self.position = 0
|
|
||||||
self.upperPressed = QtWidgets.QStyle.SC_None
|
|
||||||
self.lowerPressed = QtWidgets.QStyle.SC_None
|
|
||||||
self.firstMovement = False # State var for handling slider overlap.
|
|
||||||
self.blockTracking = False
|
|
||||||
|
|
||||||
self.setMinimum(0)
|
self.setRange(0, (1 << 15) - 1)
|
||||||
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.
|
||||||
|
# QPalettes would be nicer to use, since palette entries can be set
|
||||||
|
# individually for each slider handle, but Windows 7 does not
|
||||||
|
# use them. This seems to be the only way to override the colors
|
||||||
|
# regardless of platform.
|
||||||
self.dummyStartSlider = QtWidgets.QSlider()
|
self.dummyStartSlider = QtWidgets.QSlider()
|
||||||
self.dummyStopSlider = QtWidgets.QSlider()
|
self.dummyStopSlider = QtWidgets.QSlider()
|
||||||
self.dummyStartSlider.setStyleSheet(
|
self.dummyStartSlider.setStyleSheet(
|
||||||
|
@ -86,19 +83,6 @@ class ScanSlider(QtWidgets.QSlider):
|
||||||
self.dummyStopSlider.setStyleSheet(
|
self.dummyStopSlider.setStyleSheet(
|
||||||
"QSlider::handle {background:red}")
|
"QSlider::handle {background:red}")
|
||||||
|
|
||||||
# We basically superimpose two QSliders on top of each other, discarding
|
|
||||||
# the state that remains constant between the two when drawing.
|
|
||||||
# Everything except the handles remain constant.
|
|
||||||
def initHandleStyleOption(self, opt, handle):
|
|
||||||
self.initStyleOption(opt)
|
|
||||||
if handle == "start":
|
|
||||||
opt.sliderPosition = self.startPos
|
|
||||||
opt.sliderValue = self.startVal
|
|
||||||
elif handle == "stop":
|
|
||||||
opt.sliderPosition = self.stopPos
|
|
||||||
opt.sliderValue = self.stopVal
|
|
||||||
|
|
||||||
# We get the range of each slider separately.
|
|
||||||
def pixelPosToRangeValue(self, pos):
|
def pixelPosToRangeValue(self, pos):
|
||||||
opt = QtWidgets.QStyleOptionSlider()
|
opt = QtWidgets.QStyleOptionSlider()
|
||||||
self.initStyleOption(opt)
|
self.initStyleOption(opt)
|
||||||
|
@ -137,98 +121,59 @@ class ScanSlider(QtWidgets.QSlider):
|
||||||
self)
|
self)
|
||||||
return gr.width() - self.handleWidth()
|
return gr.width() - self.handleWidth()
|
||||||
|
|
||||||
def handleMousePress(self, pos, control, val, handle):
|
def _getStyleOptionSlider(self, val):
|
||||||
opt = QtWidgets.QStyleOptionSlider()
|
|
||||||
self.initHandleStyleOption(opt, handle)
|
|
||||||
startAtEdges = (handle == "start" and
|
|
||||||
(self.startVal == self.minimum() or
|
|
||||||
self.startVal == self.maximum()))
|
|
||||||
stopAtEdges = (handle == "stop" and
|
|
||||||
(self.stopVal == self.minimum() or
|
|
||||||
self.stopVal == self.maximum()))
|
|
||||||
|
|
||||||
# If chosen slider at edge, treat it as non-interactive.
|
|
||||||
if startAtEdges or stopAtEdges:
|
|
||||||
return QtWidgets.QStyle.SC_None
|
|
||||||
|
|
||||||
oldControl = control
|
|
||||||
control = self.style().hitTestComplexControl(
|
|
||||||
QtWidgets.QStyle.CC_Slider, opt, pos, self)
|
|
||||||
sr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
|
|
||||||
QtWidgets.QStyle.SC_SliderHandle,
|
|
||||||
self)
|
|
||||||
if control == QtWidgets.QStyle.SC_SliderHandle:
|
|
||||||
# no pick()- slider orientation static
|
|
||||||
self.offset = pos.x() - sr.topLeft().x()
|
|
||||||
self.setSliderDown(True)
|
|
||||||
# emit
|
|
||||||
|
|
||||||
# Needed?
|
|
||||||
if control != oldControl:
|
|
||||||
self.update(sr)
|
|
||||||
return control
|
|
||||||
|
|
||||||
def drawHandle(self, painter, handle):
|
|
||||||
opt = QtWidgets.QStyleOptionSlider()
|
opt = QtWidgets.QStyleOptionSlider()
|
||||||
self.initStyleOption(opt)
|
self.initStyleOption(opt)
|
||||||
self.initHandleStyleOption(opt, handle)
|
opt.sliderPosition = val
|
||||||
|
opt.sliderValue = val
|
||||||
opt.subControls = QtWidgets.QStyle.SC_SliderHandle
|
opt.subControls = QtWidgets.QStyle.SC_SliderHandle
|
||||||
painter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt)
|
return opt
|
||||||
|
|
||||||
def setSpan(self, low, high):
|
def _hitHandle(self, pos, val):
|
||||||
# TODO: Is this necessary? QStyle::sliderPositionFromValue appears
|
# If chosen slider at edge, treat it as non-interactive.
|
||||||
# to clamp already.
|
if not (self.minimum() < val < self.maximum()):
|
||||||
low = min(max(self.minimum(), low), self.maximum())
|
return False
|
||||||
high = min(max(self.minimum(), high), self.maximum())
|
opt = self._getStyleOptionSlider(val)
|
||||||
|
control = self.style().hitTestComplexControl(
|
||||||
if low != self.startVal or high != self.stopVal:
|
QtWidgets.QStyle.CC_Slider, opt, pos, self)
|
||||||
if low != self.startVal:
|
if control != QtWidgets.QStyle.SC_SliderHandle:
|
||||||
self.startVal = low
|
return False
|
||||||
self.startPos = low
|
sr = self.style().subControlRect(
|
||||||
if high != self.stopVal:
|
QtWidgets.QStyle.CC_Slider, opt,
|
||||||
self.stopVal = high
|
QtWidgets.QStyle.SC_SliderHandle, self)
|
||||||
self.stopPos = high
|
self.offset = pos.x() - sr.topLeft().x()
|
||||||
self.update()
|
self.setSliderDown(True)
|
||||||
|
# Needed?
|
||||||
|
self.update(sr)
|
||||||
|
return True
|
||||||
|
|
||||||
def setStartPosition(self, val):
|
def setStartPosition(self, val):
|
||||||
if val != self.startPos:
|
if val == self.startVal:
|
||||||
self.startPos = val
|
return
|
||||||
if not self.hasTracking():
|
self.startVal = val
|
||||||
self.update()
|
self.update()
|
||||||
if self.isSliderDown():
|
|
||||||
self.sigStartMoved.emit(self.startPos)
|
|
||||||
if self.hasTracking() and not self.blockTracking:
|
|
||||||
self.setSpan(self.startPos, self.stopVal)
|
|
||||||
|
|
||||||
def setStopPosition(self, val):
|
def setStopPosition(self, val):
|
||||||
if val != self.stopPos:
|
if val == self.stopVal:
|
||||||
self.stopPos = val
|
return
|
||||||
if not self.hasTracking():
|
self.stopVal = val
|
||||||
self.update()
|
self.update()
|
||||||
if self.isSliderDown():
|
|
||||||
self.sigStopMoved.emit(self.stopPos)
|
|
||||||
if self.hasTracking() and not self.blockTracking:
|
|
||||||
self.setSpan(self.startVal, self.stopPos)
|
|
||||||
|
|
||||||
def mousePressEvent(self, ev):
|
def mousePressEvent(self, ev):
|
||||||
if ev.buttons() ^ ev.button():
|
if ev.buttons() ^ ev.button():
|
||||||
ev.ignore()
|
ev.ignore()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Prefer stopVal in the default case.
|
# Prefer stopVal in the default case.
|
||||||
self.upperPressed = self.handleMousePress(
|
if self._hitHandle(ev.pos(), self.stopVal):
|
||||||
ev.pos(), self.upperPressed, self.stopVal, "stop")
|
self.pressed = "stop"
|
||||||
if self.upperPressed != QtWidgets.QStyle.SC_SliderHandle:
|
elif self._hitHandle(ev.pos(), self.startVal):
|
||||||
self.lowerPressed = self.handleMousePress(
|
self.pressed = "start"
|
||||||
ev.pos(), self.upperPressed, self.startVal, "start")
|
else:
|
||||||
|
self.pressed = None
|
||||||
# State that is needed to handle the case where two sliders are equal.
|
|
||||||
self.firstMovement = True
|
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
if (self.lowerPressed != QtWidgets.QStyle.SC_SliderHandle and
|
if not self.pressed:
|
||||||
self.upperPressed != QtWidgets.QStyle.SC_SliderHandle):
|
|
||||||
ev.ignore()
|
ev.ignore()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -246,49 +191,38 @@ class ScanSlider(QtWidgets.QSlider):
|
||||||
if not r.contains(ev.pos()):
|
if not r.contains(ev.pos()):
|
||||||
newPos = self.position
|
newPos = self.position
|
||||||
|
|
||||||
if self.firstMovement:
|
if self.pressed == "start":
|
||||||
if self.startPos == self.stopPos:
|
|
||||||
# StopSlider is preferred, except in the case where
|
|
||||||
# start == max possible value the slider can take.
|
|
||||||
if self.startPos == self.maximum():
|
|
||||||
self.lowerPressed = QtWidgets.QStyle.SC_SliderHandle
|
|
||||||
self.upperPressed = QtWidgets.QStyle.SC_None
|
|
||||||
self.firstMovement = False
|
|
||||||
|
|
||||||
if self.lowerPressed == QtWidgets.QStyle.SC_SliderHandle:
|
|
||||||
self.setStartPosition(newPos)
|
self.setStartPosition(newPos)
|
||||||
|
if self.hasTracking():
|
||||||
if self.upperPressed == QtWidgets.QStyle.SC_SliderHandle:
|
self.sigStartMoved.emit(self.startVal)
|
||||||
|
elif self.pressed == "stop":
|
||||||
self.setStopPosition(newPos)
|
self.setStopPosition(newPos)
|
||||||
|
if self.hasTracking():
|
||||||
|
self.sigStopMoved.emit(self.stopVal)
|
||||||
|
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
QtWidgets.QSlider.mouseReleaseEvent(self, ev)
|
QtWidgets.QSlider.mouseReleaseEvent(self, ev)
|
||||||
self.setSliderDown(False) # AbstractSlider needs this
|
self.setSliderDown(False) # AbstractSlider needs this
|
||||||
self.lowerPressed = QtWidgets.QStyle.SC_None
|
if not self.hasTracking():
|
||||||
self.upperPressed = QtWidgets.QStyle.SC_None
|
if self.pressed == "start":
|
||||||
|
self.sigStartMoved.emit(self.startVal)
|
||||||
|
elif self.pressed == "stop":
|
||||||
|
self.sigStopMoved.emit(self.stopVal)
|
||||||
|
self.pressed = None
|
||||||
|
|
||||||
def paintEvent(self, ev):
|
def paintEvent(self, ev):
|
||||||
# Use QStylePainters to make redrawing as painless as possible.
|
# Use the pre-parsed, styled sliders.
|
||||||
# Paint on the custom widget, using the attributes of the fake
|
|
||||||
# slider references we keep around. setStyleSheet within paintEvent
|
|
||||||
# leads to heavy performance penalties (and recursion?).
|
|
||||||
# QPalettes would be nicer to use, since palette entries can be set
|
|
||||||
# individually for each slider handle, but Windows 7 does not
|
|
||||||
# use them. This seems to be the only way to override the colors
|
|
||||||
# regardless of platform.
|
|
||||||
startPainter = QtWidgets.QStylePainter(self, self.dummyStartSlider)
|
startPainter = QtWidgets.QStylePainter(self, self.dummyStartSlider)
|
||||||
stopPainter = QtWidgets.QStylePainter(self, self.dummyStopSlider)
|
stopPainter = QtWidgets.QStylePainter(self, self.dummyStopSlider)
|
||||||
|
# Only draw handles that are not railed
|
||||||
# Handles
|
|
||||||
# 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
|
|
||||||
# the handles if they are at 0 or max.
|
|
||||||
if self.minimum() < self.startVal < self.maximum():
|
if self.minimum() < self.startVal < self.maximum():
|
||||||
self.drawHandle(startPainter, "start")
|
opt = self._getStyleOptionSlider(self.startVal)
|
||||||
|
startPainter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt)
|
||||||
if self.minimum() < self.stopVal < self.maximum():
|
if self.minimum() < self.stopVal < self.maximum():
|
||||||
self.drawHandle(stopPainter, "stop")
|
opt = self._getStyleOptionSlider(self.stopVal)
|
||||||
|
stopPainter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt)
|
||||||
|
|
||||||
|
|
||||||
# real (Sliders) => pixel (one pixel movement of sliders would increment by X)
|
# real (Sliders) => pixel (one pixel movement of sliders would increment by X)
|
||||||
|
@ -320,27 +254,20 @@ class ScanWidget(QtWidgets.QWidget):
|
||||||
snapRangeAct.triggered.connect(self.snapRange)
|
snapRangeAct.triggered.connect(self.snapRange)
|
||||||
self.menu.addAction(snapRangeAct)
|
self.menu.addAction(snapRangeAct)
|
||||||
|
|
||||||
self.realStart = -1.
|
self.realStart = None
|
||||||
self.realStop = 1.
|
self.realStop = None
|
||||||
self.numPoints = 11
|
self.numPoints = None
|
||||||
self.zoomMargin = zoomMargin
|
self.zoomMargin = zoomMargin
|
||||||
self.dynamicRange = dynamicRange
|
self.dynamicRange = dynamicRange
|
||||||
self.zoomFactor = zoomFactor
|
self.zoomFactor = zoomFactor
|
||||||
|
|
||||||
# Transform that maps the spinboxes to a pixel position on the
|
|
||||||
# axis. 0 to axis.width() exclusive indicate positions which will be
|
|
||||||
# displayed on the axis.
|
|
||||||
# Because the axis's width will change when placed within a layout,
|
|
||||||
# the realToPixelTransform will initially be invalid. It will be set
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Connect event observers.
|
# Connect event observers.
|
||||||
axis.installEventFilter(self)
|
axis.installEventFilter(self)
|
||||||
slider.installEventFilter(self)
|
slider.installEventFilter(self)
|
||||||
slider.sigStopMoved.connect(self.handleStopMoved)
|
slider.sigStopMoved.connect(self._handleStopMoved)
|
||||||
slider.sigStartMoved.connect(self.handleStartMoved)
|
slider.sigStartMoved.connect(self._handleStartMoved)
|
||||||
|
|
||||||
def contextMenuEvent(self, ev):
|
def contextMenuEvent(self, ev):
|
||||||
self.menu.popup(ev.globalPos())
|
self.menu.popup(ev.globalPos())
|
||||||
|
@ -353,7 +280,6 @@ class ScanWidget(QtWidgets.QWidget):
|
||||||
rawVal = min(max(-(1 << 31), rawVal), (1 << 31) - 1)
|
rawVal = min(max(-(1 << 31), rawVal), (1 << 31) - 1)
|
||||||
return rawVal
|
return rawVal
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -366,42 +292,38 @@ class ScanWidget(QtWidgets.QWidget):
|
||||||
pixelVal = self.realToPixel(val)
|
pixelVal = self.realToPixel(val)
|
||||||
return self.slider.pixelPosToRangeValue(pixelVal)
|
return self.slider.pixelPosToRangeValue(pixelVal)
|
||||||
|
|
||||||
|
def setView(self, left, scale):
|
||||||
|
self.realToPixelTransform = left, scale
|
||||||
|
sliderX = self.realToRange(self.realStop)
|
||||||
|
self.slider.setStopPosition(sliderX)
|
||||||
|
sliderX = self.realToRange(self.realStart)
|
||||||
|
self.slider.setStartPosition(sliderX)
|
||||||
|
self.axis.update()
|
||||||
|
|
||||||
def setStop(self, val):
|
def setStop(self, val):
|
||||||
|
if self.realStop == val:
|
||||||
|
return
|
||||||
sliderX = self.realToRange(val)
|
sliderX = self.realToRange(val)
|
||||||
self.slider.setStopPosition(sliderX)
|
self.slider.setStopPosition(sliderX)
|
||||||
self.realStop = val
|
self.realStop = val
|
||||||
self.axis.update() # Number of points ticks changed positions.
|
self.axis.update() # Number of points ticks changed positions.
|
||||||
|
self.sigStopMoved.emit(val)
|
||||||
|
|
||||||
def setStart(self, val):
|
def setStart(self, val):
|
||||||
|
if self.realStart == val:
|
||||||
|
return
|
||||||
sliderX = self.realToRange(val)
|
sliderX = self.realToRange(val)
|
||||||
self.slider.setStartPosition(sliderX)
|
self.slider.setStartPosition(sliderX)
|
||||||
self.realStart = val
|
self.realStart = val
|
||||||
self.axis.update()
|
self.axis.update()
|
||||||
|
self.sigStartMoved.emit(val)
|
||||||
|
|
||||||
def setNumPoints(self, val):
|
def setNumPoints(self, val):
|
||||||
|
if self.numPoints == val:
|
||||||
|
return
|
||||||
self.numPoints = val
|
self.numPoints = val
|
||||||
self.axis.update()
|
self.axis.update()
|
||||||
|
self.sigNumChanged.emit(val)
|
||||||
def handleStopMoved(self, rangeVal):
|
|
||||||
# FIXME: this relies on the event being fed back and ending up calling
|
|
||||||
# setStop()
|
|
||||||
self.sigStopMoved.emit(self.rangeToReal(rangeVal))
|
|
||||||
|
|
||||||
def handleStartMoved(self, rangeVal):
|
|
||||||
# FIXME: this relies on the event being fed back and ending up calling
|
|
||||||
# setStart()
|
|
||||||
self.sigStartMoved.emit(self.rangeToReal(rangeVal))
|
|
||||||
|
|
||||||
def handleZoom(self, zoomFactor, mouseXPos):
|
|
||||||
newScale = self.realToPixelTransform[1] * zoomFactor
|
|
||||||
refReal = self.pixelToReal(mouseXPos)
|
|
||||||
newLeft = refReal - mouseXPos/newScale
|
|
||||||
newZero = newLeft*newScale + self.slider.effectiveWidth()/2
|
|
||||||
if zoomFactor > 1 and abs(newZero) > self.dynamicRange:
|
|
||||||
return
|
|
||||||
self.realToPixelTransform = newLeft, newScale
|
|
||||||
self.setStop(self.realStop)
|
|
||||||
self.setStart(self.realStart)
|
|
||||||
|
|
||||||
def viewRange(self):
|
def viewRange(self):
|
||||||
newScale = self.slider.effectiveWidth()/abs(
|
newScale = self.slider.effectiveWidth()/abs(
|
||||||
|
@ -411,53 +333,39 @@ class ScanWidget(QtWidgets.QWidget):
|
||||||
if newCenter:
|
if newCenter:
|
||||||
newScale = min(newScale, self.dynamicRange/abs(newCenter))
|
newScale = min(newScale, self.dynamicRange/abs(newCenter))
|
||||||
newLeft = newCenter - self.slider.effectiveWidth()/2/newScale
|
newLeft = newCenter - self.slider.effectiveWidth()/2/newScale
|
||||||
self.realToPixelTransform = newLeft, newScale
|
self.setView(newLeft, newScale)
|
||||||
self.setStop(self.realStop)
|
|
||||||
self.setStart(self.realStart)
|
|
||||||
self.axis.update() # Axis normally takes care to update itself during
|
|
||||||
# zoom. In this code path however, the zoom didn't arrive via the axis
|
|
||||||
# widget, so we need to notify manually.
|
|
||||||
|
|
||||||
# This function is called if the axis width, slider width, and slider
|
|
||||||
# positions are in an inconsistent state, to initialize the widget.
|
|
||||||
# This function handles handles the slider positions. Slider and axis
|
|
||||||
# handle its own width changes; proxy watches for axis width resizeEvent to
|
|
||||||
# alter mapping from real to pixel space.
|
|
||||||
def viewRangeInit(self):
|
|
||||||
currRangeReal = abs(self.realStop - self.realStart)
|
|
||||||
if currRangeReal == 0:
|
|
||||||
self.setStop(self.realStop)
|
|
||||||
self.setStart(self.realStart)
|
|
||||||
# Ill-formed snap range- move the sliders anyway,
|
|
||||||
# because we arrived here during widget
|
|
||||||
# initialization, where the slider positions are likely invalid.
|
|
||||||
# This will force the sliders to have positions on the axis
|
|
||||||
# which reflect the start/stop values currently set.
|
|
||||||
else:
|
|
||||||
self.viewRange()
|
|
||||||
# Notify spinboxes manually, since slider wasn't clicked and will
|
|
||||||
# therefore not emit signals.
|
|
||||||
self.sigStopMoved.emit(self.realStop)
|
|
||||||
self.sigStartMoved.emit(self.realStart)
|
|
||||||
|
|
||||||
def snapRange(self):
|
def snapRange(self):
|
||||||
lowRange = self.zoomMargin
|
lowRange = self.zoomMargin
|
||||||
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())
|
||||||
# Signals won't fire unless slider was actually grabbed, so
|
self.setStart(newStart)
|
||||||
# manually update so the spinboxes know that knew values were set.
|
self.setStop(newStop)
|
||||||
# self.realStop/Start and the sliders themselves will be updated as a
|
|
||||||
# consequence of ValueChanged signal in spinboxes. The slider widget
|
|
||||||
# has guards against recursive signals in setSpan().
|
|
||||||
# FIXME: this relies on the events being fed back and ending up
|
|
||||||
# calling setStart() and setStop()
|
|
||||||
self.sigStopMoved.emit(newStop)
|
|
||||||
self.sigStartMoved.emit(newStart)
|
|
||||||
|
|
||||||
def wheelEvent(self, ev):
|
def _handleStartMoved(self, rangeVal):
|
||||||
|
val = self.rangeToReal(rangeVal)
|
||||||
|
self.realStart = val
|
||||||
|
self.axis.update()
|
||||||
|
self.sigStartMoved.emit(val)
|
||||||
|
|
||||||
|
def _handleStopMoved(self, rangeVal):
|
||||||
|
val = self.rangeToReal(rangeVal)
|
||||||
|
self.realStop = val
|
||||||
|
self.axis.update()
|
||||||
|
self.sigStopMoved.emit(val)
|
||||||
|
|
||||||
|
def _handleZoom(self, zoomFactor, mouseXPos):
|
||||||
|
newScale = self.realToPixelTransform[1] * zoomFactor
|
||||||
|
refReal = self.pixelToReal(mouseXPos)
|
||||||
|
newLeft = refReal - mouseXPos/newScale
|
||||||
|
newZero = newLeft*newScale + self.slider.effectiveWidth()/2
|
||||||
|
if zoomFactor > 1 and abs(newZero) > self.dynamicRange:
|
||||||
|
return
|
||||||
|
self.setView(newLeft, newScale)
|
||||||
|
|
||||||
|
def _wheelEvent(self, ev):
|
||||||
y = ev.angleDelta().y()
|
y = ev.angleDelta().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
|
# TODO: This is not perfect. For high-resolution touchpads you
|
||||||
|
@ -465,26 +373,25 @@ class ScanWidget(QtWidgets.QWidget):
|
||||||
# That would also match the wheel behavior of an integer
|
# That would also match the wheel behavior of an integer
|
||||||
# spinbox.
|
# spinbox.
|
||||||
z = int(y / 120.)
|
z = int(y / 120.)
|
||||||
# FIXME: this relies on the event being fed back and ending up
|
if z:
|
||||||
# calling setNumPoints()
|
self.setNumPoints(max(1, self.numPoints + z))
|
||||||
self.sigNumChanged.emit(self.numPoints + z)
|
|
||||||
self.axis.update()
|
|
||||||
ev.accept()
|
ev.accept()
|
||||||
elif ev.modifiers() & QtCore.Qt.ControlModifier:
|
elif ev.modifiers() & QtCore.Qt.ControlModifier:
|
||||||
z = self.zoomFactor**(y / 120.)
|
|
||||||
# Remove the slider-handle shift correction, b/c none of the
|
# Remove the slider-handle shift correction, b/c none of the
|
||||||
# other widgets know about it. If we have the mouse directly
|
# other widgets know about it. If we have the mouse directly
|
||||||
# over a tick during a zoom, it should appear as if we are
|
# over a tick during a zoom, it should appear as if we are
|
||||||
# doing zoom relative to the ticks which live in axis
|
# doing zoom relative to the ticks which live in axis
|
||||||
# pixel-space, not slider pixel-space.
|
# pixel-space, not slider pixel-space.
|
||||||
self.handleZoom(z, ev.x() - self.slider.handleWidth()/2)
|
if y:
|
||||||
|
z = self.zoomFactor**(y / 120.)
|
||||||
|
self._handleZoom(z, ev.x() - self.slider.handleWidth()/2)
|
||||||
ev.accept()
|
ev.accept()
|
||||||
else:
|
else:
|
||||||
ev.ignore()
|
ev.ignore()
|
||||||
|
|
||||||
def eventFilter(self, obj, ev):
|
def eventFilter(self, obj, ev):
|
||||||
if ev.type() == QtCore.QEvent.Wheel:
|
if ev.type() == QtCore.QEvent.Wheel:
|
||||||
self.wheelEvent(ev)
|
self._wheelEvent(ev)
|
||||||
return True
|
return True
|
||||||
if not (obj is self.axis and ev.type() == QtCore.QEvent.Resize):
|
if not (obj is self.axis and ev.type() == QtCore.QEvent.Resize):
|
||||||
return False
|
return False
|
||||||
|
@ -497,24 +404,7 @@ class ScanWidget(QtWidgets.QWidget):
|
||||||
center = (self.realStop + self.realStart)/2
|
center = (self.realStop + self.realStart)/2
|
||||||
if center:
|
if center:
|
||||||
newScale = min(newScale, self.dynamicRange/abs(center))
|
newScale = min(newScale, self.dynamicRange/abs(center))
|
||||||
self.realToPixelTransform = oldLeft, newScale
|
self.setView(oldLeft, newScale)
|
||||||
else:
|
else:
|
||||||
# TODO: self.axis.width() is invalid during object
|
self.viewRange()
|
||||||
# construction. The width will change when placed in a
|
|
||||||
# layout WITHOUT a resizeEvent. Why?
|
|
||||||
oldLeft = -ev.size().width()/2
|
|
||||||
newScale = 1.0
|
|
||||||
self.realToPixelTransform = oldLeft, newScale
|
|
||||||
# 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,
|
|
||||||
# we can then viewRange based on the desired real values.
|
|
||||||
# The slider handle values are invalid before this point as well;
|
|
||||||
# we set them to the correct value here, regardless of whether
|
|
||||||
# the slider has already resized itsef or not.
|
|
||||||
self.viewRangeInit()
|
|
||||||
self.invalidOldSizeExpected = False
|
|
||||||
# Slider will update independently, making sure that the old
|
|
||||||
# slider positions are preserved. Because of this, we can be
|
|
||||||
# confident that the new slider position will still map to the
|
|
||||||
# same positions in the new axis-space.
|
|
||||||
return False
|
return False
|
||||||
|
|
Loading…
Reference in New Issue