Skip to content

Commit 1c4f67d

Browse files
committed
Merge branch 'develop' of //codra.local/fichiers/DI_Projets/CEA-plotpy/Sources/PlotPy into develop
2 parents eaf4d65 + 7942d10 commit 1c4f67d

2 files changed

Lines changed: 217 additions & 70 deletions

File tree

plotpy/events.py

Lines changed: 197 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,19 @@
1818
from __future__ import annotations
1919

2020
import weakref
21+
from typing import TYPE_CHECKING
2122

23+
import numpy as np
2224
from qtpy import QtCore as QC
2325
from qtpy import QtGui as QG
26+
from qtpy import QtWidgets as QW
2427

2528
from plotpy.config import CONF
2629
from plotpy.coords import axes_to_canvas, canvas_to_axes
30+
from plotpy.items.shape.marker import Marker
31+
32+
if TYPE_CHECKING:
33+
from plotpy.plot.base import BasePlot
2734

2835
CursorShape = type(QC.Qt.CursorShape.ArrowCursor)
2936

@@ -246,14 +253,14 @@ class StatefulEventFilter(QC.QObject):
246253
d'un canvas
247254
"""
248255

249-
def __init__(self, parent):
256+
def __init__(self, parent: BasePlot):
250257
super().__init__()
251258
self.states = {0: {}} # 0 : cursor 1: panning, 2: zooming
252259
self.cursors = {}
253260
self.state = 0
254261
self.max_state = 0
255262
self.events = {}
256-
self.plot = parent
263+
self.plot: BasePlot = parent
257264
self.all_event_types = frozenset()
258265

259266
def eventFilter(self, _obj, event):
@@ -262,7 +269,6 @@ def eventFilter(self, _obj, event):
262269
print(repr(self), self)
263270
if event.type() not in self.all_event_types:
264271
return False
265-
266272
state = self.states[self.state]
267273
for match, (call_list, next_state) in list(state.items()):
268274
if match(event):
@@ -514,123 +520,250 @@ def move(self, filter, event):
514520

515521

516522
class GestureHandler(QC.QObject):
517-
"""Classe de base pour les gestionnaires d'événements du type tactile"""
523+
"""Base class used to handle gestures.
518524
519-
kind = None
525+
Args:
526+
filter: event filter into which to add the handler instance.
527+
start_state: start state to use in the event filter state machine.
528+
Defaults to 0.
529+
530+
"""
520531

521-
def __init__(self, filter, start_state=0):
522-
super(GestureHandler, self).__init__()
532+
kind: QC.Qt.GestureType | None = None
533+
534+
def __init__(self, filter: StatefulEventFilter, start_state=0) -> None:
535+
super().__init__()
523536
filter.plot.canvas().grabGesture(self.kind)
524537
self.state0 = filter.add_event(
525538
start_state,
526-
filter.gesture(self.kind, QC.Qt.GestureStarted),
539+
filter.gesture(self.kind, QC.Qt.GestureState.GestureStarted),
527540
self.start_tracking,
528541
)
529542
self.state1 = filter.add_event(
530543
self.state0,
531-
filter.gesture(self.kind, QC.Qt.GestureUpdated),
544+
filter.gesture(self.kind, QC.Qt.GestureState.GestureUpdated),
532545
self.start_moving,
533546
)
534547
filter.add_event(
535548
self.state1,
536-
filter.gesture(self.kind, QC.Qt.GestureUpdated),
549+
filter.gesture(self.kind, QC.Qt.GestureState.GestureUpdated),
537550
self.move,
538551
self.state1,
539552
)
540553
filter.add_event(
541554
self.state0,
542-
filter.gesture(self.kind, QC.Qt.GestureFinished),
555+
filter.gesture(self.kind, QC.Qt.GestureState.GestureFinished),
543556
self.stop_notmoving,
544557
start_state,
545558
)
546559
filter.add_event(
547560
self.state1,
548-
filter.gesture(self.kind, QC.Qt.GestureFinished),
561+
filter.gesture(self.kind, QC.Qt.GestureState.GestureFinished),
549562
self.stop_moving,
550563
start_state,
551564
)
552565
filter.add_event(
553566
self.state0,
554-
filter.gesture(self.kind, QC.Qt.GestureCanceled),
567+
filter.gesture(self.kind, QC.Qt.GestureState.GestureCanceled),
555568
self.stop_notmoving,
556569
start_state,
557570
)
558571
filter.add_event(
559572
self.state1,
560-
filter.gesture(self.kind, QC.Qt.GestureCanceled),
573+
filter.gesture(self.kind, QC.Qt.GestureState.GestureCanceled),
561574
self.stop_notmoving,
562575
start_state,
563576
)
564-
self.start = None # first gesture position
565-
self.last = None # gesture position seen during last event
577+
self.start = QC.QPoint() # first gesture position
578+
self.last = QC.QPoint() # gesture position seen during last event
566579
self.parent_tracking = None
567580

