Skip to content

Commit 2cacdb8

Browse files
committed
Enable dragging the rectangular selection
This makes it easier to reposition an existing selection without having to recreate it entirely from scratch. Partially addresses #25
1 parent 741cd11 commit 2cacdb8

File tree

1 file changed

+56
-10
lines changed

1 file changed

+56
-10
lines changed

app.py

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@
3535
)
3636
from PySide6.QtGui import (
3737
QPixmap, QPainter, QPen, QColor, QFont, QMouseEvent, QPaintEvent,
38-
QCloseEvent,
38+
QCloseEvent, QCursor,
3939
)
4040
from 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.
4546
PREVIEW_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

Comments
 (0)