125 lines
5.2 KiB
Python
125 lines
5.2 KiB
Python
# Общие жесты клавиатуры и колеса для Qt-графиков. Обработчики переводят пользовательские
|
|
# события в изменения области просмотра; настройки осей и сохранение данных остаются в
|
|
# владельце графика.
|
|
|
|
"""Common keyboard and wheel gestures for all plot canvases (Qt 5/6)."""
|
|
try:
|
|
from PySide6.QtCore import Qt
|
|
from PySide6.QtWidgets import QScrollArea
|
|
except ImportError:
|
|
from PySide2.QtCore import Qt
|
|
from PySide2.QtWidgets import QScrollArea
|
|
|
|
NAVIGATION_HINT = ('Колесо и +/− — масштаб X; Ctrl + колесо и Ctrl + +/− — масштаб Y. '
|
|
'ЛКМ — перемещение по X; Ctrl + ЛКМ — по Y; '
|
|
'←/→ — перемещение по X; ↑/↓ — по Y; Home/End — начало/конец.')
|
|
|
|
|
|
class PlotNavigation:
|
|
def setup_navigation(self):
|
|
self.setFocusPolicy(Qt.StrongFocus)
|
|
self.setToolTip(NAVIGATION_HINT)
|
|
|
|
def keyPressEvent(self, event):
|
|
key = event.key()
|
|
modifiers = event.modifiers()
|
|
if modifiers & (Qt.AltModifier | Qt.MetaModifier):
|
|
return super().keyPressEvent(event)
|
|
if key in (Qt.Key_Plus, Qt.Key_Equal, Qt.Key_Minus):
|
|
self._navigation_zoom(1.25 if key != Qt.Key_Minus else .8,
|
|
bool(modifiers & Qt.ControlModifier))
|
|
elif key in (Qt.Key_Left, Qt.Key_Right) and not (modifiers & Qt.ControlModifier):
|
|
self._navigation_pan(-.1 if key == Qt.Key_Left else .1)
|
|
elif key in (Qt.Key_Up, Qt.Key_Down):
|
|
self._navigation_pan_y(-.1 if key == Qt.Key_Up else .1)
|
|
elif key in (Qt.Key_Home, Qt.Key_End):
|
|
self._navigation_edge(key == Qt.Key_End)
|
|
else:
|
|
return super().keyPressEvent(event)
|
|
event.accept()
|
|
|
|
def wheelEvent(self, event):
|
|
position = event.position() if hasattr(event, 'position') else event.posF()
|
|
rect = self.plot_rect() if hasattr(self, 'plot_rect') else self.rect()
|
|
if not rect.contains(position.toPoint()):
|
|
event.ignore()
|
|
return
|
|
delta = event.angleDelta()
|
|
ticks = delta.y() or delta.x()
|
|
if not ticks:
|
|
pixels = event.pixelDelta()
|
|
ticks = (pixels.y() or pixels.x()) * 2
|
|
if ticks:
|
|
self.setFocus(Qt.MouseFocusReason)
|
|
self._navigation_zoom(1.25 ** max(-8, min(8, ticks / 120)),
|
|
bool(event.modifiers() & Qt.ControlModifier), position)
|
|
event.accept()
|
|
|
|
|
|
class ScrollPlotNavigation(PlotNavigation):
|
|
"""Canvas navigation for waveforms whose viewport is a QScrollArea."""
|
|
def scroll_area(self):
|
|
parent = self.parentWidget()
|
|
while parent is not None and not isinstance(parent, QScrollArea):
|
|
parent = parent.parentWidget()
|
|
return parent
|
|
|
|
def _navigation_zoom(self, factor, vertical, position=None):
|
|
scroll = self.scroll_area()
|
|
size = self.height() if vertical else self.width()
|
|
if position is not None:
|
|
anchor = position.y() if vertical else position.x()
|
|
elif scroll is not None:
|
|
bar = scroll.verticalScrollBar() if vertical else scroll.horizontalScrollBar()
|
|
anchor = bar.value() + bar.pageStep() / 2
|
|
else:
|
|
anchor = size / 2
|
|
self.zoom_dragged.emit('y' if vertical else 'x', factor, anchor / max(1, size))
|
|
|
|
def _navigation_pan(self, fraction):
|
|
scroll = self.scroll_area()
|
|
if scroll is not None:
|
|
bar = scroll.horizontalScrollBar()
|
|
bar.setValue(round(bar.value() + fraction * bar.pageStep()))
|
|
self.navigation_panned()
|
|
|
|
def _navigation_pan_y(self, fraction):
|
|
scroll = self.scroll_area()
|
|
if scroll is not None:
|
|
bar = scroll.verticalScrollBar()
|
|
bar.setValue(round(bar.value() + fraction * bar.pageStep()))
|
|
|
|
def _navigation_edge(self, end):
|
|
scroll = self.scroll_area()
|
|
if scroll is not None:
|
|
bar = scroll.horizontalScrollBar()
|
|
bar.setValue(bar.maximum() if end else bar.minimum())
|
|
self.navigation_panned()
|
|
|
|
def navigation_panned(self):
|
|
pass
|
|
|
|
def begin_pan(self, event):
|
|
self.setFocus(Qt.MouseFocusReason)
|
|
self._pan_position = event.globalPosition() if hasattr(event, 'globalPosition') else event.globalPos()
|
|
self.setCursor(Qt.ClosedHandCursor)
|
|
|
|
def move_pan(self, event):
|
|
previous = getattr(self, '_pan_position', None)
|
|
if previous is None:
|
|
return
|
|
position = event.globalPosition() if hasattr(event, 'globalPosition') else event.globalPos()
|
|
delta = position - previous
|
|
self._pan_position = position
|
|
scroll = self.scroll_area()
|
|
if scroll is not None:
|
|
for bar, amount in ((scroll.horizontalScrollBar(), delta.x()), (scroll.verticalScrollBar(), delta.y())):
|
|
bar.setValue(round(bar.value() - amount))
|
|
if delta.x():
|
|
self.navigation_panned()
|
|
|
|
def end_pan(self, event):
|
|
self.move_pan(event)
|
|
self._pan_position = None
|
|
self.unsetCursor()
|