Skip to content

Commit e4b854b

Browse files
committedMar 11, 2016
scanwidget: apply changes as of 98f0a56
1 parent bc92034 commit e4b854b

File tree

2 files changed

+101
-161
lines changed

2 files changed

+101
-161
lines changed
 

‎artiq/gui/entries.py

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def apply_properties(spinbox):
155155
spinbox.setSuffix(" " + procdesc["unit"])
156156

157157
self.scanner = scanner = ScanWidget()
158+
scanner.setMinimumSize(150, 0)
158159
scanner.setSizePolicy(QtWidgets.QSizePolicy(
159160
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed))
160161
disable_scroll_wheel(scanner.axis)

‎artiq/gui/scanwidget.py

+100-161
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from PyQt5 import QtGui, QtCore, QtWidgets
2-
from .ticker import Ticker
32
from numpy import linspace
43

4+
from .ticker import Ticker
5+
56

67
class ScanAxis(QtWidgets.QWidget):
78
sigZoom = QtCore.pyqtSignal(float, int)
@@ -10,7 +11,6 @@ class ScanAxis(QtWidgets.QWidget):
1011
def __init__(self, zoomFactor):
1112
QtWidgets.QWidget.__init__(self)
1213
self.proxy = None
13-
self.slider = None # Needed for eventFilter
1414
self.sizePolicy().setControlType(QtWidgets.QSizePolicy.ButtonBox)
1515
self.ticker = Ticker()
1616
self.zoomFactor = zoomFactor
@@ -26,26 +26,25 @@ def paintEvent(self, ev):
2626
painter.drawLine(0, 0, self.width(), 0)
2727
realLeft = self.proxy.pixelToReal(0)
2828
realRight = self.proxy.pixelToReal(self.width())
29-
3029
ticks, prefix, labels = self.ticker(realLeft, realRight)
30+
painter.drawText(0, -25, prefix)
31+
32+
pen = QtGui.QPen()
33+
pen.setWidth(2)
34+
painter.setPen(pen)
35+
3136
for t, l in zip(ticks, labels):
3237
t = self.proxy.realToPixel(t)
33-
textCenter = (len(l)/2.0)*avgCharWidth
34-
painter.drawLine(t, 5, t, -5)
35-
painter.drawText(t - textCenter, -10, l)
38+
painter.drawLine(t, 0, t, -5)
39+
painter.drawText(t - len(l)/2*avgCharWidth, -10, l)
3640

37-
painter.save()
38-
painter.setPen(QtGui.QColor(QtCore.Qt.green))
3941
sliderStartPixel = self.proxy.realToPixel(self.proxy.realStart)
4042
sliderStopPixel = self.proxy.realToPixel(self.proxy.realStop)
4143
pixels = linspace(sliderStartPixel, sliderStopPixel,
42-
self.proxy.numPoints)
44+
self.proxy.numPoints)
4345
for p in pixels:
4446
p_int = int(p)
4547
painter.drawLine(p_int, 0, p_int, 5)
46-
47-
painter.restore()
48-
painter.drawText(0, -25, prefix)
4948
ev.accept()
5049

5150
def wheelEvent(self, ev):
@@ -62,26 +61,24 @@ def wheelEvent(self, ev):
6261
# over a tick during a zoom, it should appear as if we are
6362
# doing zoom relative to the ticks which live in axis
6463
# pixel-space, not slider pixel-space.
65-
self.sigZoom.emit(z, ev.x() -
66-
self.proxy.slider.handleWidth()/2)
64+
self.sigZoom.emit(
65+
z, ev.x() - self.proxy.slider.handleWidth()/2)
6766
self.update()
6867
ev.accept()
6968

7069
def eventFilter(self, obj, ev):
71-
if obj != self.slider:
70+
if obj is not self.proxy.slider:
7271
return False
7372
if ev.type() != QtCore.QEvent.Wheel:
7473
return False
7574
self.wheelEvent(ev)
7675
return True
7776

77+
7878
# Basic ideas from https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
7979
class ScanSlider(QtWidgets.QSlider):
8080
sigStartMoved = QtCore.pyqtSignal(int)
8181
sigStopMoved = QtCore.pyqtSignal(int)
82-
noSlider, startSlider, stopSlider = range(3)
83-
stopStyle = "QSlider::handle {background:red}"
84-
startStyle = "QSlider::handle {background:blue}"
8582