568-
def get_gesture(self, event):
581+
def get_glob_position(self, event: QW.QGestureEvent) -> QC.QPointF:
582+
"""Returns the hotspot global position of the gesture event.
583+
584+
Args:
585+
event: event from which to get the position
586+
587+
Returns:
588+
event hotspot poisition in global coordinates.
589+
"""
590+
return self.get_gesture(event).hotSpot()
591+
592+
def get_gesture(self, event: QW.QGestureEvent) -> QW.QGesture:
593+
"""Returns the gesture from the event.
594+
595+
Args:
596+
event: event from which to get the gesture.
597+
598+
Returns:
599+
gesture from the event.
600+
"""
569601
return event.gesture(self.kind)
570602

571-
def get_move_state(self, filter, gesture):
572-
raise NotImplementedError
603+
def start_tracking(
604+
self, filter: StatefulEventFilter, event: QW.QGestureEvent
605+
) -> None:
606+
"""Handles the start of the gesture tracking.
573607
574-
def start_tracking(self, filter, event):
575-
# print("Getting event for start tracking")
576-
origin = self.get_gesture(event).hotSpot()
577-
self.start = self.last = filter.plot.mapFromGlobal(origin.toPoint())
608+
Args:
609+
filter: event filter that contains the BasePlot instance
610+
event: event that triggered the start of the tracking, used to get
611+
a position.
612+
"""
613+
origin = self.get_glob_position(event)
614+
self.start = self.last = filter.plot.canvas().mapFromGlobal(origin.toPoint())
578615

579-
def start_moving(self, filter, event):
616+
def start_moving(
617+
self, filter: StatefulEventFilter, event: QW.QGestureEvent
618+
) -> None:
619+
"""Handles the start of the gesture moving.
620+
621+
Args:
622+
filter: event filter that contains the BasePlot instance
623+
event: event that triggered the start of the moving
624+
"""
580625
return self.move(filter, event)
581626

582-
def stop_tracking(self, _filter, _event):
627+
def stop_tracking(
628+
self, _filter: StatefulEventFilter, _event: QW.QGestureEvent
629+
) -> None:
583630
pass
584-
# filter.plot.canvas().setMouseTracking(self.parent_tracking)
585631

586-
def stop_notmoving(self, filter, event):
632+
def stop_notmoving(
633+
self, filter: StatefulEventFilter, event: QW.QGestureEvent
634+
) -> None:
587635
self.stop_tracking(filter, event)
588636

589-
def stop_moving(self, filter, event):
637+
def stop_moving(self, filter: StatefulEventFilter, event: QW.QGestureEvent) -> None:
590638
self.stop_tracking(filter, event)
591639

592-
def move(self, filter, event):
640+
def move(self, filter: StatefulEventFilter, event: QW.QGestureEvent) -> None:
593641
raise NotImplementedError
594642

595643

