113 lines
4.5 KiB
Python
113 lines
4.5 KiB
Python
"""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; 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_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_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()
|
||
|