3535)
3636from PySide6 .QtGui import (
3737 QPixmap , QPainter , QPen , QColor , QFont , QMouseEvent , QPaintEvent ,
38- QCloseEvent ,
38+ QCloseEvent , QCursor ,
3939)
4040from PySide6 .QtCore import (
41- Qt , QRect , QPoint , QSize ,
41+ Qt , QRect , QPoint , QSize , QPointF ,
4242)
4343
44+
4445# DPI for the preview image. Higher values are clearer but use more memory.
4546PREVIEW_DPI = 150
4647
@@ -56,26 +57,69 @@ def __init__(self, parent=None):
5657 self .start_point : QPoint = QPoint ()
5758 self .end_point : QPoint = QPoint ()
5859 self .is_selecting : bool = False
60+ self .is_selection_moving : bool = False
61+ self .drag_offset : QPoint = QPoint ()
62+
63+ def __is_position_inside_selection (self , position : QPointF ) -> bool :
64+ """Check if the given position is inside the selection rectangle."""
65+ return (not self .selection_rect .isNull () and
66+ self .selection_rect .contains (position .toPoint (), proper = True ))
5967
6068 def mousePressEvent (self , event : QMouseEvent ) -> None : # pylint: disable=invalid-name
61- """Handle mouse click to start the selection."""
69+ """Handle mouse click to start the selection or move existing selection ."""
6270 if event .button () == Qt .MouseButton .LeftButton :
63- self .start_point = event .position ().toPoint ()
64- self .selection_rect = QRect (self .start_point , QSize ())
65- self .is_selecting = True
71+ # Check if clicking inside existing selection to move it
72+ if self .__is_position_inside_selection (event .position ()):
73+ self .is_selection_moving = True
74+ self .drag_offset = event .position ().toPoint () - self .selection_rect .topLeft ()
75+ else :
76+ # Start new selection
77+ self .start_point = event .position ().toPoint ()
78+ self .selection_rect = QRect (self .start_point , QSize ())
79+ self .is_selecting = True
6680 self .update ()
6781
6882 def mouseMoveEvent (self , event : QMouseEvent ) -> None : # pylint: disable=invalid-name
69- """Handle mouse drag while selection is in progress."""
83+ """Handle mouse drag while selection is in progress or being moved."""
84+ # Update cursor based on position
85+ # TODO(mbrukman): this is currently not working; the cursor shape does not update.
86+ if self .__is_position_inside_selection (event .position ()):
87+ self .setCursor (QCursor (Qt .CursorShape .SizeAllCursor ))
88+ else :
89+ self .setCursor (QCursor (Qt .CursorShape .ArrowCursor ))
90+ self .update ()
91+
7092 if self .is_selecting :
7193 self .end_point = event .position ().toPoint ()
7294 self .selection_rect = QRect (self .start_point , self .end_point ).normalized ()
7395 self .update () # Trigger a repaint
96+ elif self .is_selection_moving :
97+ new_top_left = event .position ().toPoint () - self .drag_offset
98+
99+ # Constrain to widget bounds
100+ rect_width = self .selection_rect .width ()
101+ rect_height = self .selection_rect .height ()
102+
103+ # Ensure we don't move outside the image area
104+ # The pixmap might be smaller than the widget if not resized, but usually it fits.
105+ # We constrain to the widget's rect for simplicity, or the pixmap rect if available.
106+ max_w = self .width ()
107+ max_h = self .height ()
108+ if self .pixmap ():
109+ max_w = self .pixmap ().width ()
110+ max_h = self .pixmap ().height ()
111+
112+ x = max (0 , min (new_top_left .x (), max_w - rect_width ))
113+ y = max (0 , min (new_top_left .y (), max_h - rect_height ))
114+
115+ self .selection_rect .moveTo (x , y )
116+ self .update ()
74117
75118 def mouseReleaseEvent (self , event : QMouseEvent ) -> None : # pylint: disable=invalid-name
76119 """Handle mouse button release while making a selection."""
77- if event .button () == Qt .MouseButton .LeftButton and self . is_selecting :
120+ if event .button () == Qt .MouseButton .LeftButton :
78121 self .is_selecting = False
122+ self .is_selection_moving = False
79123 self .update ()
80124
81125 def paintEvent (self , event : QPaintEvent ) -> None : # pylint: disable=invalid-name
@@ -140,14 +184,16 @@ def __init__(self):
140184 control_layout .addWidget (self .btn_save )
141185
142186 # Scrollable area for the PDF viewer
143- self .scroll_area : QScrollArea = QScrollArea ()
187+ self .scroll_area : QScrollArea = QScrollArea (parent = self )
144188 # Allow the widget to be larger than the viewport, enabling scrolling.
145189 self .scroll_area .setWidgetResizable (False )
146190 # A dark background makes the page stand out, using a stylesheet.
147191 self .scroll_area .setStyleSheet ("background-color: #3c3c3c;" )
192+ self .scroll_area .setMouseTracking (True )
193+ self .scroll_area .viewport ().setMouseTracking (True )
148194
149195 # Custom label for viewing and selection
150- self .viewer : PDFViewerLabel = PDFViewerLabel ()
196+ self .viewer : PDFViewerLabel = PDFViewerLabel (parent = self . scroll_area )
151197 self .viewer .setAlignment (Qt .AlignmentFlag .AlignCenter )
152198 self .scroll_area .setWidget (self .viewer )
153199
0 commit comments