artiq/artiq/gui/scanwidget.py

216 lines
7.3 KiB
Python
Raw Normal View History

import logging
2016-02-26 03:34:04 +08:00
from PyQt5 import QtGui, QtCore, QtWidgets
import numpy as np
2016-02-26 03:34:04 +08:00
from .ticker import Ticker
2016-02-26 03:34:04 +08:00
logger = logging.getLogger(__name__)
2016-03-17 05:08:32 +08:00
class ScanWidget(QtWidgets.QWidget):
startChanged = QtCore.pyqtSignal(float)
stopChanged = QtCore.pyqtSignal(float)
numChanged = QtCore.pyqtSignal(int)
def __init__(self, zoomFactor=1.05, zoomMargin=.1, dynamicRange=1e9):
2016-03-17 05:08:32 +08:00
QtWidgets.QWidget.__init__(self)
self.zoomMargin = zoomMargin
self.dynamicRange = dynamicRange
self.zoomFactor = zoomFactor
2016-02-26 03:34:04 +08:00
self.ticker = Ticker()
2016-03-17 06:30:50 +08:00
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
action = QtWidgets.QAction("V&iew range", self)
action.setShortcut(QtGui.QKeySequence("CTRL+i"))
action.setShortcutContext(QtCore.Qt.WidgetShortcut)
action.triggered.connect(self.viewRange)
2016-03-17 06:30:50 +08:00
self.addAction(action)
action = QtWidgets.QAction("Sna&p range", self)
action.setShortcut(QtGui.QKeySequence("CTRL+p"))
action.setShortcutContext(QtCore.Qt.WidgetShortcut)
action.triggered.connect(self.snapRange)
2016-03-17 06:30:50 +08:00
self.addAction(action)
qfm = QtGui.QFontMetrics(self.font())
self._labelSize = QtCore.QSize(
(self.ticker.precision + 5)*qfm.averageCharWidth(),
qfm.lineSpacing())
self._start, self._stop, self._num = None, None, None
2016-03-17 06:05:23 +08:00
self._axisView = None
self._offset, self._drag = None, None
2016-02-26 03:34:04 +08:00
2016-03-17 04:13:14 +08:00
def sizeHint(self):
return self.minimumSizeHint()
def minimumSizeHint(self):
return QtCore.QSize(2.5*3*self._labelSize.width(),
4*self._labelSize.height())
2016-03-17 04:13:14 +08:00
def _axisToPixel(self, val):
a, b = self._axisView
return a + val*b
def _pixelToAxis(self, val):
a, b = self._axisView
return (val - a)/b
def _setView(self, axis_left, axis_scale):
self._axisView = axis_left, axis_scale
self.update()
def setStart(self, val):
if self._start == val:
return
self._start = val
self.update()
self.startChanged.emit(val)
def setStop(self, val):
if self._stop == val:
return
self._stop = val
self.update()
self.stopChanged.emit(val)
def setNum(self, val):
if self._num == val:
return
self._num = val
self.update()
self.numChanged.emit(val)
def viewRange(self):
center = (self._stop + self._start)/2
scale = self.width()*(1 - 2*self.zoomMargin)
if self._stop != self._start:
scale /= abs(self._stop - self._start)
if center:
scale = min(scale, self.dynamicRange/abs(center))
else:
scale = self.dynamicRange
if center:
scale /= abs(center)
left = self.width()/2 - center*scale
self._setView(left, scale)
def snapRange(self):
self.setStart(self._pixelToAxis(self.zoomMargin*self.width()))
self.setStop(self._pixelToAxis((1 - self.zoomMargin)*self.width()))
2016-02-26 03:34:04 +08:00
def mousePressEvent(self, ev):
2016-03-17 06:05:23 +08:00
if ev.buttons() ^ ev.button(): # buttons changed
2016-02-26 03:34:04 +08:00
ev.ignore()
return
2016-03-17 06:05:23 +08:00
qfm = QtGui.QFontMetrics(self.font())
if ev.y() <= 2.5*qfm.lineSpacing(): # axis
self._drag = "axis"
self._offset = ev.x() - self._axisView[0]
elif abs(self._axisToPixel(self._stop) -
ev.x()) < qfm.lineSpacing()/2:
self._drag = "stop"
2016-03-17 05:08:32 +08:00
self._offset = ev.x() - self._axisToPixel(self._stop)
2016-03-17 06:05:23 +08:00
elif abs(self._axisToPixel(self._start) -
ev.x()) < qfm.lineSpacing()/2:
self._drag = "start"
self._offset = ev.x() - self._axisToPixel(self._start)
else:
2016-03-17 06:05:23 +08:00
self._drag = "both"
self._offset = (ev.x() - self._axisToPixel(self._start),
ev.x() - self._axisToPixel(self._stop))
2016-02-26 03:34:04 +08:00
def mouseMoveEvent(self, ev):
2016-03-17 06:05:23 +08:00
if not self._drag:
2016-02-26 03:34:04 +08:00
ev.ignore()
return
2016-03-17 06:05:23 +08:00
if self._drag == "axis":
self._setView(ev.x() - self._offset, self._axisView[1])
elif self._drag == "stop":
2016-03-16 22:59:49 +08:00
self._stop = self._pixelToAxis(ev.x() - self._offset)
self.update()
2016-03-17 05:08:32 +08:00
self.stopChanged.emit(self._stop)
2016-03-17 06:05:23 +08:00
elif self._drag == "start":
2016-03-16 22:59:49 +08:00
self._start = self._pixelToAxis(ev.x() - self._offset)
self.update()
2016-03-17 05:08:32 +08:00
self.startChanged.emit(self._start)
2016-03-17 06:05:23 +08:00
elif self._drag == "both":
self._start = self._pixelToAxis(ev.x() - self._offset[0])
self._stop = self._pixelToAxis(ev.x() - self._offset[1])
self.update()
self.startChanged.emit(self._start)
self.stopChanged.emit(self._stop)
2016-03-17 06:05:23 +08:00
def mouseReleaseEvent(self, ev):
self._drag = None
def _zoom(self, z, x):
a, b = self._axisView
scale = z*b
left = x - z*(x - a)
if z > 1 and abs(left - self.width()/2) > self.dynamicRange:
return
self._setView(left, scale)
def wheelEvent(self, ev):
y = ev.angleDelta().y()/120.
if not y:
return
if ev.modifiers() & QtCore.Qt.ShiftModifier:
self.setNum(max(1, self._num + y))
else:
self._zoom(self.zoomFactor**y, ev.x())
2016-02-26 03:34:04 +08:00
def resizeEvent(self, ev):
if not ev.oldSize().isValid():
self.viewRange()
return
a, b = self._axisView
scale = b*ev.size().width()/ev.oldSize().width()
center = (self._stop + self._start)/2
if center:
scale = min(scale, self.dynamicRange/abs(center))
left = a*scale/b
self.ticker.min_ticks = int(ev.size().width() /
(2.5*self._labelSize.width()))
self._setView(left, scale)
def paintEvent(self, ev):
painter = QtGui.QPainter(self)
qfm = QtGui.QFontMetrics(painter.font())
avgCharWidth = qfm.averageCharWidth()
lineSpacing = qfm.lineSpacing()
descent = qfm.descent()
2016-03-17 06:05:23 +08:00
ascent = qfm.ascent()
painter.translate(0, ascent)
ticks, prefix, labels = self.ticker(self._pixelToAxis(0),
self._pixelToAxis(self.width()))
painter.drawText(0, 0, prefix)
painter.translate(0, lineSpacing)
2016-03-17 04:13:14 +08:00
2016-03-17 06:05:23 +08:00
painter.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.SolidLine))
for t, l in zip(ticks, labels):
t = self._axisToPixel(t)
painter.drawText(t - len(l)/2*avgCharWidth, 0, l)
2016-03-17 06:05:23 +08:00
painter.drawLine(t, descent, t, lineSpacing/2)
painter.translate(0, lineSpacing/2)
2016-03-17 04:13:14 +08:00
painter.drawLine(0, 0, self.width(), 0)
for p in np.linspace(self._axisToPixel(self._start),
self._axisToPixel(self._stop),
self._num):
2016-03-17 06:05:23 +08:00
painter.drawLine(p, 0, p, lineSpacing/2)
painter.translate(0, lineSpacing/2)
2016-03-17 04:13:14 +08:00
for x, c in (self._start, QtCore.Qt.blue), (self._stop, QtCore.Qt.red):
x = self._axisToPixel(x)
2016-03-17 05:08:32 +08:00
painter.setPen(c)
painter.setBrush(c)
painter.drawPolygon(*(QtCore.QPointF(*i) for i in [
(x, 0), (x - lineSpacing/2, lineSpacing),
(x + lineSpacing/2, lineSpacing)]))