8683
def __init__(self):
8784
QtWidgets.QSlider.__init__(self, QtCore.Qt.Horizontal)
@@ -92,8 +89,6 @@ def __init__(self):
9289
self.stopVal = 99 # upper
9390
self.offset = 0
9491
self.position = 0
95-
self.lastPressed = ScanSlider.noSlider
96-
self.selectedHandle = ScanSlider.startSlider
9792
self.upperPressed = QtWidgets.QStyle.SC_None
9893
self.lowerPressed = QtWidgets.QStyle.SC_None
9994
self.firstMovement = False # State var for handling slider overlap.
@@ -103,63 +98,40 @@ def __init__(self):
10398
# set the stylesheets for drawing each slider later. See paintEvent.
10499
self.dummyStartSlider = QtWidgets.QSlider()
105100
self.dummyStopSlider = QtWidgets.QSlider()
106-
self.dummyStartSlider.setStyleSheet(ScanSlider.startStyle)
107-
self.dummyStopSlider.setStyleSheet(ScanSlider.stopStyle)
101+
self.dummyStartSlider.setStyleSheet(
102+
"QSlider::handle {background:blue}")
103+
self.dummyStopSlider.setStyleSheet(
104+
"QSlider::handle {background:red}")
108105

109106
# We basically superimpose two QSliders on top of each other, discarding
110107
# the state that remains constant between the two when drawing.
111108
# Everything except the handles remain constant.
112109
def initHandleStyleOption(self, opt, handle):
113110
self.initStyleOption(opt)
114-
if handle == ScanSlider.startSlider:
111+
if handle == "start":
115112
opt.sliderPosition = self.startPos
116113
opt.sliderValue = self.startVal
117-
elif handle == ScanSlider.stopSlider:
114+
elif handle == "stop":
118115
opt.sliderPosition = self.stopPos
119116
opt.sliderValue = self.stopVal
120-
else:
121-
pass # AssertionErrors
122117

123118
# We get the range of each slider separately.
124119
def pixelPosToRangeValue(self, pos):
125120
opt = QtWidgets.QStyleOptionSlider()
126121
self.initStyleOption(opt)
127-
128122
gr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
129123
QtWidgets.QStyle.SC_SliderGroove,
130124
self)
131-
sr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
132-
QtWidgets.QStyle.SC_SliderHandle,
133-
self)
134-
135-
sliderLength = sr.width()
136-
sliderStart = gr.x()
137-
# For historical reasons right() returns left()+width() - 1
138-
# x() is equivalent to left().
139-
sliderStop = gr.right() - sliderLength + 1
140-
141125
rangeVal = QtWidgets.QStyle.sliderValueFromPosition(
142-
self.minimum(), self.maximum(), pos - sliderStart,
143-
sliderStop - sliderStart, opt.upsideDown)
126+
self.minimum(), self.maximum(), pos - gr.x(),
127+
self.effectiveWidth(), opt.upsideDown)
144128
return rangeVal
145129

146130
def rangeValueToPixelPos(self, val):
147131
opt = QtWidgets.QStyleOptionSlider()
148132
self.initStyleOption(opt)
149-
150-
gr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
151-
QtWidgets.QStyle.SC_SliderGroove,
152-
self)
153-
sr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
154-
QtWidgets.QStyle.SC_SliderHandle,
155-
self)
156-
157-
sliderLength = sr.width()
158-
sliderStart = gr.x()
159-
sliderStop = gr.right() - sliderLength + 1
160-
161133
pixel = QtWidgets.QStyle.sliderPositionFromValue(
162-
self.minimum(), self.maximum(), val, sliderStop - sliderStart,
134+
self.minimum(), self.maximum(), val, self.effectiveWidth(),
163135
opt.upsideDown)
164136
return pixel
165137

@@ -180,28 +152,15 @@ def effectiveWidth(self):
180152
gr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
181153
QtWidgets.QStyle.SC_SliderGroove,
182154
self)
183-
sliderLength = self.handleWidth()
184-
sliderStart = gr.x()
185-
sliderStop = gr.right() - sliderLength + 1
186-
return sliderStop - sliderStart
187-
188-
# If groove and axis are not aligned (and they should be), we can use
189-
# this function to calculate the offset between them.
190-
def grooveX(self):
191-
opt = QtWidgets.QStyleOptionSlider()
192-
self.initStyleOption(opt)
193-
gr = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt,
194-
QtWidgets.QStyle.SC_SliderGroove,
195-
self)
196-
return gr.x()
155+
return gr.width() - self.handleWidth()
197156

