10
10
11
11
12
12
class ScanAxis (QtWidgets .QWidget ):
13
- sigZoom = QtCore .pyqtSignal (float , int )
14
- sigPoints = QtCore .pyqtSignal (int )
15
-
16
- def __init__ (self , zoomFactor ):
13
+ def __init__ (self ):
17
14
QtWidgets .QWidget .__init__ (self )
18
15
self .proxy = None
19
16
self .sizePolicy ().setControlType (QtWidgets .QSizePolicy .ButtonBox )
20
17
self .ticker = Ticker ()
21
- self .zoomFactor = zoomFactor
18
+ self .setMinimumHeight ( 40 )
22
19
23
20
def paintEvent (self , ev ):
24
21
painter = QtGui .QPainter (self )
@@ -52,37 +49,6 @@ def paintEvent(self, ev):
52
49
painter .drawLine (p_int , 0 , p_int , 5 )
53
50
ev .accept ()
54
51
55
- def wheelEvent (self , ev ):
56
- y = ev .angleDelta ().y ()
57
- if y :
58
- if ev .modifiers () & QtCore .Qt .ShiftModifier :
59
- # If shift+scroll, modify number of points.
60
- # TODO: This is not perfect. For high-resolution touchpads you
61
- # get many small events with y < 120 which should accumulate.
62
- # That would also match the wheel behavior of an integer
63
- # spinbox.
64
- z = int (y / 120. )
65
- self .sigPoints .emit (z )
66
- else :
67
- z = self .zoomFactor ** (y / 120. )
68
- # Remove the slider-handle shift correction, b/c none of the
69
- # other widgets know about it. If we have the mouse directly
70
- # over a tick during a zoom, it should appear as if we are
71
- # doing zoom relative to the ticks which live in axis
72
- # pixel-space, not slider pixel-space.
73
- self .sigZoom .emit (
74
- z , ev .x () - self .proxy .slider .handleWidth ()/ 2 )
75
- self .update ()
76
- ev .accept ()
77
-
78
- def eventFilter (self , obj , ev ):
79
- if obj is not self .proxy .slider :
80
- return False
81
- if ev .type () != QtCore .QEvent .Wheel :
82
- return False
83
- self .wheelEvent (ev )
84
- return True
85
-
86
52
87
53
# Basic ideas from https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
88
54
class ScanSlider (QtWidgets .QSlider ):
@@ -103,6 +69,9 @@ def __init__(self):
103
69
self .firstMovement = False # State var for handling slider overlap.
104
70
self .blockTracking = False
105
71
72
+ self .setMinimum (0 )
73
+ self .setMaximum (4095 )
74
+
106
75
# We need fake sliders to keep around so that we can dynamically
107
76
# set the stylesheets for drawing each slider later. See paintEvent.
108
77
self .dummyStartSlider = QtWidgets .QSlider ()
@@ -201,10 +170,6 @@ def drawHandle(self, painter, handle):
201
170
opt .subControls = QtWidgets .QStyle .SC_SliderHandle
202
171
painter .drawComplexControl (QtWidgets .QStyle .CC_Slider , opt )
203
172
204
- # def triggerAction(self, action, slider):
205
- # if action == QtWidgets.QAbstractSlider.SliderSingleStepAdd:
206
- # if
207
-
208
173
def setSpan (self , low , high ):
209
174
# TODO: Is this necessary? QStyle::sliderPositionFromValue appears
210
175
# to clamp already.
@@ -241,7 +206,7 @@ def setStopPosition(self, val):
241
206
self .setSpan (self .startVal , self .stopPos )
242
207
243
208
def mousePressEvent (self , ev ):
244
- if self . minimum () == self . maximum () or ( ev .buttons () ^ ev .button () ):
209
+ if ev .buttons () ^ ev .button ():
245
210
ev .ignore ()
246
211
return
247
212
@@ -315,9 +280,9 @@ def paintEvent(self, ev):
315
280
# Qt will snap sliders to 0 or maximum() if given a desired pixel
316
281
# location outside the mapped range. So we manually just don't draw
317
282
# the handles if they are at 0 or max.
318
- if self .startVal > 0 and self .startVal < self .maximum ():
283
+ if self .minimum () < self .startVal < self .maximum ():
319
284
self .drawHandle (startPainter , "start" )
320
- if self .stopVal > 0 and self .stopVal < self .maximum ():
285
+ if self .minimum () < self .stopVal < self .maximum ():
321
286
self .drawHandle (stopPainter , "stop" )
322
287
323
288
@@ -326,17 +291,19 @@ def paintEvent(self, ev):
326
291
class ScanProxy (QtCore .QObject ):
327
292
sigStartMoved = QtCore .pyqtSignal (float )
328
293
sigStopMoved = QtCore .pyqtSignal (float )
329
- sigNumPoints = QtCore .pyqtSignal (int )
294
+ sigNumChanged = QtCore .pyqtSignal (int )
330
295
331
- def __init__ (self , slider , axis , zoomMargin , dynamicRange ):
296
+ def __init__ (self , slider , axis , zoomMargin , dynamicRange , zoomFactor ):
332
297
QtCore .QObject .__init__ (self )
333
298
self .axis = axis
299
+ axis .proxy = self
334
300
self .slider = slider
335
- self .realStart = 0
336
- self .realStop = 0
337
- self .numPoints = 10
301
+ self .realStart = - 1.
302
+ self .realStop = 1.
303
+ self .numPoints = 11
338
304
self .zoomMargin = zoomMargin
339
305
self .dynamicRange = dynamicRange
306
+ self .zoomFactor = zoomFactor
340
307
341
308
# Transform that maps the spinboxes to a pixel position on the
342
309
# axis. 0 to axis.width() exclusive indicate positions which will be
@@ -347,6 +314,12 @@ def __init__(self, slider, axis, zoomMargin, dynamicRange):
347
314
self .realToPixelTransform = - self .axis .width ()/ 2 , 1.
348
315
self .invalidOldSizeExpected = True
349
316
317
+ # Connect event observers.
318
+ axis .installEventFilter (self )
319
+ slider .installEventFilter (self )
320
+ slider .sigStopMoved .connect (self .handleStopMoved )
321
+ slider .sigStartMoved .connect (self .handleStartMoved )
322
+
350
323
# pixel vals for sliders: 0 to slider_width - 1
351
324
def realToPixel (self , val ):
352
325
a , b = self .realToPixelTransform
@@ -381,14 +354,15 @@ def moveStart(self, val):
381
354
self .axis .update ()
382
355
383
356
def handleStopMoved (self , rangeVal ):
357
+ # FIXME: this relies on the event being fed back and ending up calling
358
+ # moveStop()
384
359
self .sigStopMoved .emit (self .rangeToReal (rangeVal ))
385
360
386
361
def handleStartMoved (self , rangeVal ):
362
+ # FIXME: this relies on the event being fed back and ending up calling
363
+ # moveStart()
387
364
self .sigStartMoved .emit (self .rangeToReal (rangeVal ))
388
365
389
- def handleNumPoints (self , inc ):
390
- self .sigNumPoints .emit (self .numPoints + inc )
391
-
392
366
def setNumPoints (self , val ):
393
367
self .numPoints = val
394
368
self .axis .update ()
@@ -446,28 +420,55 @@ def snapRange(self):
446
420
highRange = 1 - self .zoomMargin
447
421
newStart = self .pixelToReal (lowRange * self .slider .effectiveWidth ())
448
422
newStop = self .pixelToReal (highRange * self .slider .effectiveWidth ())
449
- sliderRange = self .slider .maximum () - self .slider .minimum ()
450
423
# Signals won't fire unless slider was actually grabbed, so
451
424
# manually update so the spinboxes know that knew values were set.
452
425
# self.realStop/Start and the sliders themselves will be updated as a
453
426
# consequence of ValueChanged signal in spinboxes. The slider widget
454
427
# has guards against recursive signals in setSpan().
455
- if sliderRange > 0 :
456
- self .sigStopMoved .emit (newStop )
457
- self .sigStartMoved .emit (newStart )
428
+ # FIXME: this relies on the events being fed back and ending up
429
+ # calling moveStart() and moveStop()
430
+ self .sigStopMoved .emit (newStop )
431
+ self .sigStartMoved .emit (newStart )
432
+
433
+ def wheelEvent (self , ev ):
434
+ y = ev .angleDelta ().y ()
435
+ if y :
436
+ if ev .modifiers () & QtCore .Qt .ShiftModifier :
437
+ # If shift+scroll, modify number of points.
438
+ # TODO: This is not perfect. For high-resolution touchpads you
439
+ # get many small events with y < 120 which should accumulate.
440
+ # That would also match the wheel behavior of an integer
441
+ # spinbox.
442
+ z = int (y / 120. )
443
+ # FIXME: this relies on the event being fed back and ending up
444
+ # calling setNumPoints()
445
+ self .sigNumChanged .emit (self .numPoints + z )
446
+ self .axis .update ()
447
+ else :
448
+ z = self .zoomFactor ** (y / 120. )
449
+ # Remove the slider-handle shift correction, b/c none of the
450
+ # other widgets know about it. If we have the mouse directly
451
+ # over a tick during a zoom, it should appear as if we are
452
+ # doing zoom relative to the ticks which live in axis
453
+ # pixel-space, not slider pixel-space.
454
+ self .handleZoom (z , ev .x () - self .slider .handleWidth ()/ 2 )
455
+ ev .accept ()
458
456
459
457
def eventFilter (self , obj , ev ):
460
- if obj != self .axis :
461
- return False
462
- if ev .type () != QtCore .QEvent .Resize :
458
+ if ev .type () == QtCore .QEvent .Wheel :
459
+ self .wheelEvent (ev )
460
+ return True
461
+ if not (obj is self .axis and ev .type () == QtCore .QEvent .Resize ):
463
462
return False
464
463
if ev .oldSize ().isValid ():
465
464
oldLeft = self .pixelToReal (0 )
466
465
refWidth = ev .oldSize ().width () - self .slider .handleWidth ()
467
466
refRight = self .pixelToReal (refWidth )
468
467
newWidth = ev .size ().width () - self .slider .handleWidth ()
469
- # assert refRight > oldLeft
470
468
newScale = newWidth / (refRight - oldLeft )
469
+ center = (self .realStop + self .realStart )/ 2
470
+ if center :
471
+ newScale = min (newScale , self .dynamicRange / abs (center ))
471
472
self .realToPixelTransform = oldLeft , newScale
472
473
else :
473
474
# TODO: self.axis.width() is invalid during object
@@ -484,8 +485,6 @@ def eventFilter(self, obj, ev):
484
485
# the slider has already resized itsef or not.
485
486
self .viewRangeInit ()
486
487
self .invalidOldSizeExpected = False
487
- # assert self.pixelToReal(0) == oldLeft, \
488
- # "{}, {}".format(self.pixelToReal(0), oldLeft)
489
488
# Slider will update independently, making sure that the old
490
489
# slider positions are preserved. Because of this, we can be
491
490
# confident that the new slider position will still map to the
@@ -498,43 +497,34 @@ class ScanWidget(QtWidgets.QWidget):
498
497
sigStopMoved = QtCore .pyqtSignal (float )
499
498
sigNumChanged = QtCore .pyqtSignal (int )
500
499
501
- def __init__ (self , zoomFactor = 1.05 , zoomMargin = .1 , dynamicRange = 1e8 ):
500
+ def __init__ (self , zoomFactor = 1.05 , zoomMargin = .1 , dynamicRange = 1e9 ):
502
501
QtWidgets .QWidget .__init__ (self )
503
502
self .slider = slider = ScanSlider ()
504
- self .axis = axis = ScanAxis (zoomFactor )
505
- self .proxy = ScanProxy (slider , axis , zoomMargin , dynamicRange )
506
- axis .proxy = self .proxy
507
- slider .setMaximum (1023 )
503
+ self .axis = axis = ScanAxis ()
504
+ self .proxy = ScanProxy (slider , axis , zoomMargin , dynamicRange ,
505
+ zoomFactor )
508
506
509
507
# Layout.
510
- layout = QtWidgets .QGridLayout ()
511
- # Default size will cause axis to disappear otherwise.
512
- layout .setRowMinimumHeight (0 , 40 )
513
- layout .addWidget (axis , 0 , 0 , 1 , - 1 )
514
- layout .addWidget (slider , 1 , 0 , 1 , - 1 )
508
+ layout = QtWidgets .QVBoxLayout ()
509
+ layout .setSpacing (0 )
510
+ layout .addWidget (axis )
511
+ layout .addWidget (slider )
515
512
self .setLayout (layout )
516
513
517
514
# Connect signals (minus context menu)
518
- slider .sigStopMoved .connect (self .proxy .handleStopMoved )
519
- slider .sigStartMoved .connect (self .proxy .handleStartMoved )
520
515
self .proxy .sigStopMoved .connect (self .sigStopMoved )
521
516
self .proxy .sigStartMoved .connect (self .sigStartMoved )
522
- self .proxy .sigNumPoints .connect (self .sigNumChanged )
523
- axis .sigZoom .connect (self .proxy .handleZoom )
524
- axis .sigPoints .connect (self .proxy .handleNumPoints )
525
-
526
- # Connect event observers.
527
- axis .installEventFilter (self .proxy )
528
- slider .installEventFilter (axis )
517
+ self .proxy .sigNumChanged .connect (self .sigNumChanged )
529
518
530
519
# Context menu entries
531
- self .viewRangeAct = QtWidgets .QAction ("&View Range" , self )
532
- self .snapRangeAct = QtWidgets .QAction ("&Snap Range" , self )
533
- self .viewRangeAct .triggered .connect (self .viewRange )
534
- self .snapRangeAct .triggered .connect (self .snapRange )
520
+ self .menu = QtWidgets .QMenu (self )
521
+ viewRangeAct = QtWidgets .QAction ("&View Range" , self )
522
+ viewRangeAct .triggered .connect (self .viewRange )
523
+ self .menu .addAction (viewRangeAct )
524
+ snapRangeAct = QtWidgets .QAction ("&Snap Range" , self )
525
+ snapRangeAct .triggered .connect (self .snapRange )
526
+ self .menu .addAction (snapRangeAct )
535
527
536
- # Spinbox and button slots. Any time the spinboxes change, ScanWidget
537
- # mirrors it and passes the information to the proxy.
538
528
def setStop (self , val ):
539
529
self .proxy .moveStop (val )
540
530
@@ -551,7 +541,4 @@ def snapRange(self):
551
541
self .proxy .snapRange ()
552
542
553
543
def contextMenuEvent (self , ev ):
554
- menu = QtWidgets .QMenu (self )
555
- menu .addAction (self .viewRangeAct )
556
- menu .addAction (self .snapRangeAct )
557
- menu .exec (ev .globalPos ())
544
+ self .menu .popup (ev .globalPos ())
0 commit comments