7 Module implementing a grabber widget for a rectangular snapshot region. |
7 Module implementing a grabber widget for a rectangular snapshot region. |
8 """ |
8 """ |
9 |
9 |
10 from PyQt6.QtCore import pyqtSignal, Qt, QRect, QPoint, QTimer, QLocale |
10 from PyQt6.QtCore import pyqtSignal, Qt, QRect, QPoint, QTimer, QLocale |
11 from PyQt6.QtGui import ( |
11 from PyQt6.QtGui import ( |
12 QPixmap, QColor, QRegion, QPainter, QPalette, QPaintEngine, QPen, QBrush, |
12 QPixmap, |
13 QGuiApplication, QCursor |
13 QColor, |
|
14 QRegion, |
|
15 QPainter, |
|
16 QPalette, |
|
17 QPaintEngine, |
|
18 QPen, |
|
19 QBrush, |
|
20 QGuiApplication, |
|
21 QCursor, |
14 ) |
22 ) |
15 from PyQt6.QtWidgets import QWidget, QToolTip |
23 from PyQt6.QtWidgets import QWidget, QToolTip |
16 |
24 |
17 import Globals |
25 import Globals |
18 |
26 |
19 |
27 |
20 def drawRect(painter, rect, outline, fill=None): |
28 def drawRect(painter, rect, outline, fill=None): |
21 """ |
29 """ |
22 Module function to draw a rectangle with the given parameters. |
30 Module function to draw a rectangle with the given parameters. |
23 |
31 |
24 @param painter reference to the painter to be used (QPainter) |
32 @param painter reference to the painter to be used (QPainter) |
25 @param rect rectangle to be drawn (QRect) |
33 @param rect rectangle to be drawn (QRect) |
26 @param outline color of the outline (QColor) |
34 @param outline color of the outline (QColor) |
27 @param fill fill color (QColor) |
35 @param fill fill color (QColor) |
28 """ |
36 """ |
29 clip = QRegion(rect) |
37 clip = QRegion(rect) |
30 clip = clip.subtracted(QRegion(rect.adjusted(1, 1, -1, -1))) |
38 clip = clip.subtracted(QRegion(rect.adjusted(1, 1, -1, -1))) |
31 |
39 |
32 painter.save() |
40 painter.save() |
33 painter.setClipRegion(clip) |
41 painter.setClipRegion(clip) |
34 painter.setPen(Qt.PenStyle.NoPen) |
42 painter.setPen(Qt.PenStyle.NoPen) |
35 painter.setBrush(outline) |
43 painter.setBrush(outline) |
36 painter.drawRect(rect) |
44 painter.drawRect(rect) |
42 |
50 |
43 |
51 |
44 class SnapshotRegionGrabber(QWidget): |
52 class SnapshotRegionGrabber(QWidget): |
45 """ |
53 """ |
46 Class implementing a grabber widget for a rectangular snapshot region. |
54 Class implementing a grabber widget for a rectangular snapshot region. |
47 |
55 |
48 @signal grabbed(QPixmap) emitted after the region was grabbed |
56 @signal grabbed(QPixmap) emitted after the region was grabbed |
49 """ |
57 """ |
|
58 |
50 grabbed = pyqtSignal(QPixmap) |
59 grabbed = pyqtSignal(QPixmap) |
51 |
60 |
52 StrokeMask = 0 |
61 StrokeMask = 0 |
53 FillMask = 1 |
62 FillMask = 1 |
54 |
63 |
55 Rectangle = 0 |
64 Rectangle = 0 |
56 Ellipse = 1 |
65 Ellipse = 1 |
57 |
66 |
58 def __init__(self, mode=Rectangle): |
67 def __init__(self, mode=Rectangle): |
59 """ |
68 """ |
60 Constructor |
69 Constructor |
61 |
70 |
62 @param mode region grabber mode (SnapshotRegionGrabber.Rectangle or |
71 @param mode region grabber mode (SnapshotRegionGrabber.Rectangle or |
63 SnapshotRegionGrabber.Ellipse) |
72 SnapshotRegionGrabber.Ellipse) |
64 @exception ValueError raised to indicate a bad value for the 'mode' |
73 @exception ValueError raised to indicate a bad value for the 'mode' |
65 parameter |
74 parameter |
66 """ |
75 """ |
67 super().__init__( |
76 super().__init__( |
68 None, |
77 None, |
69 Qt.WindowType.X11BypassWindowManagerHint | |
78 Qt.WindowType.X11BypassWindowManagerHint |
70 Qt.WindowType.WindowStaysOnTopHint | |
79 | Qt.WindowType.WindowStaysOnTopHint |
71 Qt.WindowType.FramelessWindowHint | |
80 | Qt.WindowType.FramelessWindowHint |
72 Qt.WindowType.Tool |
81 | Qt.WindowType.Tool, |
73 ) |
82 ) |
74 |
83 |
75 if mode not in [SnapshotRegionGrabber.Rectangle, |
84 if mode not in [SnapshotRegionGrabber.Rectangle, SnapshotRegionGrabber.Ellipse]: |
76 SnapshotRegionGrabber.Ellipse]: |
|
77 raise ValueError("Bad value for 'mode' parameter.") |
85 raise ValueError("Bad value for 'mode' parameter.") |
78 self.__mode = mode |
86 self.__mode = mode |
79 |
87 |
80 self.__selection = QRect() |
88 self.__selection = QRect() |
81 self.__mouseDown = False |
89 self.__mouseDown = False |
82 self.__newSelection = False |
90 self.__newSelection = False |
83 self.__handleSize = 10 |
91 self.__handleSize = 10 |
84 self.__mouseOverHandle = None |
92 self.__mouseOverHandle = None |
85 self.__showHelp = True |
93 self.__showHelp = True |
86 self.__grabbing = False |
94 self.__grabbing = False |
87 self.__dragStartPoint = QPoint() |
95 self.__dragStartPoint = QPoint() |
88 self.__selectionBeforeDrag = QRect() |
96 self.__selectionBeforeDrag = QRect() |
89 self.__locale = QLocale() |
97 self.__locale = QLocale() |
90 |
98 |
91 # naming conventions for handles |
99 # naming conventions for handles |
92 # T top, B bottom, R Right, L left |
100 # T top, B bottom, R Right, L left |
93 # 2 letters: a corner |
101 # 2 letters: a corner |
94 # 1 letter: the handle on the middle of the corresponding side |
102 # 1 letter: the handle on the middle of the corresponding side |
95 self.__TLHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
103 self.__TLHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
98 self.__BRHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
106 self.__BRHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
99 self.__LHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
107 self.__LHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
100 self.__THandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
108 self.__THandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
101 self.__RHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
109 self.__RHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
102 self.__BHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
110 self.__BHandle = QRect(0, 0, self.__handleSize, self.__handleSize) |
103 self.__handles = [self.__TLHandle, self.__TRHandle, self.__BLHandle, |
111 self.__handles = [ |
104 self.__BRHandle, self.__LHandle, self.__THandle, |
112 self.__TLHandle, |
105 self.__RHandle, self.__BHandle] |
113 self.__TRHandle, |
|
114 self.__BLHandle, |
|
115 self.__BRHandle, |
|
116 self.__LHandle, |
|
117 self.__THandle, |
|
118 self.__RHandle, |
|
119 self.__BHandle, |
|
120 ] |
106 self.__helpTextRect = QRect() |
121 self.__helpTextRect = QRect() |
107 self.__helpText = self.tr( |
122 self.__helpText = self.tr( |
108 "Select a region using the mouse. To take the snapshot, press" |
123 "Select a region using the mouse. To take the snapshot, press" |
109 " the Enter key or double click. Press Esc to quit.") |
124 " the Enter key or double click. Press Esc to quit." |
110 |
125 ) |
|
126 |
111 self.__pixmap = QPixmap() |
127 self.__pixmap = QPixmap() |
112 |
128 |
113 self.setMouseTracking(True) |
129 self.setMouseTracking(True) |
114 |
130 |
115 QTimer.singleShot(200, self.__initialize) |
131 QTimer.singleShot(200, self.__initialize) |
116 |
132 |
117 def __initialize(self): |
133 def __initialize(self): |
118 """ |
134 """ |
119 Private slot to initialize the rest of the widget. |
135 Private slot to initialize the rest of the widget. |
120 """ |
136 """ |
121 if Globals.isMacPlatform(): |
137 if Globals.isMacPlatform(): |
122 # macOS variant |
138 # macOS variant |
123 screen = QGuiApplication.screenAt(QCursor.pos()) |
139 screen = QGuiApplication.screenAt(QCursor.pos()) |
124 geom = screen.geometry() |
140 geom = screen.geometry() |
125 self.__pixmap = screen.grabWindow( |
141 self.__pixmap = screen.grabWindow( |
126 0, geom.x(), geom.y(), geom.width(), geom.height()) |
142 0, geom.x(), geom.y(), geom.width(), geom.height() |
|
143 ) |
127 else: |
144 else: |
128 # Linux variant |
145 # Linux variant |
129 # Windows variant |
146 # Windows variant |
130 screen = QGuiApplication.screens()[0] |
147 screen = QGuiApplication.screens()[0] |
131 geom = screen.availableVirtualGeometry() |
148 geom = screen.availableVirtualGeometry() |
132 self.__pixmap = screen.grabWindow( |
149 self.__pixmap = screen.grabWindow( |
133 0, geom.x(), geom.y(), geom.width(), geom.height()) |
150 0, geom.x(), geom.y(), geom.width(), geom.height() |
|
151 ) |
134 self.resize(self.__pixmap.size()) |
152 self.resize(self.__pixmap.size()) |
135 self.move(geom.x(), geom.y()) |
153 self.move(geom.x(), geom.y()) |
136 self.setCursor(Qt.CursorShape.CrossCursor) |
154 self.setCursor(Qt.CursorShape.CrossCursor) |
137 self.show() |
155 self.show() |
138 |
156 |
139 self.grabMouse() |
157 self.grabMouse() |
140 self.grabKeyboard() |
158 self.grabKeyboard() |
141 self.activateWindow() |
159 self.activateWindow() |
142 |
160 |
143 def paintEvent(self, evt): |
161 def paintEvent(self, evt): |
144 """ |
162 """ |
145 Protected method handling paint events. |
163 Protected method handling paint events. |
146 |
164 |
147 @param evt paint event (QPaintEvent) |
165 @param evt paint event (QPaintEvent) |
148 """ |
166 """ |
149 if self.__grabbing: # grabWindow() should just get the background |
167 if self.__grabbing: # grabWindow() should just get the background |
150 return |
168 return |
151 |
169 |
152 painter = QPainter(self) |
170 painter = QPainter(self) |
153 pal = QPalette(QToolTip.palette()) |
171 pal = QPalette(QToolTip.palette()) |
154 font = QToolTip.font() |
172 font = QToolTip.font() |
155 |
173 |
156 handleColor = pal.color(QPalette.ColorGroup.Active, |
174 handleColor = pal.color( |
157 QPalette.ColorRole.Highlight) |
175 QPalette.ColorGroup.Active, QPalette.ColorRole.Highlight |
|
176 ) |
158 handleColor.setAlpha(160) |
177 handleColor.setAlpha(160) |
159 overlayColor = QColor(0, 0, 0, 160) |
178 overlayColor = QColor(0, 0, 0, 160) |
160 textColor = pal.color(QPalette.ColorGroup.Active, |
179 textColor = pal.color(QPalette.ColorGroup.Active, QPalette.ColorRole.Text) |
161 QPalette.ColorRole.Text) |
180 textBackgroundColor = pal.color( |
162 textBackgroundColor = pal.color(QPalette.ColorGroup.Active, |
181 QPalette.ColorGroup.Active, QPalette.ColorRole.Base |
163 QPalette.ColorRole.Base) |
182 ) |
164 painter.drawPixmap(0, 0, self.__pixmap) |
183 painter.drawPixmap(0, 0, self.__pixmap) |
165 painter.setFont(font) |
184 painter.setFont(font) |
166 |
185 |
167 r = QRect(self.__selection) |
186 r = QRect(self.__selection) |
168 if not self.__selection.isNull(): |
187 if not self.__selection.isNull(): |
169 grey = QRegion(self.rect()) |
188 grey = QRegion(self.rect()) |
170 if self.__mode == SnapshotRegionGrabber.Ellipse: |
189 if self.__mode == SnapshotRegionGrabber.Ellipse: |
171 reg = QRegion(r, QRegion.RegionType.Ellipse) |
190 reg = QRegion(r, QRegion.RegionType.Ellipse) |
176 painter.setPen(Qt.PenStyle.NoPen) |
195 painter.setPen(Qt.PenStyle.NoPen) |
177 painter.setBrush(overlayColor) |
196 painter.setBrush(overlayColor) |
178 painter.drawRect(self.rect()) |
197 painter.drawRect(self.rect()) |
179 painter.setClipRect(self.rect()) |
198 painter.setClipRect(self.rect()) |
180 drawRect(painter, r, handleColor) |
199 drawRect(painter, r, handleColor) |
181 |
200 |
182 if self.__showHelp: |
201 if self.__showHelp: |
183 painter.setPen(textColor) |
202 painter.setPen(textColor) |
184 painter.setBrush(textBackgroundColor) |
203 painter.setBrush(textBackgroundColor) |
185 self.__helpTextRect = painter.boundingRect( |
204 self.__helpTextRect = painter.boundingRect( |
186 self.rect().adjusted(2, 2, -2, -2), |
205 self.rect().adjusted(2, 2, -2, -2), |
187 Qt.TextFlag.TextWordWrap, self.__helpText).translated(0, 0) |
206 Qt.TextFlag.TextWordWrap, |
|
207 self.__helpText, |
|
208 ).translated(0, 0) |
188 self.__helpTextRect.adjust(-2, -2, 4, 2) |
209 self.__helpTextRect.adjust(-2, -2, 4, 2) |
189 drawRect(painter, self.__helpTextRect, textColor, |
210 drawRect(painter, self.__helpTextRect, textColor, textBackgroundColor) |
190 textBackgroundColor) |
|
191 painter.drawText( |
211 painter.drawText( |
192 self.__helpTextRect.adjusted(3, 3, -3, -3), |
212 self.__helpTextRect.adjusted(3, 3, -3, -3), |
193 Qt.TextFlag.TextWordWrap, self.__helpText) |
213 Qt.TextFlag.TextWordWrap, |
194 |
214 self.__helpText, |
|
215 ) |
|
216 |
195 if self.__selection.isNull(): |
217 if self.__selection.isNull(): |
196 return |
218 return |
197 |
219 |
198 # The grabbed region is everything which is covered by the drawn |
220 # The grabbed region is everything which is covered by the drawn |
199 # rectangles (border included). This means that there is no 0px |
221 # rectangles (border included). This means that there is no 0px |
200 # selection, since a 0px wide rectangle will always be drawn as a line. |
222 # selection, since a 0px wide rectangle will always be drawn as a line. |
201 txt = "{0}, {1} ({2} x {3})".format( |
223 txt = "{0}, {1} ({2} x {3})".format( |
202 self.__locale.toString(self.__selection.x()), |
224 self.__locale.toString(self.__selection.x()), |
203 self.__locale.toString(self.__selection.y()), |
225 self.__locale.toString(self.__selection.y()), |
204 self.__locale.toString(self.__selection.width()), |
226 self.__locale.toString(self.__selection.width()), |
205 self.__locale.toString(self.__selection.height()) |
227 self.__locale.toString(self.__selection.height()), |
206 ) |
228 ) |
207 textRect = painter.boundingRect(self.rect(), |
229 textRect = painter.boundingRect(self.rect(), Qt.AlignmentFlag.AlignLeft, txt) |
208 Qt.AlignmentFlag.AlignLeft, txt) |
|
209 boundingRect = textRect.adjusted(-4, 0, 0, 0) |
230 boundingRect = textRect.adjusted(-4, 0, 0, 0) |
210 |
231 |
211 if ( |
232 if ( |
212 textRect.width() < r.width() - 2 * self.__handleSize and |
233 textRect.width() < r.width() - 2 * self.__handleSize |
213 textRect.height() < r.height() - 2 * self.__handleSize and |
234 and textRect.height() < r.height() - 2 * self.__handleSize |
214 r.width() > 100 and |
235 and r.width() > 100 |
215 r.height() > 100 |
236 and r.height() > 100 |
216 ): |
237 ): |
217 # center, unsuitable for small selections |
238 # center, unsuitable for small selections |
218 boundingRect.moveCenter(r.center()) |
239 boundingRect.moveCenter(r.center()) |
219 textRect.moveCenter(r.center()) |
240 textRect.moveCenter(r.center()) |
220 elif ( |
241 elif ( |
221 r.y() - 3 > textRect.height() and |
242 r.y() - 3 > textRect.height() |
222 r.x() + textRect.width() < self.rect().width() |
243 and r.x() + textRect.width() < self.rect().width() |
223 ): |
244 ): |
224 # on top, left aligned |
245 # on top, left aligned |
225 boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3)) |
246 boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3)) |
226 textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3)) |
247 textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3)) |
227 elif r.x() - 3 > textRect.width(): |
248 elif r.x() - 3 > textRect.width(): |
228 # left, top aligned |
249 # left, top aligned |
229 boundingRect.moveTopRight(QPoint(r.x() - 3, r.y())) |
250 boundingRect.moveTopRight(QPoint(r.x() - 3, r.y())) |
230 textRect.moveTopRight(QPoint(r.x() - 5, r.y())) |
251 textRect.moveTopRight(QPoint(r.x() - 5, r.y())) |
231 elif ( |
252 elif ( |
232 r.bottom() + 3 + textRect.height() < self.rect().bottom() and |
253 r.bottom() + 3 + textRect.height() < self.rect().bottom() |
233 r.right() > textRect.width() |
254 and r.right() > textRect.width() |
234 ): |
255 ): |
235 # at bottom, right aligned |
256 # at bottom, right aligned |
236 boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3)) |
257 boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3)) |
237 textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3)) |
258 textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3)) |
238 elif r.right() + textRect.width() + 3 < self.rect().width(): |
259 elif r.right() + textRect.width() + 3 < self.rect().width(): |
239 # right, bottom aligned |
260 # right, bottom aligned |
240 boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom())) |
261 boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom())) |
241 textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom())) |
262 textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom())) |
242 |
263 |
243 # If the above didn't catch it, you are running on a very |
264 # If the above didn't catch it, you are running on a very |
244 # tiny screen... |
265 # tiny screen... |
245 drawRect(painter, boundingRect, textColor, textBackgroundColor) |
266 drawRect(painter, boundingRect, textColor, textBackgroundColor) |
246 painter.drawText(textRect, Qt.AlignmentFlag.AlignHCenter, txt) |
267 painter.drawText(textRect, Qt.AlignmentFlag.AlignHCenter, txt) |
247 |
268 |
248 if ( |
269 if ( |
249 (r.height() > self.__handleSize * 2 and |
270 r.height() > self.__handleSize * 2 and r.width() > self.__handleSize * 2 |
250 r.width() > self.__handleSize * 2) or |
271 ) or not self.__mouseDown: |
251 not self.__mouseDown |
|
252 ): |
|
253 self.__updateHandles() |
272 self.__updateHandles() |
254 painter.setPen(Qt.PenStyle.NoPen) |
273 painter.setPen(Qt.PenStyle.NoPen) |
255 painter.setBrush(handleColor) |
274 painter.setBrush(handleColor) |
256 painter.setClipRegion( |
275 painter.setClipRegion(self.__handleMask(SnapshotRegionGrabber.StrokeMask)) |
257 self.__handleMask(SnapshotRegionGrabber.StrokeMask)) |
|
258 painter.drawRect(self.rect()) |
276 painter.drawRect(self.rect()) |
259 handleColor.setAlpha(60) |
277 handleColor.setAlpha(60) |
260 painter.setBrush(handleColor) |
278 painter.setBrush(handleColor) |
261 painter.setClipRegion( |
279 painter.setClipRegion(self.__handleMask(SnapshotRegionGrabber.FillMask)) |
262 self.__handleMask(SnapshotRegionGrabber.FillMask)) |
|
263 painter.drawRect(self.rect()) |
280 painter.drawRect(self.rect()) |
264 |
281 |
265 def resizeEvent(self, evt): |
282 def resizeEvent(self, evt): |
266 """ |
283 """ |
267 Protected method to handle resize events. |
284 Protected method to handle resize events. |
268 |
285 |
269 @param evt resize event (QResizeEvent) |
286 @param evt resize event (QResizeEvent) |
270 """ |
287 """ |
271 if self.__selection.isNull(): |
288 if self.__selection.isNull(): |
272 return |
289 return |
273 |
290 |
274 r = QRect(self.__selection) |
291 r = QRect(self.__selection) |
275 r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect())) |
292 r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect())) |
276 r.setBottomRight(self.__limitPointToRect(r.bottomRight(), self.rect())) |
293 r.setBottomRight(self.__limitPointToRect(r.bottomRight(), self.rect())) |
277 if r.width() <= 1 or r.height() <= 1: |
294 if r.width() <= 1 or r.height() <= 1: |
278 # This just results in ugly drawing... |
295 # This just results in ugly drawing... |
279 self.__selection = QRect() |
296 self.__selection = QRect() |
280 else: |
297 else: |
281 self.__selection = self.__normalizeSelection(r) |
298 self.__selection = self.__normalizeSelection(r) |
282 |
299 |
283 def mousePressEvent(self, evt): |
300 def mousePressEvent(self, evt): |
284 """ |
301 """ |
285 Protected method to handle mouse button presses. |
302 Protected method to handle mouse button presses. |
286 |
303 |
287 @param evt mouse press event (QMouseEvent) |
304 @param evt mouse press event (QMouseEvent) |
288 """ |
305 """ |
289 self.__showHelp = not self.__helpTextRect.contains( |
306 self.__showHelp = not self.__helpTextRect.contains(evt.position().toPoint()) |
290 evt.position().toPoint()) |
|
291 if evt.button() == Qt.MouseButton.LeftButton: |
307 if evt.button() == Qt.MouseButton.LeftButton: |
292 self.__mouseDown = True |
308 self.__mouseDown = True |
293 self.__dragStartPoint = evt.position().toPoint() |
309 self.__dragStartPoint = evt.position().toPoint() |
294 self.__selectionBeforeDrag = QRect(self.__selection) |
310 self.__selectionBeforeDrag = QRect(self.__selection) |
295 if not self.__selection.contains(evt.position().toPoint()): |
311 if not self.__selection.contains(evt.position().toPoint()): |
300 elif evt.button() == Qt.MouseButton.RightButton: |
316 elif evt.button() == Qt.MouseButton.RightButton: |
301 self.__newSelection = False |
317 self.__newSelection = False |
302 self.__selection = QRect() |
318 self.__selection = QRect() |
303 self.setCursor(Qt.CursorShape.CrossCursor) |
319 self.setCursor(Qt.CursorShape.CrossCursor) |
304 self.update() |
320 self.update() |
305 |
321 |
306 def mouseMoveEvent(self, evt): |
322 def mouseMoveEvent(self, evt): |
307 """ |
323 """ |
308 Protected method to handle mouse movements. |
324 Protected method to handle mouse movements. |
309 |
325 |
310 @param evt mouse move event (QMouseEvent) |
326 @param evt mouse move event (QMouseEvent) |
311 """ |
327 """ |
312 shouldShowHelp = not self.__helpTextRect.contains( |
328 shouldShowHelp = not self.__helpTextRect.contains(evt.position().toPoint()) |
313 evt.position().toPoint()) |
|
314 if shouldShowHelp != self.__showHelp: |
329 if shouldShowHelp != self.__showHelp: |
315 self.__showHelp = shouldShowHelp |
330 self.__showHelp = shouldShowHelp |
316 self.update() |
331 self.update() |
317 |
332 |
318 if self.__mouseDown: |
333 if self.__mouseDown: |
319 if self.__newSelection: |
334 if self.__newSelection: |
320 p = evt.position().toPoint() |
335 p = evt.position().toPoint() |
321 r = self.rect() |
336 r = self.rect() |
322 self.__selection = self.__normalizeSelection( |
337 self.__selection = self.__normalizeSelection( |
323 QRect(self.__dragStartPoint, |
338 QRect(self.__dragStartPoint, self.__limitPointToRect(p, r)) |
324 self.__limitPointToRect(p, r))) |
339 ) |
325 elif self.__mouseOverHandle is None: |
340 elif self.__mouseOverHandle is None: |
326 # moving the whole selection |
341 # moving the whole selection |
327 r = self.rect().normalized() |
342 r = self.rect().normalized() |
328 s = self.__selectionBeforeDrag.normalized() |
343 s = self.__selectionBeforeDrag.normalized() |
329 p = ( |
344 p = s.topLeft() + evt.position().toPoint() - self.__dragStartPoint |
330 s.topLeft() + evt.position().toPoint() - |
345 r.setBottomRight( |
331 self.__dragStartPoint |
346 r.bottomRight() - QPoint(s.width(), s.height()) + QPoint(1, 1) |
332 ) |
347 ) |
333 r.setBottomRight( |
|
334 r.bottomRight() - QPoint(s.width(), s.height()) + |
|
335 QPoint(1, 1)) |
|
336 if not r.isNull() and r.isValid(): |
348 if not r.isNull() and r.isValid(): |
337 self.__selection.moveTo(self.__limitPointToRect(p, r)) |
349 self.__selection.moveTo(self.__limitPointToRect(p, r)) |
338 else: |
350 else: |
339 # dragging a handle |
351 # dragging a handle |
340 r = QRect(self.__selectionBeforeDrag) |
352 r = QRect(self.__selectionBeforeDrag) |
341 offset = evt.position().toPoint() - self.__dragStartPoint |
353 offset = evt.position().toPoint() - self.__dragStartPoint |
342 |
354 |
343 if self.__mouseOverHandle in [ |
355 if self.__mouseOverHandle in [ |
344 self.__TLHandle, self.__THandle, self.__TRHandle]: |
356 self.__TLHandle, |
|
357 self.__THandle, |
|
358 self.__TRHandle, |
|
359 ]: |
345 r.setTop(r.top() + offset.y()) |
360 r.setTop(r.top() + offset.y()) |
346 |
361 |
347 if self.__mouseOverHandle in [ |
362 if self.__mouseOverHandle in [ |
348 self.__TLHandle, self.__LHandle, self.__BLHandle]: |
363 self.__TLHandle, |
|
364 self.__LHandle, |
|
365 self.__BLHandle, |
|
366 ]: |
349 r.setLeft(r.left() + offset.x()) |
367 r.setLeft(r.left() + offset.x()) |
350 |
368 |
351 if self.__mouseOverHandle in [ |
369 if self.__mouseOverHandle in [ |
352 self.__BLHandle, self.__BHandle, self.__BRHandle]: |
370 self.__BLHandle, |
|
371 self.__BHandle, |
|
372 self.__BRHandle, |
|
373 ]: |
353 r.setBottom(r.bottom() + offset.y()) |
374 r.setBottom(r.bottom() + offset.y()) |
354 |
375 |
355 if self.__mouseOverHandle in [ |
376 if self.__mouseOverHandle in [ |
356 self.__TRHandle, self.__RHandle, self.__BRHandle]: |
377 self.__TRHandle, |
|
378 self.__RHandle, |
|
379 self.__BRHandle, |
|
380 ]: |
357 r.setRight(r.right() + offset.x()) |
381 r.setRight(r.right() + offset.x()) |
358 |
382 |
359 r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect())) |
383 r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect())) |
360 r.setBottomRight( |
384 r.setBottomRight(self.__limitPointToRect(r.bottomRight(), self.rect())) |
361 self.__limitPointToRect(r.bottomRight(), self.rect())) |
|
362 self.__selection = self.__normalizeSelection(r) |
385 self.__selection = self.__normalizeSelection(r) |
363 |
386 |
364 self.update() |
387 self.update() |
365 else: |
388 else: |
366 if self.__selection.isNull(): |
389 if self.__selection.isNull(): |
367 return |
390 return |
368 |
391 |
369 found = False |
392 found = False |
370 for r in self.__handles: |
393 for r in self.__handles: |
371 if r.contains(evt.position().toPoint()): |
394 if r.contains(evt.position().toPoint()): |
372 self.__mouseOverHandle = r |
395 self.__mouseOverHandle = r |
373 found = True |
396 found = True |
374 break |
397 break |
375 |
398 |
376 if not found: |
399 if not found: |
377 self.__mouseOverHandle = None |
400 self.__mouseOverHandle = None |
378 if self.__selection.contains(evt.position().toPoint()): |
401 if self.__selection.contains(evt.position().toPoint()): |
379 self.setCursor(Qt.CursorShape.OpenHandCursor) |
402 self.setCursor(Qt.CursorShape.OpenHandCursor) |
380 else: |
403 else: |
381 self.setCursor(Qt.CursorShape.CrossCursor) |
404 self.setCursor(Qt.CursorShape.CrossCursor) |
382 else: |
405 else: |
383 if self.__mouseOverHandle in [self.__TLHandle, |
406 if self.__mouseOverHandle in [self.__TLHandle, self.__BRHandle]: |
384 self.__BRHandle]: |
|
385 self.setCursor(Qt.CursorShape.SizeFDiagCursor) |
407 self.setCursor(Qt.CursorShape.SizeFDiagCursor) |
386 elif self.__mouseOverHandle in [self.__TRHandle, |
408 elif self.__mouseOverHandle in [self.__TRHandle, self.__BLHandle]: |
387 self.__BLHandle]: |
|
388 self.setCursor(Qt.CursorShape.SizeBDiagCursor) |
409 self.setCursor(Qt.CursorShape.SizeBDiagCursor) |
389 elif self.__mouseOverHandle in [self.__LHandle, |
410 elif self.__mouseOverHandle in [self.__LHandle, self.__RHandle]: |
390 self.__RHandle]: |
|
391 self.setCursor(Qt.CursorShape.SizeHorCursor) |
411 self.setCursor(Qt.CursorShape.SizeHorCursor) |
392 elif self.__mouseOverHandle in [self.__THandle, |
412 elif self.__mouseOverHandle in [self.__THandle, self.__BHandle]: |
393 self.__BHandle]: |
|
394 self.setCursor(Qt.CursorShape.SizeVerCursor) |
413 self.setCursor(Qt.CursorShape.SizeVerCursor) |
395 |
414 |
396 def mouseReleaseEvent(self, evt): |
415 def mouseReleaseEvent(self, evt): |
397 """ |
416 """ |
398 Protected method to handle mouse button releases. |
417 Protected method to handle mouse button releases. |
399 |
418 |
400 @param evt mouse release event (QMouseEvent) |
419 @param evt mouse release event (QMouseEvent) |
401 """ |
420 """ |
402 self.__mouseDown = False |
421 self.__mouseDown = False |
403 self.__newSelection = False |
422 self.__newSelection = False |
404 if ( |
423 if self.__mouseOverHandle is None and self.__selection.contains( |
405 self.__mouseOverHandle is None and |
424 evt.position().toPoint() |
406 self.__selection.contains(evt.position().toPoint()) |
|
407 ): |
425 ): |
408 self.setCursor(Qt.CursorShape.OpenHandCursor) |
426 self.setCursor(Qt.CursorShape.OpenHandCursor) |
409 self.update() |
427 self.update() |
410 |
428 |
411 def mouseDoubleClickEvent(self, evt): |
429 def mouseDoubleClickEvent(self, evt): |
412 """ |
430 """ |
413 Protected method to handle mouse double clicks. |
431 Protected method to handle mouse double clicks. |
414 |
432 |
415 @param evt mouse double click event (QMouseEvent) |
433 @param evt mouse double click event (QMouseEvent) |
416 """ |
434 """ |
417 self.__grabRect() |
435 self.__grabRect() |
418 |
436 |
419 def keyPressEvent(self, evt): |
437 def keyPressEvent(self, evt): |
420 """ |
438 """ |
421 Protected method to handle key presses. |
439 Protected method to handle key presses. |
422 |
440 |
423 @param evt key press event (QKeyEvent) |
441 @param evt key press event (QKeyEvent) |
424 """ |
442 """ |
425 if evt.key() == Qt.Key.Key_Escape: |
443 if evt.key() == Qt.Key.Key_Escape: |
426 self.grabbed.emit(QPixmap()) |
444 self.grabbed.emit(QPixmap()) |
427 elif evt.key() in [Qt.Key.Key_Enter, Qt.Key.Key_Return]: |
445 elif evt.key() in [Qt.Key.Key_Enter, Qt.Key.Key_Return]: |
428 self.__grabRect() |
446 self.__grabRect() |
429 else: |
447 else: |
430 evt.ignore() |
448 evt.ignore() |
431 |
449 |
432 def __updateHandles(self): |
450 def __updateHandles(self): |
433 """ |
451 """ |
434 Private method to update the handles. |
452 Private method to update the handles. |
435 """ |
453 """ |
436 r = QRect(self.__selection) |
454 r = QRect(self.__selection) |
437 s2 = self.__handleSize // 2 |
455 s2 = self.__handleSize // 2 |
438 |
456 |
439 self.__TLHandle.moveTopLeft(r.topLeft()) |
457 self.__TLHandle.moveTopLeft(r.topLeft()) |
440 self.__TRHandle.moveTopRight(r.topRight()) |
458 self.__TRHandle.moveTopRight(r.topRight()) |
441 self.__BLHandle.moveBottomLeft(r.bottomLeft()) |
459 self.__BLHandle.moveBottomLeft(r.bottomLeft()) |
442 self.__BRHandle.moveBottomRight(r.bottomRight()) |
460 self.__BRHandle.moveBottomRight(r.bottomRight()) |
443 |
461 |
444 self.__LHandle.moveTopLeft( |
462 self.__LHandle.moveTopLeft(QPoint(r.x(), r.y() + r.height() // 2 - s2)) |
445 QPoint(r.x(), r.y() + r.height() // 2 - s2)) |
463 self.__THandle.moveTopLeft(QPoint(r.x() + r.width() // 2 - s2, r.y())) |
446 self.__THandle.moveTopLeft( |
464 self.__RHandle.moveTopRight(QPoint(r.right(), r.y() + r.height() // 2 - s2)) |
447 QPoint(r.x() + r.width() // 2 - s2, r.y())) |
465 self.__BHandle.moveBottomLeft(QPoint(r.x() + r.width() // 2 - s2, r.bottom())) |
448 self.__RHandle.moveTopRight( |
466 |
449 QPoint(r.right(), r.y() + r.height() // 2 - s2)) |
|
450 self.__BHandle.moveBottomLeft( |
|
451 QPoint(r.x() + r.width() // 2 - s2, r.bottom())) |
|
452 |
|
453 def __handleMask(self, maskType): |
467 def __handleMask(self, maskType): |
454 """ |
468 """ |
455 Private method to calculate the handle mask. |
469 Private method to calculate the handle mask. |
456 |
470 |
457 @param maskType type of the mask to be used |
471 @param maskType type of the mask to be used |
458 (SnapshotRegionGrabber.FillMask or |
472 (SnapshotRegionGrabber.FillMask or |
459 SnapshotRegionGrabber.StrokeMask) |
473 SnapshotRegionGrabber.StrokeMask) |
460 @return calculated mask (QRegion) |
474 @return calculated mask (QRegion) |
461 """ |
475 """ |
508 top = rect.top() |
522 top = rect.top() |
509 height = rect.height() |
523 height = rect.height() |
510 rect.setTop(top + height - 1) |
524 rect.setTop(top + height - 1) |
511 rect.setBottom(top) |
525 rect.setBottom(top) |
512 return rect |
526 return rect |
513 |
527 |
514 def __grabRect(self): |
528 def __grabRect(self): |
515 """ |
529 """ |
516 Private method to grab the selected rectangle (i.e. do the snapshot). |
530 Private method to grab the selected rectangle (i.e. do the snapshot). |
517 """ |
531 """ |
518 if self.__mode == SnapshotRegionGrabber.Ellipse: |
532 if self.__mode == SnapshotRegionGrabber.Ellipse: |
519 ell = QRegion(self.__selection, QRegion.RegionType.Ellipse) |
533 ell = QRegion(self.__selection, QRegion.RegionType.Ellipse) |
520 if not ell.isEmpty(): |
534 if not ell.isEmpty(): |
521 self.__grabbing = True |
535 self.__grabbing = True |
522 |
536 |
523 xOffset = self.__pixmap.rect().x() - ell.boundingRect().x() |
537 xOffset = self.__pixmap.rect().x() - ell.boundingRect().x() |
524 yOffset = self.__pixmap.rect().y() - ell.boundingRect().y() |
538 yOffset = self.__pixmap.rect().y() - ell.boundingRect().y() |
525 translatedEll = ell.translated(xOffset, yOffset) |
539 translatedEll = ell.translated(xOffset, yOffset) |
526 |
540 |
527 pixmap2 = QPixmap(ell.boundingRect().size()) |
541 pixmap2 = QPixmap(ell.boundingRect().size()) |
528 pixmap2.fill(Qt.GlobalColor.transparent) |
542 pixmap2.fill(Qt.GlobalColor.transparent) |
529 |
543 |
530 pt = QPainter() |
544 pt = QPainter() |
531 pt.begin(pixmap2) |
545 pt.begin(pixmap2) |
532 if pt.paintEngine().hasFeature( |
546 if pt.paintEngine().hasFeature( |
533 QPaintEngine.PaintEngineFeature.PorterDuff |
547 QPaintEngine.PaintEngineFeature.PorterDuff |
534 ): |
548 ): |
535 pt.setRenderHints( |
549 pt.setRenderHints( |
536 QPainter.RenderHint.Antialiasing | |
550 QPainter.RenderHint.Antialiasing |
537 QPainter.RenderHint.SmoothPixmapTransform, |
551 | QPainter.RenderHint.SmoothPixmapTransform, |
538 True) |
552 True, |
|
553 ) |
539 pt.setBrush(Qt.GlobalColor.black) |
554 pt.setBrush(Qt.GlobalColor.black) |
540 pt.setPen(QPen(QBrush(Qt.GlobalColor.black), 0.5)) |
555 pt.setPen(QPen(QBrush(Qt.GlobalColor.black), 0.5)) |
541 pt.drawEllipse(translatedEll.boundingRect()) |
556 pt.drawEllipse(translatedEll.boundingRect()) |
542 pt.setCompositionMode( |
557 pt.setCompositionMode( |
543 QPainter.CompositionMode.CompositionMode_SourceIn) |
558 QPainter.CompositionMode.CompositionMode_SourceIn |
|
559 ) |
544 else: |
560 else: |
545 pt.setClipRegion(translatedEll) |
561 pt.setClipRegion(translatedEll) |
546 pt.setCompositionMode( |
562 pt.setCompositionMode( |
547 QPainter.CompositionMode.CompositionMode_Source) |
563 QPainter.CompositionMode.CompositionMode_Source |
548 |
564 ) |
549 pt.drawPixmap(pixmap2.rect(), self.__pixmap, |
565 |
550 ell.boundingRect()) |
566 pt.drawPixmap(pixmap2.rect(), self.__pixmap, ell.boundingRect()) |
551 pt.end() |
567 pt.end() |
552 |
568 |
553 self.grabbed.emit(pixmap2) |
569 self.grabbed.emit(pixmap2) |
554 else: |
570 else: |
555 r = QRect(self.__selection) |
571 r = QRect(self.__selection) |
556 if not r.isNull() and r.isValid(): |
572 if not r.isNull() and r.isValid(): |
557 self.__grabbing = True |
573 self.__grabbing = True |