198157
def handleMousePress(self, pos, control, val, handle):
199158
opt = QtWidgets.QStyleOptionSlider()
200159
self.initHandleStyleOption(opt, handle)
201-
startAtEdges = (handle == ScanSlider.startSlider and
160+
startAtEdges = (handle == "start" and
202161
(self.startVal == self.minimum() or
203162
self.startVal == self.maximum()))
204-
stopAtEdges = (handle == ScanSlider.stopSlider and
163+
stopAtEdges = (handle == "stop" and
205164
(self.stopVal == self.minimum() or
206165
self.stopVal == self.maximum()))
207166

@@ -218,9 +177,7 @@ def handleMousePress(self, pos, control, val, handle):
218177
if control == QtWidgets.QStyle.SC_SliderHandle:
219178
# no pick()- slider orientation static
220179
self.offset = pos.x() - sr.topLeft().x()
221-
self.lastPressed = handle
222180
self.setSliderDown(True)
223-
self.selectedHandle = handle
224181
# emit
225182

226183
# Needed?
@@ -239,25 +196,11 @@ def drawHandle(self, painter, handle):
239196
# if action == QtWidgets.QAbstractSlider.SliderSingleStepAdd:
240197
# if
241198

242-
def setStartValue(self, val):
243-
self.setSpan(val, self.stopVal)
244-
245-
def setStopValue(self, val):
246-
self.setSpan(self.startVal, val)
247-
248-
def setSpan(self, lower, upper):
249-
# TODO: Is bound() necessary? QStyle::sliderPositionFromValue appears
199+
def setSpan(self, low, high):
200+
# TODO: Is this necessary? QStyle::sliderPositionFromValue appears
250201
# to clamp already.
251-
def bound(min, curr, max):
252-
if curr < min:
253-
return min
254-
elif curr > max:
255-
return max
256-
else:
257-
return curr
258-
259-
low = bound(self.minimum(), lower, self.maximum())
260-
high = bound(self.minimum(), upper, self.maximum())
202+
low = min(max(self.minimum(), low), self.maximum())
203+
high = min(max(self.minimum(), high), self.maximum())
261204

262205
if low != self.startVal or high != self.stopVal:
263206
if low != self.startVal:
@@ -276,7 +219,7 @@ def setStartPosition(self, val):
276219
if self.isSliderDown():
277220
self.sigStartMoved.emit(self.startPos)
278221
if self.hasTracking() and not self.blockTracking:
279-
self.setStartValue(val)
222+
self.setSpan(self.startPos, self.stopVal)
280223

281224
def setStopPosition(self, val):
282225
if val != self.stopPos:
@@ -286,7 +229,7 @@ def setStopPosition(self, val):
286229
if self.isSliderDown():
287230
self.sigStopMoved.emit(self.stopPos)
288231
if self.hasTracking() and not self.blockTracking:
289-
self.setStopValue(val)
232+
self.setSpan(self.startVal, self.stopPos)
290233

291234
def mousePressEvent(self, ev):
292235
if self.minimum() == self.maximum() or (ev.buttons() ^ ev.button()):
@@ -295,11 +238,10 @@ def mousePressEvent(self, ev):
295238

296239
# Prefer stopVal in the default case.
297240
self.upperPressed = self.handleMousePress(
298-
ev.pos(), self.upperPressed, self.stopVal, ScanSlider.stopSlider)
241+
ev.pos(), self.upperPressed, self.stopVal, "stop")
299242
if self.upperPressed != QtWidgets.QStyle.SC_SliderHandle:
300243
self.lowerPressed = self.handleMousePress(
301-
ev.pos(), self.upperPressed, self.startVal,
302-
ScanSlider.startSlider)
244+
ev.pos(), self.upperPressed, self.startVal, "start")
303245

304246
# State that is needed to handle the case where two sliders are equal.
305247
self.firstMovement = True
@@ -350,7 +292,6 @@ def mouseReleaseEvent(self, ev):
350292