596-
class PinchZoomHandler(GestureHandler):
597-
"""Classe de base pour les gestionnaires d'événements du type tactile"""
644+
class PinchPanGestureHandler(GestureHandler):
645+
"""Class used to handle pinch and pan gestures.
598646
599-
kind = QC.Qt.PinchGesture
647+
Args:
648+
filter: event filter into which to add the handler instance.
649+
start_state: start state to use in the event filter state machine.
650+
Defaults to 0.
600651
601-
def get_move_state(self, filter, gesture):
602-
rct = filter.plot.contentsRect()
603-
xshift = rct.width() * (gesture.scaleFactor() - 1)
604-
yshift = rct.height() * (gesture.scaleFactor() - 1)
605-
pos = QC.QPointF(self.last.x() + xshift, self.last.y() + yshift)
606-
dx = (pos.x(), self.last.x(), self.start.x(), rct.width())
607-
dy = (pos.y(), self.last.y(), self.start.y(), rct.height())
608-
self.last = QC.QPointF(pos)
609-
return dx, dy
652+
"""
610653

611-
def move(self, filter, event):
612-
gesture = self.get_gesture(event)
613-
x_state, y_state = self.get_move_state(filter, gesture)
614-
filter.plot.do_zoom_view(x_state, y_state)
654+
kind = QC.Qt.GestureType.PinchGesture
615655

656+
def __init__(
657+
self,
658+
filter: StatefulEventFilter,
659+
start_state=0,
660+
):
661+
super().__init__(filter, start_state)
662+
self.last_center_diff = 0
663+
self.marker: Marker | None = None
616664

617-
class PanGestureHandler(GestureHandler):
618-
"""Classe de base pour les gestionnaires d'événements du type tactile"""
665+
def get_pan_param(
666+
self, plot: BasePlot, pos: QC.QPoint
667+
) -> tuple[tuple[float, float, float, float], tuple[float, float, float, float]]:
668+
"""Returns the parameters to use for panning the plot.
619669
620-
kind = QC.Qt.PanGesture
670+
Args:
671+
plot: instance of BasePlot to use as a reference.
672+
pos: position on the plot canvas of the current hotspot.
621673
622-
def get_move_state(self, filter, gesture):
623-
rct = filter.plot.contentsRect()
624-
pos = gesture.offset()
625-
self.last = gesture.lastOffset()
674+
Returns:
675+
Returns two tuples of four floats each, representing the parameters used
676+
by BasePlot.do_pan_view.
677+
"""
678+
rct = plot.contentsRect()
626679
dx = (pos.x(), self.last.x(), self.start.x(), rct.width())
627680
dy = (pos.y(), self.last.y(), self.start.y(), rct.height())
681+
self.last = pos
628682
return dx, dy
629683

