"""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()