351293
def paintEvent(self, ev):
352294
# Use QStylePainters to make redrawing as painless as possible.
353-
painter = QtWidgets.QStylePainter(self)
354295
# Paint on the custom widget, using the attributes of the fake
355296
# slider references we keep around. setStyleSheet within paintEvent
356297
# leads to heavy performance penalties (and recursion?).
@@ -361,22 +302,14 @@ def paintEvent(self, ev):
361302
startPainter = QtWidgets.QStylePainter(self, self.dummyStartSlider)
362303
stopPainter = QtWidgets.QStylePainter(self, self.dummyStopSlider)
363304

364-
# Groove
365-
opt = QtWidgets.QStyleOptionSlider()
366-
self.initStyleOption(opt)
367-
opt.sliderValue = 0
368-
opt.sliderPosition = 0
369-
opt.subControls = QtWidgets.QStyle.SC_SliderGroove
370-
painter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt)
371-
372305
# Handles
373306
# Qt will snap sliders to 0 or maximum() if given a desired pixel
374307
# location outside the mapped range. So we manually just don't draw
375308
# the handles if they are at 0 or max.
376309
if self.startVal > 0 and self.startVal < self.maximum():
377-
self.drawHandle(startPainter, ScanSlider.startSlider)
310+
self.drawHandle(startPainter, "start")
378311
if self.stopVal > 0 and self.stopVal < self.maximum():
379-
self.drawHandle(stopPainter, ScanSlider.stopSlider)
312+
self.drawHandle(stopPainter, "stop")
380313

381314

382315
# real (Sliders) => pixel (one pixel movement of sliders would increment by X)
@@ -386,57 +319,39 @@ class ScanProxy(QtCore.QObject):
386319
sigStopMoved = QtCore.pyqtSignal(float)
387320
sigNumPoints = QtCore.pyqtSignal(int)
388321

389-
def __init__(self, slider, axis, rangeFactor):
322+
def __init__(self, slider, axis, rangeFactor, dynamicRange):
390323
QtCore.QObject.__init__(self)
391324
self.axis = axis
392325
self.slider = slider
393326
self.realStart = 0
394327
self.realStop = 0
395328
self.numPoints = 10
396329
self.rangeFactor = rangeFactor
330+
self.dynamicRange = dynamicRange
397331

398332
# Transform that maps the spinboxes to a pixel position on the
399333
# axis. 0 to axis.width() exclusive indicate positions which will be
400334
# displayed on the axis.
401335
# Because the axis's width will change when placed within a layout,
402336
# the realToPixelTransform will initially be invalid. It will be set
403337
# properly during the first resizeEvent, with the below transform.
404-
self.realToPixelTransform = self.calculateNewRealToPixel(
405-
-self.axis.width()/2, 1.0)
338+
self.realToPixelTransform = self.axis.width()/2, 1.
406339
self.invalidOldSizeExpected = True
407340

408-
# What real value should map to the axis/slider left? This doesn't depend
409-
# on any public members so we can make decisions about centering during
410-
# resize and zoom events.
411-
def calculateNewRealToPixel(self, targetLeft, targetScale):
412-
return QtGui.QTransform.fromScale(targetScale, 1).translate(
413-
-targetLeft, 0)
414-
415341
# pixel vals for sliders: 0 to slider_width - 1
416342
def realToPixel(self, val):
417-
rawVal = (QtCore.QPointF(val, 0) * self.realToPixelTransform).x()
343+
a, b = self.realToPixelTransform
344+
rawVal = b*(val + a)
418345
# Clamp pixel values to 32 bits, b/c Qt will otherwise wrap values.
419-
if rawVal < -(2**31):
420-
rawVal = -(2**31)
421-
elif rawVal > (2**31 - 1):
422-
rawVal = (2**31 - 1)
346+
rawVal = min(max(-(1 << 31), rawVal), (1 << 31) - 1)
423347
return rawVal
424348

425349
# Get a point from pixel units to what the sliders display.
426350
def pixelToReal(self, val):
427-
(revXform, invertible) = self.realToPixelTransform.inverted()
428-
if not invertible:
429-
revXform = (QtGui.QTransform.fromTranslate(
430-
-self.realToPixelTransform.dx(), 0) *
431-
QtGui.QTransform.fromScale(
432-
1/self.realToPixelTransform.m11(), 0))
433-
realPoint = QtCore.QPointF(val, 0) * revXform
434-
return realPoint.x()
351+
a, b = self.realToPixelTransform
352+
return val/b - a
435353

436354
def rangeToReal(self, val):
437-
# gx = self.slider.grooveX()
438-
# ax = self.axis.x()
439-
# assert gx == ax, "gx: {}, ax: {}".format(gx, ax)
440355
pixelVal = self.slider.rangeValueToPixelPos(val)
441356
return self.pixelToReal(pixelVal)
442357