630-
def move(self, filter, event):
631-
gesture = self.get_gesture(event)
632-
x_state, y_state = self.get_move_state(filter, gesture)
633-
filter.plot.do_pan_view(x_state, y_state)
684+
def get_zoom_param(
685+
self, plot: BasePlot, pos: QC.QPoint, factor: float
686+
) -> tuple[tuple[float, float, float, float], tuple[float, float, float, float]]:
687+
"""Returns the parameters to use for zooming on the plot.
688+
689+
Args:
690+
plot: instance of BasePlot to use as a reference.
691+
pos: position on the plot canvas of the current hotspot.
692+
factor: factor by which to zoom.
693+
694+
Returns:
695+
Returns two tuples of four floats each, representing the parameters used
696+
by BasePlot.do_zoom_view.
697+
"""
698+
rect_width = plot.contentsRect().width()
699+
dx = (
700+
pos.x() * factor,
701+
pos.x(),
702+
pos.x(),
703+
rect_width,
704+
)
705+
dy = (
706+
pos.y() * factor,
707+
pos.y(),
708+
pos.y(),
709+
rect_width,
710+
)
711+
return dx, dy
712+
713+
def start_tracking(
714+
self, filter: StatefulEventFilter, event: QW.QGestureEvent
715+
) -> None:
716+
"""Overrides the GestureHandler.start_tracking method to add a marker at the
717+
hostpot of the pinch gesture.
718+
719+
Args:
720+
filter: event filter that contains the BasePlot instance
721+
event: Gesture event that triggered the start of the tracking, used to get
722+
a position.
723+
"""
724+
plot = filter.plot
725+
if self.marker is None:
726+
self.marker = Marker()
727+
self.marker.attach(plot)
728+
self.marker.setZ(plot.get_max_z() + 1)
729+
self.marker.setVisible(True)
730+
pos = plot.canvas().mapFromGlobal(self.get_glob_position(event).toPoint())
731+
self.marker.move_local_point_to(0, pos)
732+
733+
return super().start_tracking(filter, event)
734+
735+
def move(self, filter: StatefulEventFilter, event: QW.QGestureEvent) -> None:
736+
"""Overrides the GestureHandler.move method to handle the pinch and pan gesture.
737+
738+
Args:
739+
filter: event filter that contains the BasePlot instance
740+
event: event that triggered the move, used to get the hotspot position.
741+
"""
742+
gesture: QW.QPinchGesture = self.get_gesture(event)
743+
plot = filter.plot
744+
745+
center_point = self.get_glob_position(event)
746+
center_point = filter.plot.canvas().mapFromGlobal(center_point.toPoint())
747+
scale_factor = np.clip(gesture.scaleFactor(), 0.90, 1.1)
748+
749+
pan_dx, pan_dy = self.get_pan_param(plot, center_point)
750+
zoom_dx, zoom_dy = self.get_zoom_param(plot, center_point, scale_factor)
751+
plot.do_zoom_view(zoom_dx, zoom_dy, replot=False)
752+
plot.do_pan_view(pan_dx, pan_dy)
753+
754+
def stop_tracking(
755+
self, _filter: StatefulEventFilter, _event: QW.QGestureEvent
756+
) -> None:
757+
"""Overrides the GestureHandler.stop_tracking method to remove the marker when
758+
the gesture tracking stops.
759+
760+
Args:
761+
_filter: event filter that contains the BasePlot instance
762+
_event: event that triggered the stop, not used.
763+
"""
764+
if self.marker is not None:
765+
self.marker.detach()
766+
self.marker = None
634767

635768

636769
class MenuHandler(ClickHandler):
@@ -796,7 +929,12 @@ class ObjectHandler:
796929
""" """
797930

798931
def __init__(
799-
self, filter, btn, mods=QC.Qt.NoModifier, start_state=0, multiselection=False
932+
self,
933+
filter: StatefulEventFilter,
934+
btn,
935+
mods=QC.Qt.NoModifier,
936+
start_state=0,
937+
multiselection=False,
800938
):
801939
self.multiselection = multiselection
802940
self.start_state = start_state
@@ -1135,7 +1273,7 @@ def stop_moving_action(self, filter, event):
11351273
filter.plot.do_zoom_rect_view(self.start, event.pos())
11361274

11371275

1138-
def setup_standard_tool_filter(filter, start_state):
1276+
def setup_standard_tool_filter(filter: StatefulEventFilter, start_state):
11391277
"""Création des filtres standard (pan/zoom) sur boutons milieu/droit"""
11401278
# Bouton du milieu
11411279
PanHandler(filter, QC.Qt.MidButton, start_state=start_state)
@@ -1146,10 +1284,7 @@ def setup_standard_tool_filter(filter, start_state):
11461284
MenuHandler(filter, QC.Qt.RightButton, start_state=start_state)
11471285

11481286
# Gestes
1149-
PinchZoomHandler(filter, start_state=start_state)
1150-
# FIXME: Pinch/PanZoomHandler are currently mutually exclusive: when both
1151-
# are enabled, it doesn't work ; when only one is enabled, it works
1152-
PanGestureHandler(filter, start_state=start_state)
1287+
PinchPanGestureHandler(filter, start_state=start_state)
11531288

11541289
# Autres (touches, move)
11551290
MoveHandler(filter, start_state=start_state)

0 commit comments

Comments
 (0)