@@ -316,6 +316,20 @@ def __repr__(self):
316316 return "<GestureMatch: %s:%s>" % (type_str , state_str )
317317
318318
319+ class WheelEventMatch (EventMatch ):
320+ """A callable returning True if it matches a wheel event"""
321+
322+ def __init__ (self , modifiers = QC .Qt .KeyboardModifier .NoModifier ):
323+ super ().__init__ ()
324+ self .modifiers = modifiers
325+
326+ def get_event_types (self ):
327+ return frozenset ((QC .QEvent .Type .Wheel ,))
328+
329+ def __call__ (self , event : QC .QEvent ):
330+ return isinstance (event , QG .QWheelEvent ) and event .modifiers () == self .modifiers
331+
332+
319333# Finite state machine for event handling ----------
320334class StatefulEventFilter (QC .QObject ):
321335 """Gestion d'une machine d'état pour les événements
@@ -434,6 +448,13 @@ def gesture(self, kind, state):
434448 ("gesture" , kind , state ), GestureEventMatch (kind , state )
435449 )
436450
451+ def wheel (self , modifiers = QC .Qt .KeyboardModifier .NoModifier ):
452+ """Create a filter for wheel events
453+
454+ Args:
455+ modifiers: keyboard modifiers to use with the wheel event"""
456+ return self .events .setdefault (("wheel" , modifiers ), WheelEventMatch (modifiers ))
457+
437458 def nothing (self , filter , event ):
438459 """A nothing filter, provided to help removing duplicate handlers"""
439460 pass
@@ -849,6 +870,88 @@ def click(self, filter, event):
849870 menu .popup (event .globalPos ())
850871
851872
873+ class WheelHandler (QC .QObject ):
874+ """Base class for handling mouse wheel events.
875+
876+ Args:
877+ filter: event filter into which to add the handler instance.
878+ mods: Keyboard modifier that can be used at the same time as the wheel to
879+ trigger the event. Defaults to QC.Qt.KeyboardModifier.NoModifier.
880+ start_state: start state to use in the event filter state machine.
881+ Defaults to 0."""
882+
883+ def __init__ (
884+ self ,
885+ filter : StatefulEventFilter ,
886+ mods = QC .Qt .KeyboardModifier .NoModifier ,
887+ start_state = 0 ,
888+ ):
889+ super ().__init__ ()
890+ self .state0 = filter .add_event (
891+ start_state , filter .wheel (mods ), self .wheel , start_state
892+ )
893+
894+ def wheel (self , filter : StatefulEventFilter , event : QG .QWheelEvent ):
895+ """Handles the wheel event.
896+
897+ Args:
898+ filter: event filter that contains the BasePlot instance
899+ event: the wheel event that triggered the action."""
900+ raise NotImplementedError ()
901+
902+
903+ class WheelZoomHandler (WheelHandler ):
904+ """Class to handle zooming with the mouse wheel."""
905+
906+ def get_zoom_param (
907+ self , plot : BasePlot , pos : QPoint , factor : float
908+ ) -> tuple [tuple [float , float , float , float ], tuple [float , float , float , float ]]:
909+ """Returns the parameters to use for zooming on the plot.
910+
911+ Args:
912+ plot: instance of BasePlot to use as a reference.
913+ pos: position on the plot canvas of the current hotspot.
914+ factor: factor by which to zoom (zero-centered).
915+
916+ Returns:
917+ Returns two tuples of four floats each, representing the parameters used
918+ by BasePlot.do_zoom_view.
919+ """
920+
921+ x , y = pos .x (), pos .y ()
922+ rect = plot .contentsRect ()
923+ w , h = rect .width (), rect .height ()
924+ dx = (
925+ x + (w * factor ),
926+ x ,
927+ x ,
928+ w ,
929+ )
930+ dy = (
931+ y + (h * factor ),
932+ y ,
933+ y ,
934+ h ,
935+ )
936+ return dx , dy
937+
938+ def wheel (self , filter : StatefulEventFilter , event : QG .QWheelEvent ):
939+ """Overrides the WheelHandler.wheel method to handle the zooming with the mouse
940+ wheel.
941+
942+ Args:
943+ filter: event filter that contains the BasePlot instance
944+ event: the wheel event that triggered the zooming. Use to get zoom factor
945+ and position
946+ """
947+ plot = filter .plot
948+ center_point = event .globalPos ()
949+ center_point = filter .plot .canvas ().mapFromGlobal (center_point ) # type: ignore
950+ zoom_factor = (event .angleDelta ().y () / 120 ) * 0.08
951+ dx , dy = self .get_zoom_param (plot , center_point , zoom_factor )
952+ plot .do_zoom_view (dx , dy , lock_aspect_ratio = True )
953+
954+
852955class QtDragHandler (DragHandler ):
853956 #: Signal emitted by QtDragHandler when starting tracking
854957 SIG_START_TRACKING = QC .Signal (object , "QEvent" )
@@ -1347,18 +1450,25 @@ def stop_moving_action(self, filter, event):
13471450def setup_standard_tool_filter (filter : StatefulEventFilter , start_state ):
13481451 """Création des filtres standard (pan/zoom) sur boutons milieu/droit"""
13491452 # Bouton du milieu
1350- PanHandler (filter , QC .Qt .MidButton , start_state = start_state )
1351- AutoZoomHandler (filter , QC .Qt .MidButton , start_state = start_state )
1453+ PanHandler (filter , QC .Qt .MouseButton . MidButton , start_state = start_state )
1454+ AutoZoomHandler (filter , QC .Qt .MouseButton . MidButton , start_state = start_state )
13521455
13531456 # Bouton droit
1354- ZoomHandler (filter , QC .Qt .RightButton , start_state = start_state )
1355- MenuHandler (filter , QC .Qt .RightButton , start_state = start_state )
1457+ ZoomHandler (filter , QC .Qt .MouseButton . RightButton , start_state = start_state )
1458+ MenuHandler (filter , QC .Qt .MouseButton . RightButton , start_state = start_state )
13561459
13571460 # Gestes
13581461 PinchPanGestureHandler (filter , start_state = start_state )
13591462
13601463 # Autres (touches, move)
13611464 MoveHandler (filter , start_state = start_state )
1362- MoveHandler (filter , start_state = start_state , mods = QC .Qt .ShiftModifier )
1363- MoveHandler (filter , start_state = start_state , mods = QC .Qt .AltModifier )
1465+ MoveHandler (
1466+ filter , start_state = start_state , mods = QC .Qt .KeyboardModifier .ShiftModifier
1467+ )
1468+ MoveHandler (
1469+ filter , start_state = start_state , mods = QC .Qt .KeyboardModifier .AltModifier
1470+ )
1471+ WheelZoomHandler (
1472+ filter , start_state = start_state , mods = QC .Qt .KeyboardModifier .ControlModifier
1473+ )
13641474 return start_state
0 commit comments