1
1
from PyQt5 import QtGui , QtCore , QtWidgets
2
- from .ticker import Ticker
3
2
from numpy import linspace
4
3
4
+ from .ticker import Ticker
5
+
5
6
6
7
class ScanAxis (QtWidgets .QWidget ):
7
8
sigZoom = QtCore .pyqtSignal (float , int )
@@ -10,7 +11,6 @@ class ScanAxis(QtWidgets.QWidget):
10
11
def __init__ (self , zoomFactor ):
11
12
QtWidgets .QWidget .__init__ (self )
12
13
self .proxy = None
13
- self .slider = None # Needed for eventFilter
14
14
self .sizePolicy ().setControlType (QtWidgets .QSizePolicy .ButtonBox )
15
15
self .ticker = Ticker ()
16
16
self .zoomFactor = zoomFactor
@@ -26,26 +26,25 @@ def paintEvent(self, ev):
26
26
painter .drawLine (0 , 0 , self .width (), 0 )
27
27
realLeft = self .proxy .pixelToReal (0 )
28
28
realRight = self .proxy .pixelToReal (self .width ())
29
-
30
29
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
+
31
36
for t , l in zip (ticks , labels ):
32
37
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 )
36
40
37
- painter .save ()
38
- painter .setPen (QtGui .QColor (QtCore .Qt .green ))
39
41
sliderStartPixel = self .proxy .realToPixel (self .proxy .realStart )
40
42
sliderStopPixel = self .proxy .realToPixel (self .proxy .realStop )
41
43
pixels = linspace (sliderStartPixel , sliderStopPixel ,
42
- self .proxy .numPoints )
44
+ self .proxy .numPoints )
43
45
for p in pixels :
44
46
p_int = int (p )
45
47
painter .drawLine (p_int , 0 , p_int , 5 )
46
-
47
- painter .restore ()
48
- painter .drawText (0 , - 25 , prefix )
49
48
ev .accept ()
50
49
51
50
def wheelEvent (self , ev ):
@@ -62,26 +61,24 @@ def wheelEvent(self, ev):
62
61
# over a tick during a zoom, it should appear as if we are
63
62
# doing zoom relative to the ticks which live in axis
64
63
# 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 )
67
66
self .update ()
68
67
ev .accept ()
69
68
70
69
def eventFilter (self , obj , ev ):
71
- if obj != self .slider :
70
+ if obj is not self . proxy .slider :
72
71
return False
73
72
if ev .type () != QtCore .QEvent .Wheel :
74
73
return False
75
74
self .wheelEvent (ev )
76
75
return True
77
76
77
+
78
78
# Basic ideas from https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
79
79
class ScanSlider (QtWidgets .QSlider ):
80
80
sigStartMoved = QtCore .pyqtSignal (int )
81
81
sigStopMoved = QtCore .pyqtSignal (int )
82
- noSlider , startSlider , stopSlider = range (3 )
83
- stopStyle = "QSlider::handle {background:red}"
84
- startStyle = "QSlider::handle {background:blue}"
85
82
86
83
def __init__ (self ):
87
84
QtWidgets .QSlider .__init__ (self , QtCore .Qt .Horizontal )
@@ -92,8 +89,6 @@ def __init__(self):
92
89
self .stopVal = 99 # upper
93
90
self .offset = 0
94
91
self .position = 0
95
- self .lastPressed = ScanSlider .noSlider
96
- self .selectedHandle = ScanSlider .startSlider
97
92
self .upperPressed = QtWidgets .QStyle .SC_None
98
93
self .lowerPressed = QtWidgets .QStyle .SC_None
99
94
self .firstMovement = False # State var for handling slider overlap.
@@ -103,63 +98,40 @@ def __init__(self):
103
98
# set the stylesheets for drawing each slider later. See paintEvent.
104
99
self .dummyStartSlider = QtWidgets .QSlider ()
105
100
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}" )
108
105
109
106
# We basically superimpose two QSliders on top of each other, discarding
110
107
# the state that remains constant between the two when drawing.
111
108
# Everything except the handles remain constant.
112
109
def initHandleStyleOption (self , opt , handle ):
113
110
self .initStyleOption (opt )
114
- if handle == ScanSlider . startSlider :
111
+ if handle == "start" :
115
112
opt .sliderPosition = self .startPos
116
113
opt .sliderValue = self .startVal
117
- elif handle == ScanSlider . stopSlider :
114
+ elif handle == "stop" :
118
115
opt .sliderPosition = self .stopPos
119
116
opt .sliderValue = self .stopVal
120
- else :
121
- pass # AssertionErrors
122
117
123
118
# We get the range of each slider separately.
124
119
def pixelPosToRangeValue (self , pos ):
125
120
opt = QtWidgets .QStyleOptionSlider ()
126
121
self .initStyleOption (opt )
127
-
128
122
gr = self .style ().subControlRect (QtWidgets .QStyle .CC_Slider , opt ,
129
123
QtWidgets .QStyle .SC_SliderGroove ,
130
124
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
-
141
125
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 )
144
128
return rangeVal
145
129
146
130
def rangeValueToPixelPos (self , val ):
147
131
opt = QtWidgets .QStyleOptionSlider ()
148
132
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
-
161
133
pixel = QtWidgets .QStyle .sliderPositionFromValue (
162
- self .minimum (), self .maximum (), val , sliderStop - sliderStart ,
134
+ self .minimum (), self .maximum (), val , self . effectiveWidth () ,
163
135
opt .upsideDown )
164
136
return pixel
165
137
@@ -180,28 +152,15 @@ def effectiveWidth(self):
180
152
gr = self .style ().subControlRect (QtWidgets .QStyle .CC_Slider , opt ,
181
153
QtWidgets .QStyle .SC_SliderGroove ,
182
154
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 ()
197
156
198
157
def handleMousePress (self , pos , control , val , handle ):
199
158
opt = QtWidgets .QStyleOptionSlider ()
200
159
self .initHandleStyleOption (opt , handle )
201
- startAtEdges = (handle == ScanSlider . startSlider and
160
+ startAtEdges = (handle == "start" and
202
161
(self .startVal == self .minimum () or
203
162
self .startVal == self .maximum ()))
204
- stopAtEdges = (handle == ScanSlider . stopSlider and
163
+ stopAtEdges = (handle == "stop" and
205
164
(self .stopVal == self .minimum () or
206
165
self .stopVal == self .maximum ()))
207
166
@@ -218,9 +177,7 @@ def handleMousePress(self, pos, control, val, handle):
218
177
if control == QtWidgets .QStyle .SC_SliderHandle :
219
178
# no pick()- slider orientation static
220
179
self .offset = pos .x () - sr .topLeft ().x ()
221
- self .lastPressed = handle
222
180
self .setSliderDown (True )
223
- self .selectedHandle = handle
224
181
# emit
225
182
226
183
# Needed?
@@ -239,25 +196,11 @@ def drawHandle(self, painter, handle):
239
196
# if action == QtWidgets.QAbstractSlider.SliderSingleStepAdd:
240
197
# if
241
198
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
250
201
# 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 ())
261
204
262
205
if low != self .startVal or high != self .stopVal :
263
206
if low != self .startVal :
@@ -276,7 +219,7 @@ def setStartPosition(self, val):
276
219
if self .isSliderDown ():
277
220
self .sigStartMoved .emit (self .startPos )
278
221
if self .hasTracking () and not self .blockTracking :
279
- self .setStartValue ( val )
222
+ self .setSpan ( self . startPos , self . stopVal )
280
223
281
224
def setStopPosition (self , val ):
282
225
if val != self .stopPos :
@@ -286,7 +229,7 @@ def setStopPosition(self, val):
286
229
if self .isSliderDown ():
287
230
self .sigStopMoved .emit (self .stopPos )
288
231
if self .hasTracking () and not self .blockTracking :
289
- self .setStopValue ( val )
232
+ self .setSpan ( self . startVal , self . stopPos )
290
233
291
234
def mousePressEvent (self , ev ):
292
235
if self .minimum () == self .maximum () or (ev .buttons () ^ ev .button ()):
@@ -295,11 +238,10 @@ def mousePressEvent(self, ev):
295
238
296
239
# Prefer stopVal in the default case.
297
240
self .upperPressed = self .handleMousePress (
298
- ev .pos (), self .upperPressed , self .stopVal , ScanSlider . stopSlider )
241
+ ev .pos (), self .upperPressed , self .stopVal , "stop" )
299
242
if self .upperPressed != QtWidgets .QStyle .SC_SliderHandle :
300
243
self .lowerPressed = self .handleMousePress (
301
- ev .pos (), self .upperPressed , self .startVal ,
302
- ScanSlider .startSlider )
244
+ ev .pos (), self .upperPressed , self .startVal , "start" )
303
245
304
246
# State that is needed to handle the case where two sliders are equal.
305
247
self .firstMovement = True
@@ -350,7 +292,6 @@ def mouseReleaseEvent(self, ev):
350
292
351
293
def paintEvent (self , ev ):
352
294
# Use QStylePainters to make redrawing as painless as possible.
353
- painter = QtWidgets .QStylePainter (self )
354
295
# Paint on the custom widget, using the attributes of the fake
355
296
# slider references we keep around. setStyleSheet within paintEvent
356
297
# leads to heavy performance penalties (and recursion?).
@@ -361,22 +302,14 @@ def paintEvent(self, ev):
361
302
startPainter = QtWidgets .QStylePainter (self , self .dummyStartSlider )
362
303
stopPainter = QtWidgets .QStylePainter (self , self .dummyStopSlider )
363
304
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
-
372
305
# Handles
373
306
# Qt will snap sliders to 0 or maximum() if given a desired pixel
374
307
# location outside the mapped range. So we manually just don't draw
375
308
# the handles if they are at 0 or max.
376
309
if self .startVal > 0 and self .startVal < self .maximum ():
377
- self .drawHandle (startPainter , ScanSlider . startSlider )
310
+ self .drawHandle (startPainter , "start" )
378
311
if self .stopVal > 0 and self .stopVal < self .maximum ():
379
- self .drawHandle (stopPainter , ScanSlider . stopSlider )
312
+ self .drawHandle (stopPainter , "stop" )
380
313
381
314
382
315
# real (Sliders) => pixel (one pixel movement of sliders would increment by X)
@@ -386,57 +319,39 @@ class ScanProxy(QtCore.QObject):
386
319
sigStopMoved = QtCore .pyqtSignal (float )
387
320
sigNumPoints = QtCore .pyqtSignal (int )
388
321
389
- def __init__ (self , slider , axis , rangeFactor ):
322
+ def __init__ (self , slider , axis , rangeFactor , dynamicRange ):
390
323
QtCore .QObject .__init__ (self )
391
324
self .axis = axis
392
325
self .slider = slider
393
326
self .realStart = 0
394
327
self .realStop = 0
395
328
self .numPoints = 10
396
329
self .rangeFactor = rangeFactor
330
+ self .dynamicRange = dynamicRange
397
331
398
332
# Transform that maps the spinboxes to a pixel position on the
399
333
# axis. 0 to axis.width() exclusive indicate positions which will be
400
334
# displayed on the axis.
401
335
# Because the axis's width will change when placed within a layout,
402
336
# the realToPixelTransform will initially be invalid. It will be set
403
337
# 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.
406
339
self .invalidOldSizeExpected = True
407
340
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
-
415
341
# pixel vals for sliders: 0 to slider_width - 1
416
342
def realToPixel (self , val ):
417
- rawVal = (QtCore .QPointF (val , 0 ) * self .realToPixelTransform ).x ()
343
+ a , b = self .realToPixelTransform
344
+ rawVal = b * (val + a )
418
345
# 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 )
423
347
return rawVal
424
348
425
349
# Get a point from pixel units to what the sliders display.
426
350
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
435
353
436
354
def rangeToReal (self , val ):
437
- # gx = self.slider.grooveX()
438
- # ax = self.axis.x()
439
- # assert gx == ax, "gx: {}, ax: {}".format(gx, ax)
440
355
pixelVal = self .slider .rangeValueToPixelPos (val )
441
356
return self .pixelToReal (pixelVal )
442
357
@@ -470,11 +385,12 @@ def setNumPoints(self, val):
470
385
self .axis .update ()
471
386
472
387
def handleZoom (self , zoomFactor , mouseXPos ):
473
- newScale = self .realToPixelTransform . m11 () * zoomFactor
388
+ newScale = self .realToPixelTransform [ 1 ] * zoomFactor
474
389
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
478
394
self .moveStop (self .realStop )
479
395
self .moveStart (self .realStart )
480
396
@@ -485,21 +401,41 @@ def zoomToFit(self):
485
401
refSlider = self .realStop
486
402
else :
487
403
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
490
406
proportion = self .rangeFactor / (self .rangeFactor - 2 )
491
407
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
497
411
self .moveStop (self .realStop )
498
412
self .moveStart (self .realStart )
499
413
self .axis .update () # Axis normally takes care to update itself during
500
414
# zoom. In this code path however, the zoom didn't arrive via the axis
501
415
# widget, so we need to notify manually.
502
416
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
+
503
439
def fitToView (self ):
504
440
lowRange = 1.0 / self .rangeFactor
505
441
highRange = (self .rangeFactor - 1 )/ self .rangeFactor
@@ -527,15 +463,22 @@ def eventFilter(self, obj, ev):
527
463
newWidth = ev .size ().width () - self .slider .handleWidth ()
528
464
# assert refRight > oldLeft
529
465
newScale = newWidth / (refRight - oldLeft )
466
+ self .realToPixelTransform = - oldLeft , newScale
530
467
else :
531
468
# TODO: self.axis.width() is invalid during object
532
469
# construction. The width will change when placed in a
533
470
# layout WITHOUT a resizeEvent. Why?
534
471
oldLeft = - ev .size ().width ()/ 2
535
472
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 ()
536
481
self .invalidOldSizeExpected = False
537
- self .realToPixelTransform = self .calculateNewRealToPixel (
538
- oldLeft , newScale )
539
482
# assert self.pixelToReal(0) == oldLeft, \
540
483
# "{}, {}".format(self.pixelToReal(0), oldLeft)
541
484
# Slider will update independently, making sure that the old
@@ -544,26 +487,17 @@ def eventFilter(self, obj, ev):
544
487
# same positions in the new axis-space.
545
488
return False
546
489
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
-
554
490
555
491
class ScanWidget (QtWidgets .QWidget ):
556
492
sigStartMoved = QtCore .pyqtSignal (float )
557
493
sigStopMoved = QtCore .pyqtSignal (float )
558
494
sigNumChanged = QtCore .pyqtSignal (int )
559
495
560
- def __init__ (self , zoomFactor = 1.05 , rangeFactor = 6 ):
496
+ def __init__ (self , zoomFactor = 1.05 , rangeFactor = 6 , dynamicRange = 1e8 ):
561
497
QtWidgets .QWidget .__init__ (self )
562
498
self .slider = slider = ScanSlider ()
563
499
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 )
567
501
axis .proxy = self .proxy
568
502
axis .slider = slider
569
503
slider .setMaximum (1023 )
@@ -574,25 +508,27 @@ def __init__(self, zoomFactor=1.05, rangeFactor=6):
574
508
layout .setRowMinimumHeight (0 , 40 )
575
509
layout .addWidget (axis , 0 , 0 , 1 , - 1 )
576
510
layout .addWidget (slider , 1 , 0 , 1 , - 1 )
577
- layout .addWidget (zoomFitButton , 2 , 0 )
578
- layout .addWidget (fitViewButton , 2 , 1 )
579
511
self .setLayout (layout )
580
512
581
- # Connect signals
513
+ # Connect signals (minus context menu)
582
514
slider .sigStopMoved .connect (self .proxy .handleStopMoved )
583
515
slider .sigStartMoved .connect (self .proxy .handleStartMoved )
584
516
self .proxy .sigStopMoved .connect (self .sigStopMoved )
585
517
self .proxy .sigStartMoved .connect (self .sigStartMoved )
586
518
self .proxy .sigNumPoints .connect (self .sigNumChanged )
587
519
axis .sigZoom .connect (self .proxy .handleZoom )
588
520
axis .sigPoints .connect (self .proxy .handleNumPoints )
589
- fitViewButton .clicked .connect (self .fitToView )
590
- zoomFitButton .clicked .connect (self .zoomToFit )
591
521
592
522
# Connect event observers.
593
523
axis .installEventFilter (self .proxy )
594
524
slider .installEventFilter (axis )
595
525
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
+
596
532
# Spinbox and button slots. Any time the spinboxes change, ScanWidget
597
533
# mirrors it and passes the information to the proxy.
598
534
def setStop (self , val ):
@@ -610,5 +546,8 @@ def zoomToFit(self):
610
546
def fitToView (self ):
611
547
self .proxy .fitToView ()
612
548
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