1818from __future__ import annotations
1919
2020import weakref
21+ from typing import TYPE_CHECKING
2122
23+ import numpy as np
2224from qtpy import QtCore as QC
2325from qtpy import QtGui as QG
26+ from qtpy import QtWidgets as QW
2427
2528from plotpy .config import CONF
2629from 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
2835CursorShape = 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
516522class 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
636769class 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