@@ -470,11 +385,12 @@ def setNumPoints(self, val):
470385
self.axis.update()
471386

472387
def handleZoom(self, zoomFactor, mouseXPos):
473-
newScale = self.realToPixelTransform.m11() * zoomFactor
388+
newScale = self.realToPixelTransform[1] * zoomFactor
474389
refReal = self.pixelToReal(mouseXPos)
475-
newLeft = refReal - mouseXPos/newScale
476-
self.realToPixelTransform = self.calculateNewRealToPixel(
477-
newLeft, newScale)
390+
newLeft = mouseXPos/newScale - refReal
391+
if abs(newLeft*newScale) > self.dynamicRange:
392+
return
393+
self.realToPixelTransform = newLeft, newScale
478394
self.moveStop(self.realStop)
479395
self.moveStart(self.realStart)
480396

@@ -485,21 +401,41 @@ def zoomToFit(self):
485401
refSlider = self.realStop
486402
else:
487403
refSlider = self.realStart
488-
if self.rangeFactor <= 2:
489-
return # Ill-formed snap range- do nothing.
404+
if self.rangeFactor <= 2 or currRangeReal == 0:
405+
return # Ill-formed snap range- do nothing
490406
proportion = self.rangeFactor/(self.rangeFactor - 2)
491407
newScale = self.slider.effectiveWidth()/(proportion*currRangeReal)
492-
newLeft = refSlider - self.slider.effectiveWidth() \
493-
/ (self.rangeFactor*newScale)
494-
self.realToPixelTransform = self.calculateNewRealToPixel(
495-
newLeft, newScale)
496-
self.printTransform()
408+
newLeft = (self.slider.effectiveWidth()/(self.rangeFactor*newScale) -
409+
refSlider)
410+
self.realToPixelTransform = newLeft, newScale
497411
self.moveStop(self.realStop)
498412
self.moveStart(self.realStart)
499413
self.axis.update() # Axis normally takes care to update itself during
500414
# zoom. In this code path however, the zoom didn't arrive via the axis
501415
# widget, so we need to notify manually.
502416

417+
# This function is called if the axis width, slider width, and slider
418+
# positions are in an inconsistent state, to initialize the widget.
419+
# This function handles handles the slider positions. Slider and axis
420+
# handle its own width changes; proxy watches for axis width resizeEvent to
421+
# alter mapping from real to pixel space.
422+
def zoomToFitInit(self):
423+
currRangeReal = abs(self.realStop - self.realStart)
424+
if self.rangeFactor <= 2 or currRangeReal == 0:
425+
self.moveStop(self.realStop)
426+
self.moveStart(self.realStart)
427+
# Ill-formed snap range- move the sliders anyway,
428+
# because we arrived here during widget
429+
# initialization, where the slider positions are likely invalid.
430+
# This will force the sliders to have positions on the axis
431+
# which reflect the start/stop values currently set.
432+
else:
433+
self.zoomToFit()
434+
# Notify spinboxes manually, since slider wasn't clicked and will
435+
# therefore not emit signals.
436+
self.sigStopMoved.emit(self.realStop)
437+
self.sigStartMoved.emit(self.realStart)
438+
503439
def fitToView(self):
504440
lowRange = 1.0/self.rangeFactor
505441
highRange = (self.rangeFactor - 1)/self.rangeFactor
@@ -527,15 +463,22 @@ def eventFilter(self, obj, ev):
527463
newWidth = ev.size().width() - self.slider.handleWidth()
528464
# assert refRight > oldLeft
529465
newScale = newWidth/(refRight - oldLeft)
466+
self.realToPixelTransform = -oldLeft, newScale
530467
else:
531468
# TODO: self.axis.width() is invalid during object
532469
# construction. The width will change when placed in a
533470
# layout WITHOUT a resizeEvent. Why?
534471
oldLeft = -ev.size().width()/2
535472
newScale = 1.0
473+
self.realToPixelTransform = -oldLeft, newScale
474+
# We need to reinitialize the pixel transform b/c the old width
475+
# of the axis is no longer valid. When we have a valid transform,
476+
# we can then zoomToFit based on the desired real values.
477+
# The slider handle values are invalid before this point as well;
478+
# we set them to the correct value here, regardless of whether
479+
# the slider has already resized itsef or not.
480+
self.zoomToFitInit()
536481
self.invalidOldSizeExpected = False
537-
self.realToPixelTransform = self.calculateNewRealToPixel(
538-
oldLeft, newScale)
539482
# assert self.pixelToReal(0) == oldLeft, \
540483
# "{}, {}".format(self.pixelToReal(0), oldLeft)
541484
# Slider will update independently, making sure that the old
@@ -544,26 +487,17 @@ def eventFilter(self, obj, ev):
544487
# same positions in the new axis-space.
545488
return False
546489

