--- a/src/eric7/Snapshot/SnapshotRegionGrabber.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Snapshot/SnapshotRegionGrabber.py Wed Jul 13 14:55:47 2022 +0200 @@ -9,8 +9,16 @@ from PyQt6.QtCore import pyqtSignal, Qt, QRect, QPoint, QTimer, QLocale from PyQt6.QtGui import ( - QPixmap, QColor, QRegion, QPainter, QPalette, QPaintEngine, QPen, QBrush, - QGuiApplication, QCursor + QPixmap, + QColor, + QRegion, + QPainter, + QPalette, + QPaintEngine, + QPen, + QBrush, + QGuiApplication, + QCursor, ) from PyQt6.QtWidgets import QWidget, QToolTip @@ -20,7 +28,7 @@ def drawRect(painter, rect, outline, fill=None): """ Module function to draw a rectangle with the given parameters. - + @param painter reference to the painter to be used (QPainter) @param rect rectangle to be drawn (QRect) @param outline color of the outline (QColor) @@ -28,7 +36,7 @@ """ clip = QRegion(rect) clip = clip.subtracted(QRegion(rect.adjusted(1, 1, -1, -1))) - + painter.save() painter.setClipRegion(clip) painter.setPen(Qt.PenStyle.NoPen) @@ -44,21 +52,22 @@ class SnapshotRegionGrabber(QWidget): """ Class implementing a grabber widget for a rectangular snapshot region. - + @signal grabbed(QPixmap) emitted after the region was grabbed """ + grabbed = pyqtSignal(QPixmap) - + StrokeMask = 0 FillMask = 1 - + Rectangle = 0 Ellipse = 1 - + def __init__(self, mode=Rectangle): """ Constructor - + @param mode region grabber mode (SnapshotRegionGrabber.Rectangle or SnapshotRegionGrabber.Ellipse) @exception ValueError raised to indicate a bad value for the 'mode' @@ -66,17 +75,16 @@ """ super().__init__( None, - Qt.WindowType.X11BypassWindowManagerHint | - Qt.WindowType.WindowStaysOnTopHint | - Qt.WindowType.FramelessWindowHint | - Qt.WindowType.Tool + Qt.WindowType.X11BypassWindowManagerHint + | Qt.WindowType.WindowStaysOnTopHint + | Qt.WindowType.FramelessWindowHint + | Qt.WindowType.Tool, ) - - if mode not in [SnapshotRegionGrabber.Rectangle, - SnapshotRegionGrabber.Ellipse]: + + if mode not in [SnapshotRegionGrabber.Rectangle, SnapshotRegionGrabber.Ellipse]: raise ValueError("Bad value for 'mode' parameter.") self.__mode = mode - + self.__selection = QRect() self.__mouseDown = False self.__newSelection = False @@ -87,7 +95,7 @@ self.__dragStartPoint = QPoint() self.__selectionBeforeDrag = QRect() self.__locale = QLocale() - + # naming conventions for handles # T top, B bottom, R Right, L left # 2 letters: a corner @@ -100,20 +108,28 @@ self.__THandle = QRect(0, 0, self.__handleSize, self.__handleSize) self.__RHandle = QRect(0, 0, self.__handleSize, self.__handleSize) self.__BHandle = QRect(0, 0, self.__handleSize, self.__handleSize) - self.__handles = [self.__TLHandle, self.__TRHandle, self.__BLHandle, - self.__BRHandle, self.__LHandle, self.__THandle, - self.__RHandle, self.__BHandle] + self.__handles = [ + self.__TLHandle, + self.__TRHandle, + self.__BLHandle, + self.__BRHandle, + self.__LHandle, + self.__THandle, + self.__RHandle, + self.__BHandle, + ] self.__helpTextRect = QRect() self.__helpText = self.tr( "Select a region using the mouse. To take the snapshot, press" - " the Enter key or double click. Press Esc to quit.") - + " the Enter key or double click. Press Esc to quit." + ) + self.__pixmap = QPixmap() - + self.setMouseTracking(True) - + QTimer.singleShot(200, self.__initialize) - + def __initialize(self): """ Private slot to initialize the rest of the widget. @@ -123,14 +139,16 @@ screen = QGuiApplication.screenAt(QCursor.pos()) geom = screen.geometry() self.__pixmap = screen.grabWindow( - 0, geom.x(), geom.y(), geom.width(), geom.height()) + 0, geom.x(), geom.y(), geom.width(), geom.height() + ) else: # Linux variant # Windows variant screen = QGuiApplication.screens()[0] geom = screen.availableVirtualGeometry() self.__pixmap = screen.grabWindow( - 0, geom.x(), geom.y(), geom.width(), geom.height()) + 0, geom.x(), geom.y(), geom.width(), geom.height() + ) self.resize(self.__pixmap.size()) self.move(geom.x(), geom.y()) self.setCursor(Qt.CursorShape.CrossCursor) @@ -139,31 +157,32 @@ self.grabMouse() self.grabKeyboard() self.activateWindow() - + def paintEvent(self, evt): """ Protected method handling paint events. - + @param evt paint event (QPaintEvent) """ - if self.__grabbing: # grabWindow() should just get the background + if self.__grabbing: # grabWindow() should just get the background return - + painter = QPainter(self) pal = QPalette(QToolTip.palette()) font = QToolTip.font() - - handleColor = pal.color(QPalette.ColorGroup.Active, - QPalette.ColorRole.Highlight) + + handleColor = pal.color( + QPalette.ColorGroup.Active, QPalette.ColorRole.Highlight + ) handleColor.setAlpha(160) overlayColor = QColor(0, 0, 0, 160) - textColor = pal.color(QPalette.ColorGroup.Active, - QPalette.ColorRole.Text) - textBackgroundColor = pal.color(QPalette.ColorGroup.Active, - QPalette.ColorRole.Base) + textColor = pal.color(QPalette.ColorGroup.Active, QPalette.ColorRole.Text) + textBackgroundColor = pal.color( + QPalette.ColorGroup.Active, QPalette.ColorRole.Base + ) painter.drawPixmap(0, 0, self.__pixmap) painter.setFont(font) - + r = QRect(self.__selection) if not self.__selection.isNull(): grey = QRegion(self.rect()) @@ -178,23 +197,26 @@ painter.drawRect(self.rect()) painter.setClipRect(self.rect()) drawRect(painter, r, handleColor) - + if self.__showHelp: painter.setPen(textColor) painter.setBrush(textBackgroundColor) self.__helpTextRect = painter.boundingRect( self.rect().adjusted(2, 2, -2, -2), - Qt.TextFlag.TextWordWrap, self.__helpText).translated(0, 0) + Qt.TextFlag.TextWordWrap, + self.__helpText, + ).translated(0, 0) self.__helpTextRect.adjust(-2, -2, 4, 2) - drawRect(painter, self.__helpTextRect, textColor, - textBackgroundColor) + drawRect(painter, self.__helpTextRect, textColor, textBackgroundColor) painter.drawText( self.__helpTextRect.adjusted(3, 3, -3, -3), - Qt.TextFlag.TextWordWrap, self.__helpText) - + Qt.TextFlag.TextWordWrap, + self.__helpText, + ) + if self.__selection.isNull(): return - + # The grabbed region is everything which is covered by the drawn # rectangles (border included). This means that there is no 0px # selection, since a 0px wide rectangle will always be drawn as a line. @@ -202,24 +224,23 @@ self.__locale.toString(self.__selection.x()), self.__locale.toString(self.__selection.y()), self.__locale.toString(self.__selection.width()), - self.__locale.toString(self.__selection.height()) + self.__locale.toString(self.__selection.height()), ) - textRect = painter.boundingRect(self.rect(), - Qt.AlignmentFlag.AlignLeft, txt) + textRect = painter.boundingRect(self.rect(), Qt.AlignmentFlag.AlignLeft, txt) boundingRect = textRect.adjusted(-4, 0, 0, 0) - + if ( - textRect.width() < r.width() - 2 * self.__handleSize and - textRect.height() < r.height() - 2 * self.__handleSize and - r.width() > 100 and - r.height() > 100 + textRect.width() < r.width() - 2 * self.__handleSize + and textRect.height() < r.height() - 2 * self.__handleSize + and r.width() > 100 + and r.height() > 100 ): # center, unsuitable for small selections boundingRect.moveCenter(r.center()) textRect.moveCenter(r.center()) elif ( - r.y() - 3 > textRect.height() and - r.x() + textRect.width() < self.rect().width() + r.y() - 3 > textRect.height() + and r.x() + textRect.width() < self.rect().width() ): # on top, left aligned boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3)) @@ -229,8 +250,8 @@ boundingRect.moveTopRight(QPoint(r.x() - 3, r.y())) textRect.moveTopRight(QPoint(r.x() - 5, r.y())) elif ( - r.bottom() + 3 + textRect.height() < self.rect().bottom() and - r.right() > textRect.width() + r.bottom() + 3 + textRect.height() < self.rect().bottom() + and r.right() > textRect.width() ): # at bottom, right aligned boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3)) @@ -239,38 +260,34 @@ # right, bottom aligned boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom())) textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom())) - + # If the above didn't catch it, you are running on a very # tiny screen... drawRect(painter, boundingRect, textColor, textBackgroundColor) painter.drawText(textRect, Qt.AlignmentFlag.AlignHCenter, txt) - + if ( - (r.height() > self.__handleSize * 2 and - r.width() > self.__handleSize * 2) or - not self.__mouseDown - ): + r.height() > self.__handleSize * 2 and r.width() > self.__handleSize * 2 + ) or not self.__mouseDown: self.__updateHandles() painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(handleColor) - painter.setClipRegion( - self.__handleMask(SnapshotRegionGrabber.StrokeMask)) + painter.setClipRegion(self.__handleMask(SnapshotRegionGrabber.StrokeMask)) painter.drawRect(self.rect()) handleColor.setAlpha(60) painter.setBrush(handleColor) - painter.setClipRegion( - self.__handleMask(SnapshotRegionGrabber.FillMask)) + painter.setClipRegion(self.__handleMask(SnapshotRegionGrabber.FillMask)) painter.drawRect(self.rect()) - + def resizeEvent(self, evt): """ Protected method to handle resize events. - + @param evt resize event (QResizeEvent) """ if self.__selection.isNull(): return - + r = QRect(self.__selection) r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect())) r.setBottomRight(self.__limitPointToRect(r.bottomRight(), self.rect())) @@ -279,15 +296,14 @@ self.__selection = QRect() else: self.__selection = self.__normalizeSelection(r) - + def mousePressEvent(self, evt): """ Protected method to handle mouse button presses. - + @param evt mouse press event (QMouseEvent) """ - self.__showHelp = not self.__helpTextRect.contains( - evt.position().toPoint()) + self.__showHelp = not self.__helpTextRect.contains(evt.position().toPoint()) if evt.button() == Qt.MouseButton.LeftButton: self.__mouseDown = True self.__dragStartPoint = evt.position().toPoint() @@ -302,77 +318,84 @@ self.__selection = QRect() self.setCursor(Qt.CursorShape.CrossCursor) self.update() - + def mouseMoveEvent(self, evt): """ Protected method to handle mouse movements. - + @param evt mouse move event (QMouseEvent) """ - shouldShowHelp = not self.__helpTextRect.contains( - evt.position().toPoint()) + shouldShowHelp = not self.__helpTextRect.contains(evt.position().toPoint()) if shouldShowHelp != self.__showHelp: self.__showHelp = shouldShowHelp self.update() - + if self.__mouseDown: if self.__newSelection: p = evt.position().toPoint() r = self.rect() self.__selection = self.__normalizeSelection( - QRect(self.__dragStartPoint, - self.__limitPointToRect(p, r))) + QRect(self.__dragStartPoint, self.__limitPointToRect(p, r)) + ) elif self.__mouseOverHandle is None: # moving the whole selection r = self.rect().normalized() s = self.__selectionBeforeDrag.normalized() - p = ( - s.topLeft() + evt.position().toPoint() - - self.__dragStartPoint + p = s.topLeft() + evt.position().toPoint() - self.__dragStartPoint + r.setBottomRight( + r.bottomRight() - QPoint(s.width(), s.height()) + QPoint(1, 1) ) - r.setBottomRight( - r.bottomRight() - QPoint(s.width(), s.height()) + - QPoint(1, 1)) if not r.isNull() and r.isValid(): self.__selection.moveTo(self.__limitPointToRect(p, r)) else: # dragging a handle r = QRect(self.__selectionBeforeDrag) offset = evt.position().toPoint() - self.__dragStartPoint - + if self.__mouseOverHandle in [ - self.__TLHandle, self.__THandle, self.__TRHandle]: + self.__TLHandle, + self.__THandle, + self.__TRHandle, + ]: r.setTop(r.top() + offset.y()) - + if self.__mouseOverHandle in [ - self.__TLHandle, self.__LHandle, self.__BLHandle]: + self.__TLHandle, + self.__LHandle, + self.__BLHandle, + ]: r.setLeft(r.left() + offset.x()) - + if self.__mouseOverHandle in [ - self.__BLHandle, self.__BHandle, self.__BRHandle]: + self.__BLHandle, + self.__BHandle, + self.__BRHandle, + ]: r.setBottom(r.bottom() + offset.y()) - + if self.__mouseOverHandle in [ - self.__TRHandle, self.__RHandle, self.__BRHandle]: + self.__TRHandle, + self.__RHandle, + self.__BRHandle, + ]: r.setRight(r.right() + offset.x()) - + r.setTopLeft(self.__limitPointToRect(r.topLeft(), self.rect())) - r.setBottomRight( - self.__limitPointToRect(r.bottomRight(), self.rect())) + r.setBottomRight(self.__limitPointToRect(r.bottomRight(), self.rect())) self.__selection = self.__normalizeSelection(r) - + self.update() else: if self.__selection.isNull(): return - + found = False for r in self.__handles: if r.contains(evt.position().toPoint()): self.__mouseOverHandle = r found = True break - + if not found: self.__mouseOverHandle = None if self.__selection.contains(evt.position().toPoint()): @@ -380,46 +403,41 @@ else: self.setCursor(Qt.CursorShape.CrossCursor) else: - if self.__mouseOverHandle in [self.__TLHandle, - self.__BRHandle]: + if self.__mouseOverHandle in [self.__TLHandle, self.__BRHandle]: self.setCursor(Qt.CursorShape.SizeFDiagCursor) - elif self.__mouseOverHandle in [self.__TRHandle, - self.__BLHandle]: + elif self.__mouseOverHandle in [self.__TRHandle, self.__BLHandle]: self.setCursor(Qt.CursorShape.SizeBDiagCursor) - elif self.__mouseOverHandle in [self.__LHandle, - self.__RHandle]: + elif self.__mouseOverHandle in [self.__LHandle, self.__RHandle]: self.setCursor(Qt.CursorShape.SizeHorCursor) - elif self.__mouseOverHandle in [self.__THandle, - self.__BHandle]: + elif self.__mouseOverHandle in [self.__THandle, self.__BHandle]: self.setCursor(Qt.CursorShape.SizeVerCursor) - + def mouseReleaseEvent(self, evt): """ Protected method to handle mouse button releases. - + @param evt mouse release event (QMouseEvent) """ self.__mouseDown = False self.__newSelection = False - if ( - self.__mouseOverHandle is None and - self.__selection.contains(evt.position().toPoint()) + if self.__mouseOverHandle is None and self.__selection.contains( + evt.position().toPoint() ): self.setCursor(Qt.CursorShape.OpenHandCursor) self.update() - + def mouseDoubleClickEvent(self, evt): """ Protected method to handle mouse double clicks. - + @param evt mouse double click event (QMouseEvent) """ self.__grabRect() - + def keyPressEvent(self, evt): """ Protected method to handle key presses. - + @param evt key press event (QKeyEvent) """ if evt.key() == Qt.Key.Key_Escape: @@ -428,32 +446,28 @@ self.__grabRect() else: evt.ignore() - + def __updateHandles(self): """ Private method to update the handles. """ r = QRect(self.__selection) s2 = self.__handleSize // 2 - + self.__TLHandle.moveTopLeft(r.topLeft()) self.__TRHandle.moveTopRight(r.topRight()) self.__BLHandle.moveBottomLeft(r.bottomLeft()) self.__BRHandle.moveBottomRight(r.bottomRight()) - - self.__LHandle.moveTopLeft( - QPoint(r.x(), r.y() + r.height() // 2 - s2)) - self.__THandle.moveTopLeft( - QPoint(r.x() + r.width() // 2 - s2, r.y())) - self.__RHandle.moveTopRight( - QPoint(r.right(), r.y() + r.height() // 2 - s2)) - self.__BHandle.moveBottomLeft( - QPoint(r.x() + r.width() // 2 - s2, r.bottom())) - + + self.__LHandle.moveTopLeft(QPoint(r.x(), r.y() + r.height() // 2 - s2)) + self.__THandle.moveTopLeft(QPoint(r.x() + r.width() // 2 - s2, r.y())) + self.__RHandle.moveTopRight(QPoint(r.right(), r.y() + r.height() // 2 - s2)) + self.__BHandle.moveBottomLeft(QPoint(r.x() + r.width() // 2 - s2, r.bottom())) + def __handleMask(self, maskType): """ Private method to calculate the handle mask. - + @param maskType type of the mask to be used (SnapshotRegionGrabber.FillMask or SnapshotRegionGrabber.StrokeMask) @@ -467,11 +481,11 @@ else: mask += QRegion(rect.adjusted(1, 1, -1, -1)) return mask - + def __limitPointToRect(self, point, rect): """ Private method to limit the given point to the given rectangle. - + @param point point to be limited (QPoint) @param rect rectangle the point shall be limited to (QRect) @return limited point (QPoint) @@ -490,11 +504,11 @@ else: q.setY(rect.bottom()) return q - + def __normalizeSelection(self, sel): """ Private method to normalize the given selection. - + @param sel selection to be normalized (QRect) @return normalized selection (QRect) """ @@ -510,7 +524,7 @@ rect.setTop(top + height - 1) rect.setBottom(top) return rect - + def __grabRect(self): """ Private method to grab the selected rectangle (i.e. do the snapshot). @@ -519,37 +533,39 @@ ell = QRegion(self.__selection, QRegion.RegionType.Ellipse) if not ell.isEmpty(): self.__grabbing = True - + xOffset = self.__pixmap.rect().x() - ell.boundingRect().x() yOffset = self.__pixmap.rect().y() - ell.boundingRect().y() translatedEll = ell.translated(xOffset, yOffset) - + pixmap2 = QPixmap(ell.boundingRect().size()) pixmap2.fill(Qt.GlobalColor.transparent) - + pt = QPainter() pt.begin(pixmap2) if pt.paintEngine().hasFeature( QPaintEngine.PaintEngineFeature.PorterDuff ): pt.setRenderHints( - QPainter.RenderHint.Antialiasing | - QPainter.RenderHint.SmoothPixmapTransform, - True) + QPainter.RenderHint.Antialiasing + | QPainter.RenderHint.SmoothPixmapTransform, + True, + ) pt.setBrush(Qt.GlobalColor.black) pt.setPen(QPen(QBrush(Qt.GlobalColor.black), 0.5)) pt.drawEllipse(translatedEll.boundingRect()) pt.setCompositionMode( - QPainter.CompositionMode.CompositionMode_SourceIn) + QPainter.CompositionMode.CompositionMode_SourceIn + ) else: pt.setClipRegion(translatedEll) pt.setCompositionMode( - QPainter.CompositionMode.CompositionMode_Source) - - pt.drawPixmap(pixmap2.rect(), self.__pixmap, - ell.boundingRect()) + QPainter.CompositionMode.CompositionMode_Source + ) + + pt.drawPixmap(pixmap2.rect(), self.__pixmap, ell.boundingRect()) pt.end() - + self.grabbed.emit(pixmap2) else: r = QRect(self.__selection)