Skip to content

Commit 78f2706

Browse files
committedMar 11, 2016
scanwidget: apply changes as of 579bf5e
1 parent d34d83f commit 78f2706

File tree

1 file changed

+77
-90
lines changed

1 file changed

+77
-90
lines changed
 

‎artiq/gui/scanwidget.py

+77-90
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,12 @@
1010

1111

1212
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):
1714
QtWidgets.QWidget.__init__(self)
1815
self.proxy = None
1916
self.sizePolicy().setControlType(QtWidgets.QSizePolicy.ButtonBox)
2017
self.ticker = Ticker()
21-
self.zoomFactor = zoomFactor
18+
self.setMinimumHeight(40)
2219

2320
def paintEvent(self, ev):
2421
painter = QtGui.QPainter(self)
@@ -52,37 +49,6 @@ def paintEvent(self, ev):
5249
painter.drawLine(p_int, 0, p_int, 5)
5350
ev.accept()
5451

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-
8652

8753
# Basic ideas from https://gist.github.com/Riateche/27e36977f7d5ea72cf4f
8854
class ScanSlider(QtWidgets.QSlider):
@@ -103,6 +69,9 @@ def __init__(self):
10369
self.firstMovement = False # State var for handling slider overlap.
10470
self.blockTracking = False
10571

72+
self.setMinimum(0)
73+
self.setMaximum(4095)
74+
10675
# We need fake sliders to keep around so that we can dynamically
10776
# set the stylesheets for drawing each slider later. See paintEvent.
10877
self.dummyStartSlider = QtWidgets.QSlider()
@@ -201,10 +170,6 @@ def drawHandle(self, painter, handle):
201170
opt.subControls = QtWidgets.QStyle.SC_SliderHandle
202171
painter.drawComplexControl(QtWidgets.QStyle.CC_Slider, opt)
203172

204-
# def triggerAction(self, action, slider):
205-
# if action == QtWidgets.QAbstractSlider.SliderSingleStepAdd:
206-
# if
207-
208173
def setSpan(self, low, high):
209174
# TODO: Is this necessary? QStyle::sliderPositionFromValue appears
210175
# to clamp already.
@@ -241,7 +206,7 @@ def setStopPosition(self, val):
241206
self.setSpan(self.startVal, self.stopPos)
242207

243208
def mousePressEvent(self, ev):
244-
if self.minimum() == self.maximum() or (ev.buttons() ^ ev.button()):
209+
if ev.buttons() ^ ev.button():
245210
ev.ignore()
246211
return
247212

@@ -315,9 +280,9 @@ def paintEvent(self, ev):
315280
# Qt will snap sliders to 0 or maximum() if given a desired pixel
316281
# location outside the mapped range. So we manually just don't draw
317282
# 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():
319284
self.drawHandle(startPainter, "start")
320-
if self.stopVal > 0 and self.stopVal < self.maximum():
285+
if self.minimum() < self.stopVal < self.maximum():
321286
self.drawHandle(stopPainter, "stop")
322287

323288

@@ -326,17 +291,19 @@ def paintEvent(self, ev):
326291
class ScanProxy(QtCore.QObject):
327292
sigStartMoved = QtCore.pyqtSignal(float)
328293
sigStopMoved = QtCore.pyqtSignal(float)
329-
sigNumPoints = QtCore.pyqtSignal(int)
294+
sigNumChanged = QtCore.pyqtSignal(int)
330295

331-
def __init__(self, slider, axis, zoomMargin, dynamicRange):
296+
def __init__(self, slider, axis, zoomMargin, dynamicRange, zoomFactor):
332297
QtCore.QObject.__init__(self)
333298
self.axis = axis
299+
axis.proxy = self
334300
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
338304
self.zoomMargin = zoomMargin
339305
self.dynamicRange = dynamicRange
306+
self.zoomFactor = zoomFactor
340307

