9393from qtpy import QtWidgets as QW
9494
9595from plotpy .config import CONF
96+ from plotpy .constants import SHAPE_Z_OFFSET
9697from plotpy .coords import axes_to_canvas , canvas_to_axes
97- from plotpy .items . shape . marker import Marker
98+ from plotpy .items import AnnotatedPolygon , Marker , PolygonShape
9899
99100if TYPE_CHECKING :
100101 from qtpy .QtCore import QPoint
@@ -1134,16 +1135,16 @@ class QtDragHandler(DragHandler):
11341135 """Class to handle drag events using Qt signals."""
11351136
11361137 #: Signal emitted by QtDragHandler when starting tracking
1137- SIG_START_TRACKING = QC .Signal (object , "QEvent " )
1138+ SIG_START_TRACKING = QC .Signal (object , "QMouseEvent " )
11381139
11391140 #: Signal emitted by QtDragHandler when stopping tracking and not moving
1140- SIG_STOP_NOT_MOVING = QC .Signal (object , "QEvent " )
1141+ SIG_STOP_NOT_MOVING = QC .Signal (object , "QMouseEvent " )
11411142
11421143 #: Signal emitted by QtDragHandler when stopping tracking and moving
1143- SIG_STOP_MOVING = QC .Signal (object , "QEvent " )
1144+ SIG_STOP_MOVING = QC .Signal (object , "QMouseEvent " )
11441145
11451146 #: Signal emitted by QtDragHandler when moving
1146- SIG_MOVE = QC .Signal (object , "QEvent " )
1147+ SIG_MOVE = QC .Signal (object , "QMouseEvent " )
11471148
11481149 def start_tracking (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
11491150 """Starts tracking the drag event.
@@ -1165,8 +1166,7 @@ def stop_notmoving(self, filter: StatefulEventFilter, event: QMouseEvent) -> Non
11651166 self .SIG_STOP_NOT_MOVING .emit (filter , event )
11661167
11671168 def stop_moving (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1168- """
1169- Stops the movement of the drag event.
1169+ """Stops the movement of the drag event.
11701170
11711171 Args:
11721172 filter: The StatefulEventFilter instance.
@@ -1632,6 +1632,10 @@ def __init__(
16321632 ) -> None :
16331633 super ().__init__ (filter , btn , mods , start_state )
16341634 self .avoid_null_shape = False
1635+ self .setup_shape_cb = None
1636+ self .shape = None
1637+ self .shape_h0 = None
1638+ self .shape_h1 = None
16351639
16361640 def set_shape (
16371641 self ,
@@ -1766,6 +1770,171 @@ def stop_moving_action(
17661770 filter .plot .do_zoom_rect_view (self .start , QC .QPointF (event .pos ()))
17671771
17681772
1773+ class MultilineSelectionHandler (DragHandler ):
1774+ """
1775+ A handler for handling multi-line selections.
1776+
1777+ This handler extends the DragHandler to handle multi-line selections.
1778+
1779+ Args:
1780+ filter: The StatefulEventFilter instance.
1781+ btn: The mouse button to match.
1782+ mods: The keyboard modifiers to match. (default: QC.Qt.NoModifier)
1783+ start_state: The starting state. (default: 0)
1784+ closed: Whether the polygon should be closed. (default: False)
1785+ """
1786+
1787+ SIG_END_POLYLINE = QC .Signal (object , np .ndarray )
1788+
1789+ def __init__ (
1790+ self ,
1791+ filter : StatefulEventFilter ,
1792+ btn : int ,
1793+ mods : QC .Qt .KeyboardModifiers = QC .Qt .NoModifier ,
1794+ start_state : int = 0 ,
1795+ closed : bool = False ,
1796+ ) -> None :
1797+ super ().__init__ (filter , btn , mods , start_state )
1798+ self .closed = closed
1799+ filter .add_event (
1800+ start_state ,
1801+ KeyEventMatch ((QC .Qt .Key_Enter , QC .Qt .Key_Return , QC .Qt .Key_Space )),
1802+ self .accept_polygonshape ,
1803+ start_state ,
1804+ )
1805+ filter .add_event (
1806+ start_state ,
1807+ KeyEventMatch ((QC .Qt .Key_Backspace , QC .Qt .Key_Escape )),
1808+ self .cancel_point ,
1809+ start_state ,
1810+ )
1811+ self .init_pos : QC .QPointF | None = None
1812+ self .shape : PolygonShape | AnnotatedPolygon | None = None
1813+ self .current_handle : int | None = None
1814+ self .setup_shape_cb : Callable | None = None
1815+
1816+ def set_shape (
1817+ self ,
1818+ shape : PolygonShape | AnnotatedPolygon ,
1819+ setup_shape_cb : Callable | None = None ,
1820+ ) -> None :
1821+ """Set the shape.
1822+
1823+ Args:
1824+ shape: The shape.
1825+ setup_shape_cb: The setup shape callback.
1826+ """
1827+ self .shape = shape
1828+ self .setup_shape_cb = setup_shape_cb
1829+
1830+ def start_tracking (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1831+ """Starts tracking the drag event.
1832+
1833+ Args:
1834+ filter: The StatefulEventFilter instance.
1835+ event: The QC.QEvent instance.
1836+ """
1837+ if self .init_pos is None :
1838+ self .init_pos = QC .QPointF (event .pos ())
1839+
1840+ def start_moving (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1841+ """Start moving the object.
1842+
1843+ Args:
1844+ filter: The StatefulEventFilter instance.
1845+ event: The mouse event.
1846+ """
1847+ if self .init_pos is None :
1848+ return
1849+ if self .shape .plot () is None :
1850+ self .shape .set_points (None )
1851+ filter .plot .add_item_with_z_offset (self .shape , SHAPE_Z_OFFSET )
1852+ self .shape .setZ (filter .plot .get_max_z () + 1 )
1853+ if self .setup_shape_cb is not None :
1854+ self .setup_shape_cb (self .shape )
1855+ self .shape .add_local_point (self .init_pos )
1856+ self .current_handle = self .shape .add_local_point (QC .QPointF (event .pos ()))
1857+ self .shape .set_closed (self .closed and len (self .shape .get_points ()) > 2 )
1858+ self .shape .show ()
1859+ filter .plot .replot ()
1860+
1861+ def move (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1862+ """
1863+ Handle mouse move event to update the position of the last point.
1864+
1865+ Args:
1866+ filter: The plot filter.
1867+ event: The mouse event.
1868+ """
1869+ self .shape .move_local_point_to (self .current_handle , QC .QPointF (event .pos ()))
1870+ filter .plot .replot ()
1871+
1872+ def stop_notmoving (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1873+ """Stops tracking when the drag event is not moving.
1874+
1875+ Args:
1876+ filter: The StatefulEventFilter instance.
1877+ event: The QC.QEvent instance.
1878+ """
1879+ points = self .shape .get_points ()
1880+ if len (points ) > 1 :
1881+ self .accept_polygonshape (filter , event )
1882+
1883+ def stop_moving (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1884+ """Stops the movement of the drag event.
1885+
1886+ Args:
1887+ filter: The StatefulEventFilter instance.
1888+ event: The QC.QEvent instance
1889+ """
1890+ pos = QC .QPointF (event .pos ())
1891+ if self .init_pos == pos :
1892+ self .shape .del_point (- 1 )
1893+ else :
1894+ self .shape .move_local_point_to (self .current_handle , pos )
1895+ filter .plot .replot ()
1896+
1897+ def accept_polygonshape (
1898+ self , filter : StatefulEventFilter , event : QMouseEvent
1899+ ) -> None :
1900+ """Accept the polygon shape.
1901+
1902+ Args:
1903+ filter: The StatefulEventFilter instance.
1904+ event: The mouse event.
1905+ """
1906+ if self .shape .plot () is None :
1907+ return
1908+ filter .plot .del_item (self .shape )
1909+ self .init_pos = None
1910+ self .current_handle = None
1911+ self .SIG_END_POLYLINE .emit (filter , self .shape .get_points ())
1912+
1913+ def cancel_point (self , filter : StatefulEventFilter , event : QMouseEvent ) -> None :
1914+ """
1915+ Cancel the last point or remove the shape if it has less than 3 points.
1916+
1917+ Args:
1918+ filter: The plot filter.
1919+ event: The triggering event.
1920+ """
1921+ points = self .shape .get_points ()
1922+ if points is None or self .shape .plot () is None :
1923+ return
1924+ if len (points ) <= 2 :
1925+ filter .plot .del_item (self .shape )
1926+ self .init_pos = None
1927+ self .shape .closed = False
1928+ else :
1929+ if self .current_handle :
1930+ newh = self .shape .del_point (self .current_handle )
1931+ else :
1932+ newh = self .shape .del_point (- 1 )
1933+ self .current_handle = newh
1934+ self .shape .set_closed (self .closed and len (self .shape .get_points ()) > 2 )
1935+ filter .plot .replot ()
1936+
1937+
17691938def setup_standard_tool_filter (filter : StatefulEventFilter , start_state : int ) -> int :
17701939 """Creation of standard filters (pan/zoom) on middle/right buttons
17711940
0 commit comments