547-
def printTransform(self):
548-
print("m11: {}, dx: {}".format(
549-
self.realToPixelTransform.m11(), self.realToPixelTransform.dx()))
550-
(inverted, invertible) = self.realToPixelTransform.inverted()
551-
print("m11: {}, dx: {}, singular: {}".format(
552-
inverted.m11(), inverted.dx(), not invertible))
553-
554490

555491
class ScanWidget(QtWidgets.QWidget):
556492
sigStartMoved = QtCore.pyqtSignal(float)
557493
sigStopMoved = QtCore.pyqtSignal(float)
558494
sigNumChanged = QtCore.pyqtSignal(int)
559495

560-
def __init__(self, zoomFactor=1.05, rangeFactor=6):
496+
def __init__(self, zoomFactor=1.05, rangeFactor=6, dynamicRange=1e8):
561497
QtWidgets.QWidget.__init__(self)
562498
self.slider = slider = ScanSlider()
563499
self.axis = axis = ScanAxis(zoomFactor)
564-
zoomFitButton = QtWidgets.QPushButton("View Range")
565-
fitViewButton = QtWidgets.QPushButton("Snap Range")
566-
self.proxy = ScanProxy(slider, axis, rangeFactor)
500+
self.proxy = ScanProxy(slider, axis, rangeFactor, dynamicRange)
567501
axis.proxy = self.proxy
568502
axis.slider = slider
569503
slider.setMaximum(1023)
@@ -574,25 +508,27 @@ def __init__(self, zoomFactor=1.05, rangeFactor=6):
574508
layout.setRowMinimumHeight(0, 40)
575509
layout.addWidget(axis, 0, 0, 1, -1)
576510
layout.addWidget(slider, 1, 0, 1, -1)
577-
layout.addWidget(zoomFitButton, 2, 0)
578-
layout.addWidget(fitViewButton, 2, 1)
579511
self.setLayout(layout)
580512

581-
# Connect signals
513+
# Connect signals (minus context menu)
582514
slider.sigStopMoved.connect(self.proxy.handleStopMoved)
583515
slider.sigStartMoved.connect(self.proxy.handleStartMoved)
584516
self.proxy.sigStopMoved.connect(self.sigStopMoved)
585517
self.proxy.sigStartMoved.connect(self.sigStartMoved)
586518
self.proxy.sigNumPoints.connect(self.sigNumChanged)
587519
axis.sigZoom.connect(self.proxy.handleZoom)
588520
axis.sigPoints.connect(self.proxy.handleNumPoints)
589-
fitViewButton.clicked.connect(self.fitToView)
590-
zoomFitButton.clicked.connect(self.zoomToFit)
591521

592522
# Connect event observers.
593523
axis.installEventFilter(self.proxy)
594524
slider.installEventFilter(axis)
595525

526+
# Context menu entries
527+
self.zoomToFitAct = QtWidgets.QAction("&View Range", self)
528+
self.fitToViewAct = QtWidgets.QAction("&Snap Range", self)
529+
self.zoomToFitAct.triggered.connect(self.zoomToFit)
530+
self.fitToViewAct.triggered.connect(self.fitToView)
531+
596532
# Spinbox and button slots. Any time the spinboxes change, ScanWidget
597533
# mirrors it and passes the information to the proxy.
598534
def setStop(self, val):
@@ -610,5 +546,8 @@ def zoomToFit(self):
610546
def fitToView(self):
611547
self.proxy.fitToView()
612548

613-
def reset(self):
614-
self.proxy.reset()
549+
def contextMenuEvent(self, ev):
550+
menu = QtWidgets.QMenu(self)
551+
menu.addAction(self.zoomToFitAct)
552+
menu.addAction(self.fitToViewAct)
553+
menu.exec(ev.globalPos())

0 commit comments

Comments
 (0)
Please sign in to comment.