341308
# Transform that maps the spinboxes to a pixel position on the
342309
# axis. 0 to axis.width() exclusive indicate positions which will be
@@ -347,6 +314,12 @@ def __init__(self, slider, axis, zoomMargin, dynamicRange):
347314
self.realToPixelTransform = -self.axis.width()/2, 1.
348315
self.invalidOldSizeExpected = True
349316

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+
350323
# pixel vals for sliders: 0 to slider_width - 1
351324
def realToPixel(self, val):
352325
a, b = self.realToPixelTransform
@@ -381,14 +354,15 @@ def moveStart(self, val):
381354
self.axis.update()
382355

383356
def handleStopMoved(self, rangeVal):
357+
# FIXME: this relies on the event being fed back and ending up calling
358+
# moveStop()
384359
self.sigStopMoved.emit(self.rangeToReal(rangeVal))
385360

386361
def handleStartMoved(self, rangeVal):
362+
# FIXME: this relies on the event being fed back and ending up calling
363+
# moveStart()
387364
self.sigStartMoved.emit(self.rangeToReal(rangeVal))
388365

389-
def handleNumPoints(self, inc):
390-
self.sigNumPoints.emit(self.numPoints + inc)
391-
392366
def setNumPoints(self, val):
393367
self.numPoints = val
394368
self.axis.update()
@@ -446,28 +420,55 @@ def snapRange(self):
446420
highRange = 1 - self.zoomMargin
447421
newStart = self.pixelToReal(lowRange * self.slider.effectiveWidth())
448422
newStop = self.pixelToReal(highRange * self.slider.effectiveWidth())
449-
sliderRange = self.slider.maximum() - self.slider.minimum()
450423
# Signals won't fire unless slider was actually grabbed, so
451424
# manually update so the spinboxes know that knew values were set.
452425
# self.realStop/Start and the sliders themselves will be updated as a
453426
# consequence of ValueChanged signal in spinboxes. The slider widget
454427
# 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()
458456

459457
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):
463462
return False
464463
if ev.oldSize().isValid():
465464
oldLeft = self.pixelToReal(0)
466465
refWidth = ev.oldSize().width() - self.slider.handleWidth()
467466
refRight = self.pixelToReal(refWidth)
468467
newWidth = ev.size().width() - self.slider.handleWidth()
469-
# assert refRight > oldLeft
470468
newScale = newWidth/(refRight - oldLeft)
469+
center = (self.realStop + self.realStart)/2
470+
if center:
471+
newScale = min(newScale, self.dynamicRange/abs(center))
471472
self.realToPixelTransform = oldLeft, newScale
472473
else:
473474
# TODO: self.axis.width() is invalid during object
@@ -484,8 +485,6 @@ def eventFilter(self, obj, ev):
484485
# the slider has already resized itsef or not.
485486
self.viewRangeInit()
486487
self.invalidOldSizeExpected = False
487-
# assert self.pixelToReal(0) == oldLeft, \
488-
# "{}, {}".format(self.pixelToReal(0), oldLeft)
489488
# Slider will update independently, making sure that the old
490489
# slider positions are preserved. Because of this, we can be
491490
# confident that the new slider position will still map to the
@@ -498,43 +497,34 @@ class ScanWidget(QtWidgets.QWidget):
498497
sigStopMoved = QtCore.pyqtSignal(float)
499498
sigNumChanged = QtCore.pyqtSignal(int)
500499

501-
def __init__(self, zoomFactor=1.05, zoomMargin=.1, dynamicRange=1e8):
500+
def __init__(self, zoomFactor=1.05, zoomMargin=.1, dynamicRange=1e9):
502501
QtWidgets.QWidget.__init__(self)
503502
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)
508506

509507
# 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)
515512
self.setLayout(layout)
516513

517514
# Connect signals (minus context menu)
518-
slider.sigStopMoved.connect(self.proxy.handleStopMoved)
519-
slider.sigStartMoved.connect(self.proxy.handleStartMoved)
520515
self.proxy.sigStopMoved.connect(self.sigStopMoved)
521516
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)
529518

530519
# 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)
535527

536-
# Spinbox and button slots. Any time the spinboxes change, ScanWidget
537-
# mirrors it and passes the information to the proxy.
538528
def setStop(self, val):
539529
self.proxy.moveStop(val)
540530

@@ -551,7 +541,4 @@ def snapRange(self):
551541
self.proxy.snapRange()
552542

553543
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

Comments
 (0)
Please sign in to comment.