From fb4a6a1363cceff4ea799b54b3ba2584955092bc Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sat, 13 Jan 2024 02:56:18 +0100 Subject: [PATCH 01/36] Add dialogs and better ImageViewer --- .../Grab.app/Resources/QtImageViewer.py | 616 ++++++++++++++++++ .../Grab.app/Resources/dialog_screen_grab.py | 27 + .../Grab.app/Resources/dialog_screen_grab.ui | 214 ++++++ .../Resources/dialog_screen_grab_ui.py | 93 +++ .../Resources/dialog_timed_screen_grab.py | 36 + .../Resources/dialog_timed_screen_grab.ui | 235 +++++++ .../Resources/dialog_timed_screen_grab_ui.py | 106 +++ Under Construction/Grab.app/Resources/grab.py | 203 +++++- .../Grab.app/Resources/main_window.ui | 249 ++++++- .../Grab.app/Resources/main_window_ui.py | 136 +++- .../Resources/widget_screenshot_preview.py | 88 +-- .../Resources/widget_transparent_window.py | 43 ++ 12 files changed, 1968 insertions(+), 78 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/QtImageViewer.py create mode 100644 Under Construction/Grab.app/Resources/dialog_screen_grab.py create mode 100644 Under Construction/Grab.app/Resources/dialog_screen_grab.ui create mode 100644 Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py create mode 100644 Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py create mode 100644 Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui create mode 100644 Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py create mode 100644 Under Construction/Grab.app/Resources/widget_transparent_window.py diff --git a/Under Construction/Grab.app/Resources/QtImageViewer.py b/Under Construction/Grab.app/Resources/QtImageViewer.py new file mode 100644 index 00000000..beffc1c5 --- /dev/null +++ b/Under Construction/Grab.app/Resources/QtImageViewer.py @@ -0,0 +1,616 @@ +""" QtImageViewer.py: PyQt image viewer widget based on QGraphicsView with mouse zooming/panning and ROIs. + +""" + +import os.path + +try: + from PyQt6.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize + from PyQt6.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen + from PyQt6.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QSizePolicy, \ + QGraphicsItem, QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsLineItem, QGraphicsPolygonItem +except ImportError: + try: + from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize + from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen, QGuiApplication + from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QSizePolicy, \ + QGraphicsItem, QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsLineItem, QGraphicsPolygonItem + except ImportError: + raise ImportError("Requires PyQt (version 5 or 6)") + +# numpy is optional: only needed if you want to display numpy 2d arrays as images. +try: + import numpy as np +except ImportError: + np = None + +# qimage2ndarray is optional: useful for displaying numpy 2d arrays as images. +# !!! qimage2ndarray requires PyQt5. +# Some custom code in the viewer appears to handle the conversion from numpy 2d arrays, +# so qimage2ndarray probably is not needed anymore. I've left it here just in case. +try: + import qimage2ndarray +except ImportError: + qimage2ndarray = None + +__author__ = ["Marcel Goldschen-Ohm ", + "Tuxa alias Hierosme"] +__version__ = '2.0.0' + + +class QtImageViewer(QGraphicsView): + """ PyQt image viewer widget based on QGraphicsView with mouse zooming/panning and ROIs. + + Image File: + ----------- + Use the open("path/to/file") method to load an image file into the viewer. + Calling open() without a file argument will popup a file selection dialog. + + Image: + ------ + Use the setImage(im) method to set the image data in the viewer. + - im can be a QImage, QPixmap, or NumPy 2D array (the later requires the package qimage2ndarray). + For display in the QGraphicsView the image will be converted to a QPixmap. + + Some useful image format conversion utilities: + qimage2ndarray: NumPy ndarray <==> QImage (https://github.com/hmeine/qimage2ndarray) + ImageQt: PIL Image <==> QImage (https://github.com/python-pillow/Pillow/blob/master/PIL/ImageQt.py) + + Mouse: + ------ + Mouse interactions for zooming and panning is fully customizable by simply setting the desired button interactions: + e.g., + regionZoomButton = Qt.LeftButton # Drag a zoom box. + zoomOutButton = Qt.RightButton # Pop end of zoom stack (double click clears zoom stack). + panButton = Qt.MiddleButton # Drag to pan. + wheelZoomFactor = 1.25 # Set to None or 1 to disable mouse wheel zoom. + + To disable any interaction, just disable its button. + e.g., to disable panning: + panButton = None + + ROIs: + ----- + Can also add ellipse, rectangle, line, and polygon ROIs to the image. + ROIs should be derived from the provided EllipseROI, RectROI, LineROI, and PolygonROI classes. + ROIs are selectable and optionally moveable with the mouse (see setROIsAreMovable). + + TODO: Add support for editing the displayed image contrast. + TODO: Add support for drawing ROIs with the mouse. + """ + + # Mouse button signals emit image scene (x, y) coordinates. + # !!! For image (row, column) matrix indexing, row = y and column = x. + # !!! These signals will NOT be emitted if the event is handled by an interaction such as zoom or pan. + # !!! If aspect ratio prevents image from filling viewport, emitted position may be outside image bounds. + leftMouseButtonPressed = pyqtSignal(float, float) + leftMouseButtonReleased = pyqtSignal(float, float) + middleMouseButtonPressed = pyqtSignal(float, float) + middleMouseButtonReleased = pyqtSignal(float, float) + rightMouseButtonPressed = pyqtSignal(float, float) + rightMouseButtonReleased = pyqtSignal(float, float) + leftMouseButtonDoubleClicked = pyqtSignal(float, float) + rightMouseButtonDoubleClicked = pyqtSignal(float, float) + + # Emitted upon zooming/panning. + viewChanged = pyqtSignal() + + # Emitted on mouse motion. + # Emits mouse position over image in image pixel coordinates. + # !!! setMouseTracking(True) if you want to use this at all times. + mousePositionOnImageChanged = pyqtSignal(QPoint) + + # Emit index of selected ROI + roiSelected = pyqtSignal(int) + + def __init__(self, parent=None): + QGraphicsView.__init__(self, parent) + + # Image is displayed as a QPixmap in a QGraphicsScene attached to this QGraphicsView. + self.scene = QGraphicsScene() + self.setScene(self.scene) + + # Better quality pixmap scaling? + # self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) + + # Displayed image pixmap in the QGraphicsScene. + self._image = None + + # Image aspect ratio mode. + # Qt.IgnoreAspectRatio: Scale image to fit viewport. + # Qt.KeepAspectRatio: Scale image to fit inside viewport, preserving aspect ratio. + # Qt.KeepAspectRatioByExpanding: Scale image to fill the viewport, preserving aspect ratio. + self.aspectRatioMode = Qt.AspectRatioMode.KeepAspectRatio + + # Scroll bar behaviour. + # Qt.ScrollBarAlwaysOff: Never shows a scroll bar. + # Qt.ScrollBarAlwaysOn: Always shows a scroll bar. + # Qt.ScrollBarAsNeeded: Shows a scroll bar only when zoomed. + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + # Interactions (set buttons to None to disable interactions) + # !!! Events handled by interactions will NOT emit *MouseButton* signals. + # Note: regionZoomButton will still emit a *MouseButtonReleased signal on a click (i.e. tiny box). + self.regionZoomButton = Qt.MouseButton.LeftButton # Drag a zoom box. + self.zoomOutButton = Qt.MouseButton.RightButton # Pop end of zoom stack (double click clears zoom stack). + self.panButton = Qt.MouseButton.MiddleButton # Drag to pan. + self.wheelZoomFactor = 1.25 # Set to None or 1 to disable mouse wheel zoom. + + # Stack of QRectF zoom boxes in scene coordinates. + # !!! If you update this manually, be sure to call updateViewer() to reflect any changes. + self.zoomStack = [] + + # Flags for active zooming/panning. + self._isZooming = False + self._isPanning = False + + # Store temporary position in screen pixels or scene units. + self._pixelPosition = QPoint() + self._scenePosition = QPointF() + + # Track mouse position. e.g., For displaying coordinates in a UI. + # self.setMouseTracking(True) + + # ROIs. + self.ROIs = [] + + # # For drawing ROIs. + # self.drawROI = None + + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + + def sizeHint(self): + return QSize(900, 600) + + def hasImage(self): + """ Returns whether the scene contains an image pixmap. + """ + return self._image is not None + + def clearImage(self): + """ Removes the current image pixmap from the scene if it exists. + """ + if self.hasImage(): + self.scene.removeItem(self._image) + self._image = None + + def pixmap(self): + """ Returns the scene's current image pixmap as a QPixmap, or else None if no image exists. + :rtype: QPixmap | None + """ + if self.hasImage(): + return self._image.pixmap() + return None + + def image(self): + """ Returns the scene's current image pixmap as a QImage, or else None if no image exists. + :rtype: QImage | None + """ + if self.hasImage(): + return self._image.pixmap().toImage() + return None + + def setImage(self, image): + """ Set the scene's current image pixmap to the input QImage or QPixmap. + Raises a RuntimeError if the input image has type other than QImage or QPixmap. + :type image: QImage | QPixmap + """ + if image is None: + pixmap = QPixmap() + elif type(image) is QPixmap: + pixmap = image + elif type(image) is QImage: + pixmap = QPixmap.fromImage(image) + else: + raise RuntimeError("ImageViewer.setImage: Argument must be a QImage, QPixmap") + if self.hasImage(): + self._image.setPixmap(pixmap) + else: + self._image = self.scene.addPixmap(pixmap) + + # Better quality pixmap scaling? + # !!! This will distort actual pixel data when zoomed way in. + # For scientific image analysis, you probably don't want this. + # self._image.setTransformationMode(Qt.SmoothTransformation) + + self.setSceneRect(QRectF(pixmap.rect())) # Set scene size to image size. + self.updateViewer() + + def updateViewer(self): + """ Show current zoom (if showing entire image, apply current aspect ratio mode). + """ + if not self.hasImage(): + return + if len(self.zoomStack): + self._image.setTransformationMode(Qt.FastTransformation) + self.fitInView(self.zoomStack[-1], self.aspectRatioMode) # Show zoomed rect. + else: + self._image.setTransformationMode(Qt.SmoothTransformation) + self.fitInView(self.sceneRect(), self.aspectRatioMode) # Show entire image. + + def clearZoom(self): + if len(self.zoomStack) > 0: + self.zoomStack = [] + self.updateViewer() + self.viewChanged.emit() + + def resizeEvent(self, event): + """ Maintain current zoom on resize. + """ + self.updateViewer() + + def mousePressEvent(self, event): + """ Start mouse pan or zoom mode. + """ + # Ignore dummy events. e.g., Faking pan with left button ScrollHandDrag. + dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier + | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier) + if event.modifiers() == dummyModifiers: + QGraphicsView.mousePressEvent(self, event) + event.accept() + return + + # # Draw ROI + # if self.drawROI is not None: + # if self.drawROI == "Ellipse": + # # Click and drag to draw ellipse. +Shift for circle. + # pass + # elif self.drawROI == "Rect": + # # Click and drag to draw rectangle. +Shift for square. + # pass + # elif self.drawROI == "Line": + # # Click and drag to draw line. + # pass + # elif self.drawROI == "Polygon": + # # Click to add points to polygon. Double-click to close polygon. + # pass + + # Start dragging a region zoom box? + if (self.regionZoomButton is not None) and (event.button() == self.regionZoomButton): + self._pixelPosition = event.pos() # store pixel position + self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) + QGraphicsView.mousePressEvent(self, event) + event.accept() + self._isZooming = True + return + + if (self.zoomOutButton is not None) and (event.button() == self.zoomOutButton): + if len(self.zoomStack): + self.zoomStack.pop() + self.updateViewer() + self.viewChanged.emit() + event.accept() + return + + # Start dragging to pan? + if (self.panButton is not None) and (event.button() == self.panButton): + self._pixelPosition = event.pos() # store pixel position + self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag) + if self.panButton == Qt.MouseButton.LeftButton: + QGraphicsView.mousePressEvent(self, event) + else: + # ScrollHandDrag ONLY works with LeftButton, so fake it. + # Use a bunch of dummy modifiers to notify that event should NOT be handled as usual. + self.viewport().setCursor(Qt.CursorShape.ClosedHandCursor) + dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier + | Qt.KeyboardModifier.ControlModifier + | Qt.KeyboardModifier.AltModifier + | Qt.KeyboardModifier.MetaModifier) + dummyEvent = QMouseEvent(QEvent.Type.MouseButtonPress, QPointF(event.pos()), Qt.MouseButton.LeftButton, + event.buttons(), dummyModifiers) + self.mousePressEvent(dummyEvent) + sceneViewport = self.mapToScene(self.viewport().rect()).boundingRect().intersected(self.sceneRect()) + self._scenePosition = sceneViewport.topLeft() + event.accept() + self._isPanning = True + return + + scenePos = self.mapToScene(event.pos()) + if event.button() == Qt.MouseButton.LeftButton: + self.leftMouseButtonPressed.emit(scenePos.x(), scenePos.y()) + elif event.button() == Qt.MouseButton.MiddleButton: + self.middleMouseButtonPressed.emit(scenePos.x(), scenePos.y()) + elif event.button() == Qt.MouseButton.RightButton: + self.rightMouseButtonPressed.emit(scenePos.x(), scenePos.y()) + + QGraphicsView.mousePressEvent(self, event) + + def mouseReleaseEvent(self, event): + """ Stop mouse pan or zoom mode (apply zoom if valid). + """ + # Ignore dummy events. e.g., Faking pan with left button ScrollHandDrag. + dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier + | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier) + if event.modifiers() == dummyModifiers: + QGraphicsView.mouseReleaseEvent(self, event) + event.accept() + return + + # Finish dragging a region zoom box? + if (self.regionZoomButton is not None) and (event.button() == self.regionZoomButton): + QGraphicsView.mouseReleaseEvent(self, event) + zoomRect = self.scene.selectionArea().boundingRect().intersected(self.sceneRect()) + # Clear current selection area (i.e. rubberband rect). + self.scene.setSelectionArea(QPainterPath()) + self.setDragMode(QGraphicsView.DragMode.NoDrag) + # If zoom box is 3x3 screen pixels or smaller, do not zoom and proceed to process as a click release. + zoomPixelWidth = abs(event.pos().x() - self._pixelPosition.x()) + zoomPixelHeight = abs(event.pos().y() - self._pixelPosition.y()) + if zoomPixelWidth > 3 and zoomPixelHeight > 3: + if zoomRect.isValid() and (zoomRect != self.sceneRect()): + self.zoomStack.append(zoomRect) + self.updateViewer() + self.viewChanged.emit() + event.accept() + self._isZooming = False + return + + # Finish panning? + if (self.panButton is not None) and (event.button() == self.panButton): + if self.panButton == Qt.MouseButton.LeftButton: + QGraphicsView.mouseReleaseEvent(self, event) + else: + # ScrollHandDrag ONLY works with LeftButton, so fake it. + # Use a bunch of dummy modifiers to notify that event should NOT be handled as usual. + self.viewport().setCursor(Qt.CursorShape.ArrowCursor) + dummyModifiers = Qt.KeyboardModifier(Qt.KeyboardModifier.ShiftModifier + | Qt.KeyboardModifier.ControlModifier + | Qt.KeyboardModifier.AltModifier + | Qt.KeyboardModifier.MetaModifier) + dummyEvent = QMouseEvent(QEvent.Type.MouseButtonRelease, QPointF(event.pos()), + Qt.MouseButton.LeftButton, event.buttons(), dummyModifiers) + self.mouseReleaseEvent(dummyEvent) + self.setDragMode(QGraphicsView.DragMode.NoDrag) + if len(self.zoomStack) > 0: + sceneViewport = self.mapToScene(self.viewport().rect()).boundingRect().intersected(self.sceneRect()) + delta = sceneViewport.topLeft() - self._scenePosition + self.zoomStack[-1].translate(delta) + self.zoomStack[-1] = self.zoomStack[-1].intersected(self.sceneRect()) + self.viewChanged.emit() + event.accept() + self._isPanning = False + return + + scenePos = self.mapToScene(event.pos()) + if event.button() == Qt.MouseButton.LeftButton: + self.leftMouseButtonReleased.emit(scenePos.x(), scenePos.y()) + elif event.button() == Qt.MouseButton.MiddleButton: + self.middleMouseButtonReleased.emit(scenePos.x(), scenePos.y()) + elif event.button() == Qt.MouseButton.RightButton: + self.rightMouseButtonReleased.emit(scenePos.x(), scenePos.y()) + + QGraphicsView.mouseReleaseEvent(self, event) + + def mouseDoubleClickEvent(self, event): + """ Show entire image. + """ + # Zoom out on double click? + if (self.zoomOutButton is not None) and (event.button() == self.zoomOutButton): + self.clearZoom() + event.accept() + return + + scenePos = self.mapToScene(event.pos()) + if event.button() == Qt.MouseButton.LeftButton: + self.leftMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y()) + elif event.button() == Qt.MouseButton.RightButton: + self.rightMouseButtonDoubleClicked.emit(scenePos.x(), scenePos.y()) + + QGraphicsView.mouseDoubleClickEvent(self, event) + + def wheelEvent(self, event): + if self.wheelZoomFactor is not None: + modifiers = QGuiApplication.keyboardModifiers() + if modifiers == Qt.ControlModifier: + if self.wheelZoomFactor == 1: + return + if event.angleDelta().y() < 0: + self.zoomOut() + else: + self.zoomIn() + event.accept() + return + + QGraphicsView.wheelEvent(self, event) + + + def zoomIn(self): + # zoom in + if len(self.zoomStack) == 0: + self.zoomStack.append(self.sceneRect()) + elif len(self.zoomStack) > 1: + del self.zoomStack[:-1] + zoomRect = self.zoomStack[-1] + center = zoomRect.center() + zoomRect.setWidth(zoomRect.width() / self.wheelZoomFactor) + zoomRect.setHeight(zoomRect.height() / self.wheelZoomFactor) + zoomRect.moveCenter(center) + self.zoomStack[-1] = zoomRect.intersected(self.sceneRect()) + self.updateViewer() + self.viewChanged.emit() + + def zoomOut(self): + # zoom out + if len(self.zoomStack) == 0: + # Already fully zoomed out. + return + if len(self.zoomStack) > 1: + del self.zoomStack[:-1] + zoomRect = self.zoomStack[-1] + center = zoomRect.center() + zoomRect.setWidth(zoomRect.width() * self.wheelZoomFactor) + zoomRect.setHeight(zoomRect.height() * self.wheelZoomFactor) + zoomRect.moveCenter(center) + self.zoomStack[-1] = zoomRect.intersected(self.sceneRect()) + if self.zoomStack[-1] == self.sceneRect(): + self.zoomStack = [] + self.updateViewer() + self.viewChanged.emit() + + def mouseMoveEvent(self, event): + # Emit updated view during panning. + if self._isPanning: + QGraphicsView.mouseMoveEvent(self, event) + if len(self.zoomStack) > 0: + sceneViewport = self.mapToScene(self.viewport().rect()).boundingRect().intersected(self.sceneRect()) + delta = sceneViewport.topLeft() - self._scenePosition + self._scenePosition = sceneViewport.topLeft() + self.zoomStack[-1].translate(delta) + self.zoomStack[-1] = self.zoomStack[-1].intersected(self.sceneRect()) + self.updateViewer() + self.viewChanged.emit() + + scenePos = self.mapToScene(event.pos()) + if self.sceneRect().contains(scenePos): + # Pixel index offset from pixel center. + x = int(round(scenePos.x() - 0.5)) + y = int(round(scenePos.y() - 0.5)) + imagePos = QPoint(x, y) + else: + # Invalid pixel position. + imagePos = QPoint(-1, -1) + self.mousePositionOnImageChanged.emit(imagePos) + + QGraphicsView.mouseMoveEvent(self, event) + + def enterEvent(self, event): + self.setCursor(Qt.CursorShape.CrossCursor) + + def leaveEvent(self, event): + self.setCursor(Qt.CursorShape.ArrowCursor) + + def addROIs(self, rois): + for roi in rois: + self.scene.addItem(roi) + self.ROIs.append(roi) + + def deleteROIs(self, rois): + for roi in rois: + self.scene.removeItem(roi) + self.ROIs.remove(roi) + del roi + + def clearROIs(self): + for roi in self.ROIs: + self.scene.removeItem(roi) + del self.ROIs[:] + + def roiClicked(self, roi): + for i in range(len(self.ROIs)): + if roi is self.ROIs[i]: + self.roiSelected.emit(i) + print(i) + break + + def setROIsAreMovable(self, tf): + if tf: + for roi in self.ROIs: + roi.setFlags(roi.flags() | QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + else: + for roi in self.ROIs: + roi.setFlags(roi.flags() & ~QGraphicsItem.GraphicsItemFlag.ItemIsMovable) + + def addSpots(self, xy, radius): + for xy_ in xy: + x, y = xy_ + spot = EllipseROI(self) + spot.setRect(x - radius, y - radius, 2 * radius, 2 * radius) + self.scene.addItem(spot) + self.ROIs.append(spot) + + +class EllipseROI(QGraphicsEllipseItem): + + def __init__(self, viewer): + QGraphicsItem.__init__(self) + self._viewer = viewer + pen = QPen(Qt.yellow) + pen.setCosmetic(True) + self.setPen(pen) + self.setFlags(self.GraphicsItemFlag.ItemIsSelectable) + + def mousePressEvent(self, event): + QGraphicsItem.mousePressEvent(self, event) + if event.button() == Qt.MouseButton.LeftButton: + self._viewer.roiClicked(self) + + +class RectROI(QGraphicsRectItem): + + def __init__(self, viewer): + QGraphicsItem.__init__(self) + self._viewer = viewer + pen = QPen(Qt.GlobalColor.yellow) + pen.setCosmetic(True) + self.setPen(pen) + self.setFlags(self.GraphicsItemFlag.ItemIsSelectable) + + def mousePressEvent(self, event): + QGraphicsItem.mousePressEvent(self, event) + if event.button() == Qt.MouseButton.LeftButton: + self._viewer.roiClicked(self) + + +class LineROI(QGraphicsLineItem): + + def __init__(self, viewer): + QGraphicsItem.__init__(self) + self._viewer = viewer + pen = QPen(Qt.GlobalColor.yellow) + pen.setCosmetic(True) + self.setPen(pen) + self.setFlags(self.GraphicsItemFlag.ItemIsSelectable) + + def mousePressEvent(self, event): + QGraphicsItem.mousePressEvent(self, event) + if event.button() == Qt.MouseButton.LeftButton: + self._viewer.roiClicked(self) + + +class PolygonROI(QGraphicsPolygonItem): + + def __init__(self, viewer): + QGraphicsItem.__init__(self) + self._viewer = viewer + pen = QPen(Qt.GlobalColor.yellow) + pen.setCosmetic(True) + self.setPen(pen) + self.setFlags(self.GraphicsItemFlag.ItemIsSelectable) + + def mousePressEvent(self, event): + QGraphicsItem.mousePressEvent(self, event) + if event.button() == Qt.MouseButton.LeftButton: + self._viewer.roiClicked(self) + + +if __name__ == '__main__': + import sys + try: + from PyQt6.QtWidgets import QApplication + except ImportError: + from PyQt5.QtWidgets import QApplication + + def handleLeftClick(x, y): + row = int(y) + column = int(x) + print("Clicked on image pixel (row="+str(row)+", column="+str(column)+")") + + def handleViewChange(): + print("viewChanged") + + # Create the application. + app = QApplication(sys.argv) + + # Create image viewer. + viewer = QtImageViewer() + + # Open an image from file. + viewer.open() + + # Handle left mouse clicks with custom slot. + viewer.leftMouseButtonReleased.connect(handleLeftClick) + + # Show viewer and run application. + viewer.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py new file mode 100644 index 00000000..4c66039c --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -0,0 +1,27 @@ +import os + +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import QDialog +from PyQt5.QtCore import pyqtSignal, Qt +from dialog_screen_grab_ui import Ui_ScreenGrab + + +class ScreenGrabDialog(QDialog): + screen_dialog_signal_quit = pyqtSignal() + + def __init__(self, parent=None): + super(ScreenGrabDialog, self).__init__(parent) + self.setWindowFlags(Qt.Dialog) + self.ui = Ui_ScreenGrab() + self.ui.setupUi(self) + self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.setFixedSize(self.size()) + + self.ui.button_cancel.clicked.connect(self.cancel_dialog) + + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + + def cancel_dialog(self): + self.screen_dialog_signal_quit.emit() + + diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui new file mode 100644 index 00000000..54eb22fd --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -0,0 +1,214 @@ + + + ScreenGrab + + + Qt::NonModal + + + + 0 + 0 + 467 + 213 + + + + Qt::NoFocus + + + Screen Grab + + + true + + + + + + 6 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + Nimbus Sans + + + + To capture the screen, click outside this window. (This window will not be included in the screen capture.) If you selected a pointer in Grab preferences, the pointer will be superimposed where you click. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + Cancel + + + + + + + + + + + + diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py new file mode 100644 index 00000000..127c63c5 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './dialog_screen_grab.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ScreenGrab(object): + def setupUi(self, ScreenGrab): + ScreenGrab.setObjectName("ScreenGrab") + ScreenGrab.setWindowModality(QtCore.Qt.NonModal) + ScreenGrab.resize(467, 213) + ScreenGrab.setFocusPolicy(QtCore.Qt.NoFocus) + ScreenGrab.setModal(True) + self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrab) + self.verticalLayout.setObjectName("verticalLayout") + self.MainVbox = QtWidgets.QVBoxLayout() + self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + self.MainVbox.setSpacing(6) + self.MainVbox.setObjectName("MainVbox") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.icon = QtWidgets.QLabel(ScreenGrab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.icon.sizePolicy().hasHeightForWidth()) + self.icon.setSizePolicy(sizePolicy) + self.icon.setMinimumSize(QtCore.QSize(64, 64)) + self.icon.setMaximumSize(QtCore.QSize(64, 64)) + self.icon.setText("") + self.icon.setPixmap(QtGui.QPixmap("./../../Processes.app/Resources/Processes.png")) + self.icon.setScaledContents(True) + self.icon.setObjectName("icon") + self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.Label = QtWidgets.QLabel(ScreenGrab) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.Label.setFont(font) + self.Label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.Label.setWordWrap(True) + self.Label.setObjectName("Label") + self.verticalLayout_2.addWidget(self.Label) + self.horizontalLayout_2.addLayout(self.verticalLayout_2) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem2) + self.MainVbox.addLayout(self.horizontalLayout_2) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + self.MainVbox.addItem(spacerItem3) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout.setSpacing(22) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem4) + self.button_cancel = QtWidgets.QPushButton(ScreenGrab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.button_cancel.sizePolicy().hasHeightForWidth()) + self.button_cancel.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.button_cancel.setFont(font) + self.button_cancel.setObjectName("button_cancel") + self.horizontalLayout.addWidget(self.button_cancel) + self.MainVbox.addLayout(self.horizontalLayout) + self.verticalLayout.addLayout(self.MainVbox) + + self.retranslateUi(ScreenGrab) + QtCore.QMetaObject.connectSlotsByName(ScreenGrab) + + def retranslateUi(self, ScreenGrab): + _translate = QtCore.QCoreApplication.translate + ScreenGrab.setWindowTitle(_translate("ScreenGrab", "Screen Grab")) + self.Label.setText(_translate("ScreenGrab", "To capture the screen, click outside this window. (This window will not be included in the screen capture.) If you selected a pointer in Grab preferences, the pointer will be superimposed where you click.")) + self.button_cancel.setText(_translate("ScreenGrab", "Cancel")) diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py new file mode 100644 index 00000000..2065babf --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -0,0 +1,36 @@ +import os + +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import QDialog +from PyQt5.QtCore import pyqtSignal, Qt +from dialog_timed_screen_grab_ui import Ui_TimedScreenGrab + + +class TimedScreenGrabDialog(QDialog): + timer_dialog_signal_quit = pyqtSignal() + timer_dialog_signal_start = pyqtSignal() + + def __init__(self, parent=None, timer=None): + super(TimedScreenGrabDialog, self).__init__(parent) + self.setWindowFlags(Qt.Dialog) + self.sec = int(timer / 1000) + self.ui = Ui_TimedScreenGrab() + self.ui.setupUi(self) + self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + + if self.sec > 1: + self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} seconds") + else: + self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} second") + self.setFixedSize(self.size()) + + self.ui.button_cancel.clicked.connect(self.cancel_dialog) + self.ui.button_start_timer.clicked.connect(self.start_timer_dialog) + + def cancel_dialog(self): + self.timer_dialog_signal_quit.emit() + + def start_timer_dialog(self): + self.timer_dialog_signal_start.emit() + diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui new file mode 100644 index 00000000..1d6dcced --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -0,0 +1,235 @@ + + + TimedScreenGrab + + + Qt::NonModal + + + + 0 + 0 + 466 + 212 + + + + Qt::NoFocus + + + Timed Screen Grab + + + true + + + + + + 6 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + Nimbus Sans + + + + Grab will caputure the screen %s after you start the timer. (This window will not be included in the screen capture.) + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + Cancel + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + Start Timer + + + true + + + + + + + + + + + + diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py new file mode 100644 index 00000000..8a3f7e88 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './dialog_timed_screen_grab.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_TimedScreenGrab(object): + def setupUi(self, TimedScreenGrab): + TimedScreenGrab.setObjectName("TimedScreenGrab") + TimedScreenGrab.setWindowModality(QtCore.Qt.NonModal) + TimedScreenGrab.resize(466, 212) + TimedScreenGrab.setFocusPolicy(QtCore.Qt.NoFocus) + TimedScreenGrab.setModal(True) + self.verticalLayout = QtWidgets.QVBoxLayout(TimedScreenGrab) + self.verticalLayout.setObjectName("verticalLayout") + self.MainVbox = QtWidgets.QVBoxLayout() + self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + self.MainVbox.setSpacing(6) + self.MainVbox.setObjectName("MainVbox") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.icon = QtWidgets.QLabel(TimedScreenGrab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.icon.sizePolicy().hasHeightForWidth()) + self.icon.setSizePolicy(sizePolicy) + self.icon.setMinimumSize(QtCore.QSize(64, 64)) + self.icon.setMaximumSize(QtCore.QSize(64, 64)) + self.icon.setText("") + self.icon.setPixmap(QtGui.QPixmap("./../../Processes.app/Resources/Processes.png")) + self.icon.setScaledContents(True) + self.icon.setObjectName("icon") + self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.Label = QtWidgets.QLabel(TimedScreenGrab) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.Label.setFont(font) + self.Label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.Label.setWordWrap(True) + self.Label.setObjectName("Label") + self.verticalLayout_2.addWidget(self.Label) + self.horizontalLayout_2.addLayout(self.verticalLayout_2) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem2) + self.MainVbox.addLayout(self.horizontalLayout_2) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + self.MainVbox.addItem(spacerItem3) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout.setSpacing(22) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem4) + self.button_cancel = QtWidgets.QPushButton(TimedScreenGrab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.button_cancel.sizePolicy().hasHeightForWidth()) + self.button_cancel.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.button_cancel.setFont(font) + self.button_cancel.setObjectName("button_cancel") + self.horizontalLayout.addWidget(self.button_cancel) + self.button_start_timer = QtWidgets.QPushButton(TimedScreenGrab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.button_start_timer.sizePolicy().hasHeightForWidth()) + self.button_start_timer.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.button_start_timer.setFont(font) + self.button_start_timer.setDefault(True) + self.button_start_timer.setObjectName("button_start_timer") + self.horizontalLayout.addWidget(self.button_start_timer) + self.MainVbox.addLayout(self.horizontalLayout) + self.verticalLayout.addLayout(self.MainVbox) + + self.retranslateUi(TimedScreenGrab) + QtCore.QMetaObject.connectSlotsByName(TimedScreenGrab) + + def retranslateUi(self, TimedScreenGrab): + _translate = QtCore.QCoreApplication.translate + TimedScreenGrab.setWindowTitle(_translate("TimedScreenGrab", "Timed Screen Grab")) + self.Label.setText(_translate("TimedScreenGrab", "Grab will caputure the screen %s after you start the timer. (This window will not be included in the screen capture.)")) + self.button_cancel.setText(_translate("TimedScreenGrab", "Cancel")) + self.button_start_timer.setText(_translate("TimedScreenGrab", "Start Timer")) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index edb11153..09ea5e92 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QGridLayout, QFileDialog, QMessageBox +from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup from PyQt5.QtGui import QPixmap, QIcon, QPainter from PyQt5.QtCore import Qt, QTimer from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog @@ -10,6 +10,8 @@ # The Main Window from main_window_ui import Ui_MainWindow +from dialog_timed_screen_grab import TimedScreenGrabDialog +from dialog_screen_grab import ScreenGrabDialog class Window(QMainWindow, Ui_MainWindow): @@ -18,38 +20,96 @@ def __init__(self, parent=None): self.fileName = None self.printerObj = None self.timer_count = None + self.scale_factor = None self.setupUi(self) - self.setupCustomUi() + self.setupCustomUiGroups() self.connectSignalsSlots() + self.initialState() + + def initialState(self): + + self.ActionMenuFilePrint.setEnabled(False) + self.ActionMenuFilePrintSetup.setEnabled(False) + self.ActionMenuViewFitToWindow.setEnabled(False) + + # Image viewer + # Set viewer's aspect ratio mode. + self.img_preview.aspectRatioMode = Qt.AspectRatioMode.KeepAspectRatio + # Set the viewer's scroll bar behaviour. + self.img_preview.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.img_preview.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.img_preview.regionZoomButton = Qt.MouseButton.LeftButton # set to None to disable + # Pop end of zoom stack (double click clears zoom stack). + self.img_preview.zoomOutButton = Qt.MouseButton.RightButton # set to None to disable + # Mouse wheel zooming. + self.img_preview.wheelZoomFactor = 1.25 # Set to None or 1 to disable + # Allow panning with the middle mouse button. + self.img_preview.panButton = Qt.MouseButton.MiddleButton # set to None to disable + self.timer_count = 10000 + self.setWindowTitle("Grab - new document[*]") + self.resize(370, 270) self.take_screenshot() def setupCustomUi(self): # creating an object of the QPrinter class - self.timer_count = 10000 + self.printerObj = QPrinter() - self.setWindowTitle("Grab - new document[*]") - self.resize(370, 270) + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + def setupCustomUiGroups(self): + menu_frequency_group = QActionGroup(self) + menu_frequency_group.addAction(self.ActionUpdateTimerTo1Sec) + menu_frequency_group.addAction(self.ActionUpdateTimerTo2Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo3Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo4Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo5Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo6Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo7Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo8Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo9Secs) + menu_frequency_group.addAction(self.ActionUpdateTimerTo10Secs) + def connectSignalsSlots(self): - # Menu # File self.ActionMenuFileClose.triggered.connect(self.close) self.ActionMenuFileSave.triggered.connect(self.save) self.ActionMenuFileSaveAs.triggered.connect(self.save_as) self.ActionMenuFilePrint.triggered.connect(self.print_image) self.ActionMenuFilePrintSetup.triggered.connect(self.print_preview_image) + # Edit self.ActionMenuEditCopy.triggered.connect(self.copy_to_clipboard) + + # View + self.ActionMenuViewZoomIn.triggered.connect(self.img_preview.zoomIn) + self.ActionMenuViewZoomOut.triggered.connect(self.img_preview.zoomOut) + self.ActionMenuViewZoomClear.triggered.connect(self.img_preview.clearZoom) + # Capture - self.ActionMenuCaptureScreen.triggered.connect(self.new_screenshot) - self.ActionMenuCaptureTimedScreen.triggered.connect(self.new_timed_screenshot) + #self.ActionMenuCaptureScreen.triggered.connect(self.new_screenshot) + self.ActionMenuCaptureScreen.triggered.connect(self._showScreenGrabDialog) + self.ActionMenuCaptureTimedScreen.triggered.connect(self._showTimedScreenGrabDialog) + + # Capture / Timer + self.ActionUpdateTimerTo1Sec.triggered.connect(self._timer_change_for_1_sec) + self.ActionUpdateTimerTo2Secs.triggered.connect(self._timer_change_for_2_secs) + self.ActionUpdateTimerTo3Secs.triggered.connect(self._timer_change_for_3_secs) + self.ActionUpdateTimerTo4Secs.triggered.connect(self._timer_change_for_4_secs) + self.ActionUpdateTimerTo5Secs.triggered.connect(self._timer_change_for_5_secs) + self.ActionUpdateTimerTo6Secs.triggered.connect(self._timer_change_for_6_secs) + self.ActionUpdateTimerTo7Secs.triggered.connect(self._timer_change_for_7_secs) + self.ActionUpdateTimerTo8Secs.triggered.connect(self._timer_change_for_8_secs) + self.ActionUpdateTimerTo9Secs.triggered.connect(self._timer_change_for_9_secs) + self.ActionUpdateTimerTo10Secs.triggered.connect(self._timer_change_for_10_secs) # About self.ActionMenuHelpAbout.triggered.connect(self._showAboutDialog) + + def closeEvent(self, evnt): super(Window, self).closeEvent(evnt) @@ -67,8 +127,6 @@ def save(self): self.setWindowModified(False) def save_as(self): - # if not self.isWindowModified(): - # return options = QFileDialog.Options() # options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName( @@ -90,24 +148,74 @@ def copy_to_clipboard(self): QApplication.clipboard().setImage(qi) def new_screenshot(self): - self.hide() + if self.isVisible(): + self.hide() QTimer.singleShot(1000, self.take_screenshot) def new_timed_screenshot(self): - self.hide() + if self.isVisible(): + self.hide() QTimer.singleShot(self.timer_count, self.take_screenshot) def take_screenshot(self): - self.img_preview.setPixmap(QApplication.primaryScreen().grabWindow(0)) + # Start by clean the last image + self.img_preview.setImage(None) - self.show() + # Take a screenshot in case the pixmap will stay to Null + self.img_preview.setImage(QApplication.primaryScreen().grabWindow(0)) + + # Inform the application about the contain change if self.fileName: self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) else: self.setWindowTitle("Grab - new document[*]") self.setWindowModified(True) - # defining the method to print the image + self.update_actions() + self.show() + + def normal_size(self): + self.img_preview.clearZoom() + + def fit_to_window(self): + # retrieving the Boolean value from the "Fit To Window" action + self.ActionMenuViewFitToWindow.setEnabled(False) + fitToWindow = self.ActionMenuViewFitToWindow.isChecked() + # configuring the scroll area to resizable + # self.scroll_area.setWidgetResizable(fitToWindow) + # if the retrieved value is False, calling the user-defined normal_size() method + if not fitToWindow: + self.normal_size() + # calling the user-defined update_actions() method + # self.update_actions() + + # defining the method to update the actions + def update_actions(self): + if self.img_preview.pixmap().isNull(): + + self.ActionMenuFileSave.setEnabled(False) + self.ActionMenuFileSaveAs.setEnabled(False) + self.ActionMenuFilePrint.setEnabled(False) + self.ActionMenuFilePrintSetup.setEnabled(False) + + self.ActionMenuViewActualSize.setEnabled(False) + self.ActionMenuViewZoomToFit.setEnabled(False) + self.ActionMenuViewZoomIn.setEnabled(False) + self.ActionMenuViewZoomOut.setEnabled(False) + self.ActionMenuViewZoomToSelection.setEnabled(False) + else: + + self.ActionMenuFileSave.setEnabled(True) + self.ActionMenuFileSaveAs.setEnabled(True) + self.ActionMenuFilePrint.setEnabled(True) + self.ActionMenuFilePrintSetup.setEnabled(True) + + self.ActionMenuViewActualSize.setEnabled(True) + self.ActionMenuViewZoomToFit.setEnabled(True) + self.ActionMenuViewZoomIn.setEnabled(True) + self.ActionMenuViewZoomOut.setEnabled(True) + self.ActionMenuViewZoomToSelection.setEnabled(True) + def print_image(self): # creating an object of the QPrintDialog class print_dialog = QPrintDialog(self.printerObj, self) @@ -175,6 +283,71 @@ def _showAboutDialog(): ) msg.exec() + def _showScreenGrabDialog(self): + + if self.ActionMenuCaptureTimedScreen.isEnabled(): + self.hide() + self.ScreenGrabDialog = ScreenGrabDialog() + + self.ScreenGrabDialog.show() + self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._ScreenGrabQuit) + + def _ScreenGrabQuit(self): + if self.ScreenGrabDialog.isVisible() and self.ScreenGrabDialog.isEnabled(): + self.ScreenGrabDialog.close() + if not self.isVisible(): + self.show() + + def _showTimedScreenGrabDialog(self): + + if self.ActionMenuCaptureTimedScreen.isEnabled(): + self.hide() + self.TimedScreenGrabDialog = TimedScreenGrabDialog(timer=self.timer_count) + + self.TimedScreenGrabDialog.show() + self.TimedScreenGrabDialog.timer_dialog_signal_start.connect(self._TimedScreenGrabStart) + self.TimedScreenGrabDialog.timer_dialog_signal_quit.connect(self._TimedScreenGrabQuit) + + def _TimedScreenGrabStart(self): + self._TimedScreenGrabQuit() + self.new_timed_screenshot() + + def _TimedScreenGrabQuit(self): + if self.TimedScreenGrabDialog.isVisible() and self.TimedScreenGrabDialog.isEnabled(): + self.TimedScreenGrabDialog.close() + if not self.isVisible(): + self.show() + + def _timer_change_for_1_sec(self): + self.timer_count = 1000 + + def _timer_change_for_2_secs(self): + self.timer_count = 2000 + + def _timer_change_for_3_secs(self): + self.timer_count = 3000 + + def _timer_change_for_4_secs(self): + self.timer_count = 4000 + + def _timer_change_for_5_secs(self): + self.timer_count = 5000 + + def _timer_change_for_6_secs(self): + self.timer_count = 6000 + + def _timer_change_for_7_secs(self): + self.timer_count = 7000 + + def _timer_change_for_8_secs(self): + self.timer_count = 8000 + + def _timer_change_for_9_secs(self): + self.timer_count = 9000 + + def _timer_change_for_10_secs(self): + self.timer_count = 10000 + if __name__ == "__main__": app = QApplication(sys.argv) diff --git a/Under Construction/Grab.app/Resources/main_window.ui b/Under Construction/Grab.app/Resources/main_window.ui index d602a2cd..994ea593 100644 --- a/Under Construction/Grab.app/Resources/main_window.ui +++ b/Under Construction/Grab.app/Resources/main_window.ui @@ -6,14 +6,20 @@ 0 0 - 353 + 601 294 Grab + + + + + background-color: rgba(255,255,255, 3%) + 0 @@ -31,12 +37,9 @@ 0 - - - - 0 - 0 - + + + background-color: rgba(255,255,255, 3%) @@ -47,7 +50,7 @@ 0 0 - 353 + 601 28 @@ -67,15 +70,35 @@ Edit + + + Capture + + + Timer + + + + + + + + + + + + + + @@ -83,8 +106,20 @@ + + + View + + + + + + + + + @@ -102,6 +137,9 @@ + + false + Save @@ -110,6 +148,9 @@ + + false + Save As @@ -118,6 +159,13 @@ + + false + + + + .. + Print @@ -126,6 +174,9 @@ + + false + Print Setup @@ -182,12 +233,190 @@ Ctrl+Shift+Z + + + false + + + false + + + Fit to Window + + + + + false + + + Zoom In + + + Ctrl++ + + + + + false + + + Zoom Out + + + Ctrl+- + + + + + false + + + Grop + + + + + false + + + Undo + + + + + false + + + Redo + + + + + false + + + false + + + Zoom Clear + + + Ctrl+0 + + + + + false + + + Actual Size + + + + + false + + + Zoom To Fit + + + + + false + + + Zoom To Selection + + + + + true + + + 1 sec + + + + + true + + + 2 secs + + + + + true + + + 3 secs + + + + + true + + + 4 secs + + + + + true + + + 5 secs + + + + + true + + + 6 secs + + + + + true + + + 7 secs + + + + + true + + + 8 secs + + + + + true + + + 9 secs + + + + + true + + + true + + + 10 secs + + - ScreenShotPreview + QtImageViewer QWidget -
widget_screenshot_preview
+
QtImageViewer
1
diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index 1f6207ce..2589f8d3 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -14,24 +14,22 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(353, 294) + MainWindow.resize(601, 294) + MainWindow.setStyleSheet("") self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setStyleSheet("background-color: rgba(255,255,255, 3%)") self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") - self.img_preview = ScreenShotPreview(self.centralwidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.img_preview.sizePolicy().hasHeightForWidth()) - self.img_preview.setSizePolicy(sizePolicy) + self.img_preview = QtImageViewer(self.centralwidget) + self.img_preview.setStyleSheet("background-color: rgba(255,255,255, 3%)") self.img_preview.setObjectName("img_preview") self.verticalLayout.addWidget(self.img_preview) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 353, 28)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 601, 28)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName("menuFile") @@ -39,20 +37,30 @@ def setupUi(self, MainWindow): self.menuEdit.setObjectName("menuEdit") self.menuCapture = QtWidgets.QMenu(self.menubar) self.menuCapture.setObjectName("menuCapture") + self.menuTimer = QtWidgets.QMenu(self.menuCapture) + self.menuTimer.setObjectName("menuTimer") self.menuHelp = QtWidgets.QMenu(self.menubar) self.menuHelp.setObjectName("menuHelp") + self.menuView = QtWidgets.QMenu(self.menubar) + self.menuView.setObjectName("menuView") MainWindow.setMenuBar(self.menubar) self.ActionMenuEditCopy = QtWidgets.QAction(MainWindow) self.ActionMenuEditCopy.setObjectName("ActionMenuEditCopy") self.ActionMenuHelpAbout = QtWidgets.QAction(MainWindow) self.ActionMenuHelpAbout.setObjectName("ActionMenuHelpAbout") self.ActionMenuFileSave = QtWidgets.QAction(MainWindow) + self.ActionMenuFileSave.setEnabled(False) self.ActionMenuFileSave.setObjectName("ActionMenuFileSave") self.ActionMenuFileSaveAs = QtWidgets.QAction(MainWindow) + self.ActionMenuFileSaveAs.setEnabled(False) self.ActionMenuFileSaveAs.setObjectName("ActionMenuFileSaveAs") self.ActionMenuFilePrint = QtWidgets.QAction(MainWindow) + self.ActionMenuFilePrint.setEnabled(False) + icon = QtGui.QIcon.fromTheme("document-print") + self.ActionMenuFilePrint.setIcon(icon) self.ActionMenuFilePrint.setObjectName("ActionMenuFilePrint") self.ActionMenuFilePrintSetup = QtWidgets.QAction(MainWindow) + self.ActionMenuFilePrintSetup.setEnabled(False) self.ActionMenuFilePrintSetup.setObjectName("ActionMenuFilePrintSetup") self.ActionMenuFileClose = QtWidgets.QAction(MainWindow) self.ActionMenuFileClose.setObjectName("ActionMenuFileClose") @@ -67,6 +75,69 @@ def setupUi(self, MainWindow): self.ActionMenuCaptureTimedScreen = QtWidgets.QAction(MainWindow) self.ActionMenuCaptureTimedScreen.setEnabled(True) self.ActionMenuCaptureTimedScreen.setObjectName("ActionMenuCaptureTimedScreen") + self.ActionMenuViewFitToWindow = QtWidgets.QAction(MainWindow) + self.ActionMenuViewFitToWindow.setCheckable(False) + self.ActionMenuViewFitToWindow.setEnabled(False) + self.ActionMenuViewFitToWindow.setObjectName("ActionMenuViewFitToWindow") + self.ActionMenuViewZoomIn = QtWidgets.QAction(MainWindow) + self.ActionMenuViewZoomIn.setEnabled(False) + self.ActionMenuViewZoomIn.setObjectName("ActionMenuViewZoomIn") + self.ActionMenuViewZoomOut = QtWidgets.QAction(MainWindow) + self.ActionMenuViewZoomOut.setEnabled(False) + self.ActionMenuViewZoomOut.setObjectName("ActionMenuViewZoomOut") + self.actionGrop = QtWidgets.QAction(MainWindow) + self.actionGrop.setEnabled(False) + self.actionGrop.setObjectName("actionGrop") + self.actionUndo = QtWidgets.QAction(MainWindow) + self.actionUndo.setEnabled(False) + self.actionUndo.setObjectName("actionUndo") + self.actionRedo = QtWidgets.QAction(MainWindow) + self.actionRedo.setEnabled(False) + self.actionRedo.setObjectName("actionRedo") + self.ActionMenuViewZoomClear = QtWidgets.QAction(MainWindow) + self.ActionMenuViewZoomClear.setCheckable(False) + self.ActionMenuViewZoomClear.setEnabled(False) + self.ActionMenuViewZoomClear.setObjectName("ActionMenuViewZoomClear") + self.ActionMenuViewActualSize = QtWidgets.QAction(MainWindow) + self.ActionMenuViewActualSize.setEnabled(False) + self.ActionMenuViewActualSize.setObjectName("ActionMenuViewActualSize") + self.ActionMenuViewZoomToFit = QtWidgets.QAction(MainWindow) + self.ActionMenuViewZoomToFit.setEnabled(False) + self.ActionMenuViewZoomToFit.setObjectName("ActionMenuViewZoomToFit") + self.ActionMenuViewZoomToSelection = QtWidgets.QAction(MainWindow) + self.ActionMenuViewZoomToSelection.setEnabled(False) + self.ActionMenuViewZoomToSelection.setObjectName("ActionMenuViewZoomToSelection") + self.ActionUpdateTimerTo1Sec = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo1Sec.setCheckable(True) + self.ActionUpdateTimerTo1Sec.setObjectName("ActionUpdateTimerTo1Sec") + self.ActionUpdateTimerTo2Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo2Secs.setCheckable(True) + self.ActionUpdateTimerTo2Secs.setObjectName("ActionUpdateTimerTo2Secs") + self.ActionUpdateTimerTo3Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo3Secs.setCheckable(True) + self.ActionUpdateTimerTo3Secs.setObjectName("ActionUpdateTimerTo3Secs") + self.ActionUpdateTimerTo4Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo4Secs.setCheckable(True) + self.ActionUpdateTimerTo4Secs.setObjectName("ActionUpdateTimerTo4Secs") + self.ActionUpdateTimerTo5Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo5Secs.setCheckable(True) + self.ActionUpdateTimerTo5Secs.setObjectName("ActionUpdateTimerTo5Secs") + self.ActionUpdateTimerTo6Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo6Secs.setCheckable(True) + self.ActionUpdateTimerTo6Secs.setObjectName("ActionUpdateTimerTo6Secs") + self.ActionUpdateTimerTo7Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo7Secs.setCheckable(True) + self.ActionUpdateTimerTo7Secs.setObjectName("ActionUpdateTimerTo7Secs") + self.ActionUpdateTimerTo8Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo8Secs.setCheckable(True) + self.ActionUpdateTimerTo8Secs.setObjectName("ActionUpdateTimerTo8Secs") + self.ActionUpdateTimerTo9Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo9Secs.setCheckable(True) + self.ActionUpdateTimerTo9Secs.setObjectName("ActionUpdateTimerTo9Secs") + self.ActionUpdateTimerTo10Secs = QtWidgets.QAction(MainWindow) + self.ActionUpdateTimerTo10Secs.setCheckable(True) + self.ActionUpdateTimerTo10Secs.setChecked(True) + self.ActionUpdateTimerTo10Secs.setObjectName("ActionUpdateTimerTo10Secs") self.menuFile.addAction(self.ActionMenuFileClose) self.menuFile.addAction(self.ActionMenuFileSave) self.menuFile.addAction(self.ActionMenuFileSaveAs) @@ -74,13 +145,35 @@ def setupUi(self, MainWindow): self.menuFile.addAction(self.ActionMenuFilePrint) self.menuFile.addAction(self.ActionMenuFilePrintSetup) self.menuEdit.addAction(self.ActionMenuEditCopy) + self.menuEdit.addAction(self.actionGrop) + self.menuEdit.addAction(self.actionUndo) + self.menuEdit.addAction(self.actionRedo) + self.menuTimer.addAction(self.ActionUpdateTimerTo1Sec) + self.menuTimer.addAction(self.ActionUpdateTimerTo2Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo3Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo4Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo5Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo6Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo7Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo8Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo9Secs) + self.menuTimer.addAction(self.ActionUpdateTimerTo10Secs) self.menuCapture.addAction(self.ActionMenuCaptureSelection) self.menuCapture.addAction(self.ActionMenuCaptureWindow) self.menuCapture.addAction(self.ActionMenuCaptureScreen) self.menuCapture.addAction(self.ActionMenuCaptureTimedScreen) + self.menuCapture.addSeparator() + self.menuCapture.addAction(self.menuTimer.menuAction()) self.menuHelp.addAction(self.ActionMenuHelpAbout) + self.menuView.addAction(self.ActionMenuViewActualSize) + self.menuView.addAction(self.ActionMenuViewZoomToFit) + self.menuView.addAction(self.ActionMenuViewZoomIn) + self.menuView.addAction(self.ActionMenuViewZoomOut) + self.menuView.addAction(self.ActionMenuViewZoomClear) + self.menuView.addAction(self.ActionMenuViewZoomToSelection) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuEdit.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuCapture.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) @@ -93,7 +186,9 @@ def retranslateUi(self, MainWindow): self.menuFile.setTitle(_translate("MainWindow", "File")) self.menuEdit.setTitle(_translate("MainWindow", "Edit")) self.menuCapture.setTitle(_translate("MainWindow", "Capture")) + self.menuTimer.setTitle(_translate("MainWindow", "Timer")) self.menuHelp.setTitle(_translate("MainWindow", "Help")) + self.menuView.setTitle(_translate("MainWindow", "View")) self.ActionMenuEditCopy.setText(_translate("MainWindow", "Copy")) self.ActionMenuEditCopy.setShortcut(_translate("MainWindow", "Ctrl+C")) self.ActionMenuHelpAbout.setText(_translate("MainWindow", "About")) @@ -115,4 +210,27 @@ def retranslateUi(self, MainWindow): self.ActionMenuCaptureScreen.setShortcut(_translate("MainWindow", "Ctrl+Z")) self.ActionMenuCaptureTimedScreen.setText(_translate("MainWindow", "Timed Screen")) self.ActionMenuCaptureTimedScreen.setShortcut(_translate("MainWindow", "Ctrl+Shift+Z")) -from widget_screenshot_preview import ScreenShotPreview + self.ActionMenuViewFitToWindow.setText(_translate("MainWindow", "Fit to Window")) + self.ActionMenuViewZoomIn.setText(_translate("MainWindow", "Zoom In")) + self.ActionMenuViewZoomIn.setShortcut(_translate("MainWindow", "Ctrl++")) + self.ActionMenuViewZoomOut.setText(_translate("MainWindow", "Zoom Out")) + self.ActionMenuViewZoomOut.setShortcut(_translate("MainWindow", "Ctrl+-")) + self.actionGrop.setText(_translate("MainWindow", "Grop")) + self.actionUndo.setText(_translate("MainWindow", "Undo")) + self.actionRedo.setText(_translate("MainWindow", "Redo")) + self.ActionMenuViewZoomClear.setText(_translate("MainWindow", "Zoom Clear")) + self.ActionMenuViewZoomClear.setShortcut(_translate("MainWindow", "Ctrl+0")) + self.ActionMenuViewActualSize.setText(_translate("MainWindow", "Actual Size")) + self.ActionMenuViewZoomToFit.setText(_translate("MainWindow", "Zoom To Fit")) + self.ActionMenuViewZoomToSelection.setText(_translate("MainWindow", "Zoom To Selection")) + self.ActionUpdateTimerTo1Sec.setText(_translate("MainWindow", "1 sec")) + self.ActionUpdateTimerTo2Secs.setText(_translate("MainWindow", "2 secs")) + self.ActionUpdateTimerTo3Secs.setText(_translate("MainWindow", "3 secs")) + self.ActionUpdateTimerTo4Secs.setText(_translate("MainWindow", "4 secs")) + self.ActionUpdateTimerTo5Secs.setText(_translate("MainWindow", "5 secs")) + self.ActionUpdateTimerTo6Secs.setText(_translate("MainWindow", "6 secs")) + self.ActionUpdateTimerTo7Secs.setText(_translate("MainWindow", "7 secs")) + self.ActionUpdateTimerTo8Secs.setText(_translate("MainWindow", "8 secs")) + self.ActionUpdateTimerTo9Secs.setText(_translate("MainWindow", "9 secs")) + self.ActionUpdateTimerTo10Secs.setText(_translate("MainWindow", "10 secs")) +from QtImageViewer import QtImageViewer diff --git a/Under Construction/Grab.app/Resources/widget_screenshot_preview.py b/Under Construction/Grab.app/Resources/widget_screenshot_preview.py index 3cb6b2e7..2dbd7b1f 100644 --- a/Under Construction/Grab.app/Resources/widget_screenshot_preview.py +++ b/Under Construction/Grab.app/Resources/widget_screenshot_preview.py @@ -22,49 +22,49 @@ def __init__(self, parent=None): self.__pixmap = None self.qp = None - self.setupUi() - self.setup() - - - def setupUi(self): - # setting window title - self.setWindowTitle('ScreenShotViewer') - - # setting window geometry - # self.setGeometry(200, 200, 300, 300) - - def setup(self): - self.qp = QPainter() - self.show() - - def setPixmap(self, pixmap): - if pixmap != self.__pixmap: - self.__pixmap = pixmap - self.pixmapChanged.emit() - - def pixmap(self): - return self.__pixmap + # self.setupUi() + # self.setup() + # + # + # def setupUi(self): + # # setting window title + # self.setWindowTitle('ScreenShotViewer') + # + # # setting window geometry + # # self.setGeometry(200, 200, 300, 300) + # + # def setup(self): + # # self.qp = QPainter() + # self.show() + # + # def setPixmap(self, pixmap): + # if pixmap != self.__pixmap: + # self.__pixmap = pixmap + # self.pixmapChanged.emit() + # + # def pixmap(self): + # return self.__pixmap # method for paint event - def paintEvent(self, event): - - self.qp.begin(self) - - self.qp.setBrush(Qt.darkGray) - self.qp.drawRect(0, 0, self.width(), self.height()) - - # tune up painter - self.qp.setRenderHint(QPainter.Antialiasing) - - # # drawing background - if isinstance(self.__pixmap, QPixmap): - bg = self.pixmap().scaled(self.width(), self.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) - - # drawing background - self.qp.drawImage(int((self.width() - bg.width()) / 2), - int((self.height() - bg.height()) / 2), - bg.toImage() - ) - - # ending the painter - self.qp.end() + # def paintEvent(self, event): + # + # self.qp.begin(self) + # + # self.qp.setBrush(Qt.darkGray) + # self.qp.drawRect(0, 0, self.width(), self.height()) + # + # # tune up painter + # self.qp.setRenderHint(QPainter.Antialiasing) + # + # # # drawing background + # if isinstance(self.__pixmap, QPixmap): + # bg = self.pixmap().scaled(self.width(), self.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) + # + # # drawing background + # self.qp.drawImage(int((self.width() - bg.width()) / 2), + # int((self.height() - bg.height()) / 2), + # bg.toImage() + # ) + # + # # ending the painter + # self.qp.end() diff --git a/Under Construction/Grab.app/Resources/widget_transparent_window.py b/Under Construction/Grab.app/Resources/widget_transparent_window.py new file mode 100644 index 00000000..d7c63811 --- /dev/null +++ b/Under Construction/Grab.app/Resources/widget_transparent_window.py @@ -0,0 +1,43 @@ +try: + from PyQt6.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize + from PyQt6.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen + from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication +except ImportError: + try: + from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize + from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen, QGuiApplication + from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication + except ImportError: + raise ImportError("Requires PyQt (version 5 or 6)") + +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication +from PyQt5.QtGui import QMouseEvent +import numpy + + +class TransWindow(QWidget): + mouse_press = pyqtSignal() + + def __init__(self): + QWidget.__init__(self, None, Qt.Window) + self.layout = QGridLayout() + self.setLayout(self.layout) + + self.showMaximized() + self.activateWindow() + self.raise_() + self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) + + screenGeometry = QApplication.desktop().availableGeometry() + self.setGeometry(screenGeometry) + self.setStyleSheet("QWidget { background-color: rgba(255,255,255, 5%); }") + self.pos = None + + def mousePressEvent(self, QMouseEvent): + self.hide() + xd = QApplication.desktop().screenGeometry().x() - QApplication.desktop().availableGeometry().x() + yd = QApplication.desktop().screenGeometry().y() - QApplication.desktop().availableGeometry().y() + self.pos = numpy.array([QMouseEvent.pos().x() - xd, + QMouseEvent.pos().y() - yd]) + self.mouse_press.emit("mouse_press()") From eb95dda9972a93ddf6ea9c6e6c6816d8080afbc7 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sat, 10 Feb 2024 16:42:03 +0100 Subject: [PATCH 02/36] Update the Grab utility --- Under Construction/Grab.app/Resources/LICENSE | 8 + .../Grab.app/Resources/README.md | 16 ++ .../Grab.app/Resources/dialog_help.py | 31 +++ .../Grab.app/Resources/dialog_help.ui | 36 ++++ .../Grab.app/Resources/dialog_help_ui.py | 30 +++ .../Grab.app/Resources/dialog_screen_grab.py | 41 +++- .../Resources/dialog_screen_grab_ui.py | 1 - .../Resources/dialog_timed_screen_grab.py | 6 +- Under Construction/Grab.app/Resources/grab.py | 200 ++++++++++++++---- .../Grab.app/Resources/main_window.ui | 11 +- .../Grab.app/Resources/main_window_ui.py | 7 +- .../Grab.app/Resources/trigger_of_camera.wav | Bin 0 -> 55854 bytes .../Grab.app/Resources/widget_snapping.py | 153 ++++++++++++++ .../Resources/widget_transparent_window.py | 41 ++-- .../Grab.app/Resources/windowlistmodel.py | 33 +++ .../Grab.app/Resources/worker_snapshots.py | 29 +++ 16 files changed, 584 insertions(+), 59 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/README.md create mode 100644 Under Construction/Grab.app/Resources/dialog_help.py create mode 100644 Under Construction/Grab.app/Resources/dialog_help.ui create mode 100644 Under Construction/Grab.app/Resources/dialog_help_ui.py create mode 100644 Under Construction/Grab.app/Resources/trigger_of_camera.wav create mode 100644 Under Construction/Grab.app/Resources/widget_snapping.py create mode 100644 Under Construction/Grab.app/Resources/windowlistmodel.py create mode 100644 Under Construction/Grab.app/Resources/worker_snapshots.py diff --git a/Under Construction/Grab.app/Resources/LICENSE b/Under Construction/Grab.app/Resources/LICENSE index 0e77b64a..47bff5f1 100644 --- a/Under Construction/Grab.app/Resources/LICENSE +++ b/Under Construction/Grab.app/Resources/LICENSE @@ -3,3 +3,11 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Sound bank: + +* Trigger of camera 1 + * Title: Trigger camera SLR type (Nikon D70S). Standard noise. + * Author: Joseph SARDIN https://josephsardin.fr/ + * Source: https://bigsoundbank.com/sound-2394-trigger-of-camera-1.html + * Right: https://bigsoundbank.com/right.html diff --git a/Under Construction/Grab.app/Resources/README.md b/Under Construction/Grab.app/Resources/README.md new file mode 100644 index 00000000..3738c810 --- /dev/null +++ b/Under Construction/Grab.app/Resources/README.md @@ -0,0 +1,16 @@ +# Grab + +Grab is an application that can capture screenshots of helloSystem and its applications. You tell Grab to capture what appears on your screen and it creates a PNG file. You can then view the PNG file with Preview or any application capable of opening PNGs. + +## Create a screenshot +1. Set up the screen, so it shows what you want to capture. +2. Open the ``Grab`` icon in the Utilities folder or if ``Grab`` is already running, click its icon on the ``Dock`` to make it active. +3. Choose an option from the Capture menu or press its corresponding shortcut key: + * Selection ``(Maj+Ctrl+A)`` enable you to capture a portion of the screen. When you choose this option, the Selection Grab dialog appears. Use the mouse pointer to drag a box around the portion of the scree you want to capture . Release the mouse button to capture the screen. + * Window ``(Maj+Ctrl+W)`` enables you to capture a window. When you choose this option, the Window Grab dialog appears. Click the Choose Window button, then click the window you want to capture. + * Screen ``(Ctrl+Z)`` enables you to capture the entire screen. When you choose this option, the Screen Grab dialog appears. Click outside the dialog to capture the screen. + * Timed Screen ``(Maj+Ctrl+Z)`` enables you to capture the entire screen after a ten-second delay. When you choose this option, the Timed Screen Grab dialog appears. Click the Start Timer button, then activate the program you want to capture and arrange onscreen elements as desired. In ten seconds, the screen is captured. +4. ``Grab`` makes a camera shutter sound as it captures the screen. The image appears in an untitled document window. +5. If you are satisfied with the screenshot, choose File > Save (Figure 7) or press (Ctrl+S) and use the Save As dialog sheet that appears to save it as a file on disk or if you are not satisfied with the screenshot, choose File > Close (Figure 8) or press (Ctrl+W) to close the window. In the Close dialog sheet, click Don't Save. + +You can create screenshots without ``Grab``. Press (Maj+Ctrl+3) to capture the entire screen or (Maj+Ctrl+4) to capture a portion of the screen. The screenshot is automatically saved on the desktop as a PNG file. diff --git a/Under Construction/Grab.app/Resources/dialog_help.py b/Under Construction/Grab.app/Resources/dialog_help.py new file mode 100644 index 00000000..af9f24fe --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_help.py @@ -0,0 +1,31 @@ +import os + +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence +from PyQt5.QtWidgets import QDialog, QShortcut, qApp +from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot +from dialog_help_ui import Ui_HelpDialog + + +class HelpDialog(QDialog): + screen_dialog_signal_quit = pyqtSignal() + screen_dialog_signal_start = pyqtSignal() + + def __init__(self, parent=None): + super(HelpDialog, self).__init__(parent) + self.ui = Ui_HelpDialog() + self.ui.setupUi(self) + self.setup() + + def setup(self): + + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.open() + self.setFocus() + + def open(self) -> None: + with open(os.path.join(os.path.dirname(__file__), "README.md"), "r") as file: + text = file.read() + self.ui.textBrowser.setMarkdown(text) + + + diff --git a/Under Construction/Grab.app/Resources/dialog_help.ui b/Under Construction/Grab.app/Resources/dialog_help.ui new file mode 100644 index 00000000..74182eb0 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_help.ui @@ -0,0 +1,36 @@ + + + HelpDialog + + + + 0 + 0 + 554 + 458 + + + + Dialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + diff --git a/Under Construction/Grab.app/Resources/dialog_help_ui.py b/Under Construction/Grab.app/Resources/dialog_help_ui.py new file mode 100644 index 00000000..2b29138b --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_help_ui.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './dialog_help.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_HelpDialog(object): + def setupUi(self, HelpDialog): + HelpDialog.setObjectName("HelpDialog") + HelpDialog.resize(541, 458) + self.verticalLayout = QtWidgets.QVBoxLayout(HelpDialog) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.textBrowser = QtWidgets.QTextBrowser(HelpDialog) + self.textBrowser.setObjectName("textBrowser") + self.verticalLayout.addWidget(self.textBrowser) + + self.retranslateUi(HelpDialog) + QtCore.QMetaObject.connectSlotsByName(HelpDialog) + + def retranslateUi(self, HelpDialog): + _translate = QtCore.QCoreApplication.translate + HelpDialog.setWindowTitle(_translate("HelpDialog", "Dialog")) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 4c66039c..72ca94a8 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,27 +1,56 @@ import os -from PyQt5.QtGui import QPixmap, QIcon -from PyQt5.QtWidgets import QDialog -from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence +from PyQt5.QtWidgets import QDialog, QShortcut, qApp +from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot from dialog_screen_grab_ui import Ui_ScreenGrab class ScreenGrabDialog(QDialog): screen_dialog_signal_quit = pyqtSignal() + screen_dialog_signal_start = pyqtSignal() def __init__(self, parent=None): super(ScreenGrabDialog, self).__init__(parent) - self.setWindowFlags(Qt.Dialog) + # self.setWindowFlags( + # Qt.Window | Qt.WindowTitleHint | Qt.WindowCloseButtonHint + # ) + # self.setWindowFlags( + # Qt.Window | + # Qt.CustomizeWindowHint | + # Qt.WindowTitleHint | + # Qt.WindowCloseButtonHint + # ) + #self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) + self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + self.setWindowModality(Qt.WindowModality.ApplicationModal) + self.ui = Ui_ScreenGrab() self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setFixedSize(self.size()) - self.ui.button_cancel.clicked.connect(self.cancel_dialog) + self.ui.button_cancel.clicked.connect(self.screen_dialog_quit) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.screen_dialog_quit) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - def cancel_dialog(self): + self.setFocus() + + + # def focusOutEvent(self, event): + # self.screen_dialog_signal_quit.emit() + + def closeEvent(self, event): + super(ScreenGrabDialog, self).closeEvent(event) + self.screen_dialog_signal_quit.emit() + event.accept() + + def screen_dialog_quit(self): self.screen_dialog_signal_quit.emit() + def screen_dialog_start(self): + self.screen_dialog_signal_start.emit() diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index 127c63c5..984bc0fb 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -17,7 +17,6 @@ def setupUi(self, ScreenGrab): ScreenGrab.setWindowModality(QtCore.Qt.NonModal) ScreenGrab.resize(467, 213) ScreenGrab.setFocusPolicy(QtCore.Qt.NoFocus) - ScreenGrab.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrab) self.verticalLayout.setObjectName("verticalLayout") self.MainVbox = QtWidgets.QVBoxLayout() diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index 2065babf..fe0d0d0f 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -1,7 +1,7 @@ import os -from PyQt5.QtGui import QPixmap, QIcon -from PyQt5.QtWidgets import QDialog +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence +from PyQt5.QtWidgets import QDialog, QShortcut from PyQt5.QtCore import pyqtSignal, Qt from dialog_timed_screen_grab_ui import Ui_TimedScreenGrab @@ -27,6 +27,8 @@ def __init__(self, parent=None, timer=None): self.ui.button_cancel.clicked.connect(self.cancel_dialog) self.ui.button_start_timer.clicked.connect(self.start_timer_dialog) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.cancel_dialog) def cancel_dialog(self): self.timer_dialog_signal_quit.emit() diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 09ea5e92..8e33a323 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup -from PyQt5.QtGui import QPixmap, QIcon, QPainter -from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut +from PyQt5.QtGui import QPixmap, QIcon, QPainter, QImage +from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog - +from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer import sys import os @@ -12,21 +12,35 @@ from main_window_ui import Ui_MainWindow from dialog_timed_screen_grab import TimedScreenGrabDialog from dialog_screen_grab import ScreenGrabDialog +from dialog_help import HelpDialog +from widget_transparent_window import TransWindow +from widget_snapping import SnippingWidget - +QLoggingCategory.setFilterRules("*.debug=false\nqt.qpa.*=false") class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(Window, self).__init__() + self.initialized = False + self.settings = None self.fileName = None self.printerObj = None self.timer_count = None self.scale_factor = None + self.snippingWidget = None + self._pixmap = None + self.sound = None + + self.TimedScreenGrabDialog = None + self.ScreenGrabDialog = None + self.TransWindow = None + self.setupUi(self) self.setupCustomUi() self.setupCustomUiGroups() self.connectSignalsSlots() self.initialState() + self.initialized = True def initialState(self): @@ -51,6 +65,8 @@ def initialState(self): self.timer_count = 10000 self.setWindowTitle("Grab - new document[*]") self.resize(370, 270) + self.settings = QSettings("helloSystem", "Grab.app") + self.read_settings() self.take_screenshot() def setupCustomUi(self): @@ -60,6 +76,15 @@ def setupCustomUi(self): self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.snippingWidget = SnippingWidget(app=QApplication.instance()) + self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted + + self.sound = QMediaPlayer() + self.sound.setMedia(QMediaContent(QUrl.fromLocalFile( + os.path.join(os.path.dirname(__file__), "trigger_of_camera.wav") + ))) + + def setupCustomUiGroups(self): menu_frequency_group = QActionGroup(self) menu_frequency_group.addAction(self.ActionUpdateTimerTo1Sec) @@ -90,7 +115,6 @@ def connectSignalsSlots(self): self.ActionMenuViewZoomClear.triggered.connect(self.img_preview.clearZoom) # Capture - #self.ActionMenuCaptureScreen.triggered.connect(self.new_screenshot) self.ActionMenuCaptureScreen.triggered.connect(self._showScreenGrabDialog) self.ActionMenuCaptureTimedScreen.triggered.connect(self._showTimedScreenGrabDialog) @@ -105,13 +129,71 @@ def connectSignalsSlots(self): self.ActionUpdateTimerTo8Secs.triggered.connect(self._timer_change_for_8_secs) self.ActionUpdateTimerTo9Secs.triggered.connect(self._timer_change_for_9_secs) self.ActionUpdateTimerTo10Secs.triggered.connect(self._timer_change_for_10_secs) + + # Capture / Area + self.ActionMenuCaptureSelection.triggered.connect(self.snipArea) + # self.ui.pushButton_area.clicked.connect(self.snipArea) + # self.ui.pushButton_full.clicked.connect(self.snipFull) + # About self.ActionMenuHelpAbout.triggered.connect(self._showAboutDialog) + self.ActionMenuHelpDocumentation.triggered.connect(self._showHelpDialog) + def onSnippingCompleted(self, frame): + self.setWindowState(Qt.WindowActive) + if frame is None: + return + + # image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) + # pixmap = QPixmap.fromImage(image) + # Start by clean the last image + self.img_preview.setImage(None) - def closeEvent(self, evnt): - super(Window, self).closeEvent(evnt) + # Take a screenshot in case the pixmap will stay to Null + self.img_preview.setImage(frame) + + # Inform the application about the contain change + if self.fileName: + self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) + else: + self.setWindowTitle("Grab - new document[*]") + + self.setWindowModified(True) + + self.update_actions() + + + def snipArea(self): + self.setWindowState(Qt.WindowMinimized) + self.snippingWidget.start() + + def snipFull(self): + self.setWindowState(Qt.WindowMinimized) + self.snippingWidget.fullscreen() + + def write_settings(self): + self.settings.setValue("geometry", self.saveGeometry()) + self.settings.setValue("windowState", self.saveState()) + + def read_settings(self): + self.restoreGeometry(self.settings.value("geometry", QByteArray())) + self.restoreState(self.settings.value("windowState", QByteArray())) + + def closeEvent(self, event): + self.write_settings() + super(Window, self).closeEvent(event) + event.accept() + + def dragEnterEvent(self, event): + event.acceptProposedAction() + + def dropEvent(self, event): + urls = event.mimeData().urls() + filename = urls[0].toLocalFile() + self.loadFile(filename) + self.decodeFile(filename) + event.acceptProposedAction() def save(self): if not self.isWindowModified(): @@ -147,20 +229,19 @@ def copy_to_clipboard(self): qi = self.img_preview.pixmap().toImage() QApplication.clipboard().setImage(qi) - def new_screenshot(self): - if self.isVisible(): - self.hide() - QTimer.singleShot(1000, self.take_screenshot) - def new_timed_screenshot(self): - if self.isVisible(): - self.hide() QTimer.singleShot(self.timer_count, self.take_screenshot) def take_screenshot(self): + + self.hide() + # Start by clean the last image self.img_preview.setImage(None) + if self.initialized: + self.sound.play() + # Take a screenshot in case the pixmap will stay to Null self.img_preview.setImage(QApplication.primaryScreen().grabWindow(0)) @@ -169,9 +250,11 @@ def take_screenshot(self): self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) else: self.setWindowTitle("Grab - new document[*]") + self.setWindowModified(True) self.update_actions() + self.show() def normal_size(self): @@ -287,37 +370,73 @@ def _showScreenGrabDialog(self): if self.ActionMenuCaptureTimedScreen.isEnabled(): self.hide() - self.ScreenGrabDialog = ScreenGrabDialog() + # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) + + + + + + self.ScreenGrabDialog = ScreenGrabDialog(self) + self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) + self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) + + self.TransWindow = TransWindow(self) + self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) + self.TransWindow.transparent_window_signal_quit.connect(self._CloseAllDialogs) + + self.ScreenGrabDialog.hide() self.ScreenGrabDialog.show() - self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._ScreenGrabQuit) - def _ScreenGrabQuit(self): - if self.ScreenGrabDialog.isVisible() and self.ScreenGrabDialog.isEnabled(): - self.ScreenGrabDialog.close() - if not self.isVisible(): - self.show() + self.TransWindow.hide() + self.TransWindow.show() - def _showTimedScreenGrabDialog(self): + while not self.windowHandle(): + QApplication.processEvents() + + + + def _ScreenGrabStart(self): + self._CloseAllDialogs() + + QApplication.processEvents() + self.take_screenshot() + + def _showTimedScreenGrabDialog(self): if self.ActionMenuCaptureTimedScreen.isEnabled(): self.hide() - self.TimedScreenGrabDialog = TimedScreenGrabDialog(timer=self.timer_count) - - self.TimedScreenGrabDialog.show() - self.TimedScreenGrabDialog.timer_dialog_signal_start.connect(self._TimedScreenGrabStart) - self.TimedScreenGrabDialog.timer_dialog_signal_quit.connect(self._TimedScreenGrabQuit) + if self.TimedScreenGrabDialog is None: + self.TimedScreenGrabDialog = TimedScreenGrabDialog(timer=self.timer_count) + self.TimedScreenGrabDialog.timer_dialog_signal_start.connect(self._TimedScreenGrabStart) + self.TimedScreenGrabDialog.timer_dialog_signal_quit.connect(self._CloseAllDialogs) + self.TimedScreenGrabDialog.exec_() + self.show() def _TimedScreenGrabStart(self): - self._TimedScreenGrabQuit() + self._CloseAllDialogs() self.new_timed_screenshot() - def _TimedScreenGrabQuit(self): - if self.TimedScreenGrabDialog.isVisible() and self.TimedScreenGrabDialog.isEnabled(): + def _CloseAllDialogs(self): + if self.TimedScreenGrabDialog and isinstance(self.TimedScreenGrabDialog, TimedScreenGrabDialog): self.TimedScreenGrabDialog.close() - if not self.isVisible(): + self.TimedScreenGrabDialog = None + if self.ScreenGrabDialog and isinstance(self.ScreenGrabDialog, ScreenGrabDialog): + self.ScreenGrabDialog.close() + self.ScreenGrabDialog = None + if self.TransWindow and isinstance(self.TransWindow, TransWindow): + self.TransWindow.close() + self.TransWindow = None + + if self.isHidden(): self.show() + while not self.windowHandle(): + QApplication.processEvents() + + def _timer_change_for(self, value): + self.timer_count = value + def _timer_change_for_1_sec(self): self.timer_count = 1000 @@ -348,9 +467,18 @@ def _timer_change_for_9_secs(self): def _timer_change_for_10_secs(self): self.timer_count = 10000 + def _showHelpDialog(self): + if self.ActionMenuHelpAbout.isEnabled(): + + self.HelpDialog = HelpDialog() + self.HelpDialog.show() -if __name__ == "__main__": +def main(): app = QApplication(sys.argv) - win = Window() - win.show() - sys.exit(app.exec()) + window = Window() + window.show() + sys.exit(app.exec_()) + + +if __name__ == '__main__': + main() diff --git a/Under Construction/Grab.app/Resources/main_window.ui b/Under Construction/Grab.app/Resources/main_window.ui index 994ea593..016c50e8 100644 --- a/Under Construction/Grab.app/Resources/main_window.ui +++ b/Under Construction/Grab.app/Resources/main_window.ui @@ -10,6 +10,9 @@ 294 + + true + Grab @@ -104,6 +107,7 @@ Help + @@ -194,7 +198,7 @@ - false + true Selection @@ -411,6 +415,11 @@ 10 secs + + + Documentation + + diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index 2589f8d3..0d14853d 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -15,6 +15,7 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(601, 294) + MainWindow.setAcceptDrops(True) MainWindow.setStyleSheet("") self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setStyleSheet("background-color: rgba(255,255,255, 3%)") @@ -65,7 +66,7 @@ def setupUi(self, MainWindow): self.ActionMenuFileClose = QtWidgets.QAction(MainWindow) self.ActionMenuFileClose.setObjectName("ActionMenuFileClose") self.ActionMenuCaptureSelection = QtWidgets.QAction(MainWindow) - self.ActionMenuCaptureSelection.setEnabled(False) + self.ActionMenuCaptureSelection.setEnabled(True) self.ActionMenuCaptureSelection.setObjectName("ActionMenuCaptureSelection") self.ActionMenuCaptureWindow = QtWidgets.QAction(MainWindow) self.ActionMenuCaptureWindow.setEnabled(False) @@ -138,6 +139,8 @@ def setupUi(self, MainWindow): self.ActionUpdateTimerTo10Secs.setCheckable(True) self.ActionUpdateTimerTo10Secs.setChecked(True) self.ActionUpdateTimerTo10Secs.setObjectName("ActionUpdateTimerTo10Secs") + self.ActionMenuHelpDocumentation = QtWidgets.QAction(MainWindow) + self.ActionMenuHelpDocumentation.setObjectName("ActionMenuHelpDocumentation") self.menuFile.addAction(self.ActionMenuFileClose) self.menuFile.addAction(self.ActionMenuFileSave) self.menuFile.addAction(self.ActionMenuFileSaveAs) @@ -164,6 +167,7 @@ def setupUi(self, MainWindow): self.menuCapture.addAction(self.ActionMenuCaptureTimedScreen) self.menuCapture.addSeparator() self.menuCapture.addAction(self.menuTimer.menuAction()) + self.menuHelp.addAction(self.ActionMenuHelpDocumentation) self.menuHelp.addAction(self.ActionMenuHelpAbout) self.menuView.addAction(self.ActionMenuViewActualSize) self.menuView.addAction(self.ActionMenuViewZoomToFit) @@ -233,4 +237,5 @@ def retranslateUi(self, MainWindow): self.ActionUpdateTimerTo8Secs.setText(_translate("MainWindow", "8 secs")) self.ActionUpdateTimerTo9Secs.setText(_translate("MainWindow", "9 secs")) self.ActionUpdateTimerTo10Secs.setText(_translate("MainWindow", "10 secs")) + self.ActionMenuHelpDocumentation.setText(_translate("MainWindow", "Documentation")) from QtImageViewer import QtImageViewer diff --git a/Under Construction/Grab.app/Resources/trigger_of_camera.wav b/Under Construction/Grab.app/Resources/trigger_of_camera.wav new file mode 100644 index 0000000000000000000000000000000000000000..000ea19c0890ead4720a187d6b349ab8942d55bf GIT binary patch literal 55854 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 0: time.sleep(wait) + # empty the queue here (thread safe) + with self.queue.mutex: + self.queue.queue.clear() From 6df63a1d9d377af3f8cbc5004f935c9391e7f89f Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sat, 10 Feb 2024 21:38:06 +0100 Subject: [PATCH 03/36] Fixe Shift and many docs troubles --- Under Construction/Grab.app/Resources/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Under Construction/Grab.app/Resources/README.md b/Under Construction/Grab.app/Resources/README.md index 3738c810..ca728462 100644 --- a/Under Construction/Grab.app/Resources/README.md +++ b/Under Construction/Grab.app/Resources/README.md @@ -5,12 +5,12 @@ Grab is an application that can capture screenshots of helloSystem and its appli ## Create a screenshot 1. Set up the screen, so it shows what you want to capture. 2. Open the ``Grab`` icon in the Utilities folder or if ``Grab`` is already running, click its icon on the ``Dock`` to make it active. -3. Choose an option from the Capture menu or press its corresponding shortcut key: - * Selection ``(Maj+Ctrl+A)`` enable you to capture a portion of the screen. When you choose this option, the Selection Grab dialog appears. Use the mouse pointer to drag a box around the portion of the scree you want to capture . Release the mouse button to capture the screen. - * Window ``(Maj+Ctrl+W)`` enables you to capture a window. When you choose this option, the Window Grab dialog appears. Click the Choose Window button, then click the window you want to capture. +3. Choose an option from the ``Capture`` menu or press its corresponding shortcut key: + * Selection ``(Shift+Ctrl+A)`` enable you to capture a portion of the screen. When you choose this option, the Selection Grab dialog appears. Use the mouse pointer to drag a box around the portion of the screen you want to capture . Release the mouse button to capture the screen. + * Window ``(Shift+Ctrl+W)`` enables you to capture a window. When you choose this option, the Window Grab dialog appears. Click the Choose Window button, then click the window you want to capture. * Screen ``(Ctrl+Z)`` enables you to capture the entire screen. When you choose this option, the Screen Grab dialog appears. Click outside the dialog to capture the screen. - * Timed Screen ``(Maj+Ctrl+Z)`` enables you to capture the entire screen after a ten-second delay. When you choose this option, the Timed Screen Grab dialog appears. Click the Start Timer button, then activate the program you want to capture and arrange onscreen elements as desired. In ten seconds, the screen is captured. + * Timed Screen ``(Shift+Ctrl+Z)`` enables you to capture the entire screen after a ten-second delay. When you choose this option, the Timed Screen Grab dialog appears. Click the Start Timer button, then activate the program you want to capture and arrange screen elements as desired. In ten seconds, the screen is captured. 4. ``Grab`` makes a camera shutter sound as it captures the screen. The image appears in an untitled document window. -5. If you are satisfied with the screenshot, choose File > Save (Figure 7) or press (Ctrl+S) and use the Save As dialog sheet that appears to save it as a file on disk or if you are not satisfied with the screenshot, choose File > Close (Figure 8) or press (Ctrl+W) to close the window. In the Close dialog sheet, click Don't Save. +5. If you are satisfied with the screenshot, choose ``File > Save`` or press ``(Ctrl+S)`` and use the Save As dialog sheet that appears to save it as a file on disk or if you are not satisfied with the screenshot, choose ``File > Close`` or press ``(Ctrl+W)`` to close the window. In the Close dialog sheet, click Don't Save. -You can create screenshots without ``Grab``. Press (Maj+Ctrl+3) to capture the entire screen or (Maj+Ctrl+4) to capture a portion of the screen. The screenshot is automatically saved on the desktop as a PNG file. +You can create screenshots without ``Grab``. Press ``(Shift+Ctrl+3)`` to capture the entire screen or ``(Shift+Ctrl+4)`` to capture a portion of the screen. The screenshot is automatically saved on the desktop as a PNG file. From 49a61ef1931101bacd4da3c0656f44ff43658f32 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sat, 10 Feb 2024 21:39:08 +0100 Subject: [PATCH 04/36] Try to hide window when tacking a screenshot --- .../Grab.app/Resources/dialog_screen_grab.py | 12 +++++----- Under Construction/Grab.app/Resources/grab.py | 22 ++++++++++++++----- .../Resources/widget_transparent_window.py | 8 ++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 72ca94a8..f8b2add4 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,8 +1,8 @@ import os -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence -from PyQt5.QtWidgets import QDialog, QShortcut, qApp -from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QCloseEvent +from PyQt5.QtWidgets import QDialog, QShortcut, QApplication +from PyQt5.QtCore import pyqtSignal, Qt from dialog_screen_grab_ui import Ui_ScreenGrab @@ -43,9 +43,11 @@ def __init__(self, parent=None): # def focusOutEvent(self, event): # self.screen_dialog_signal_quit.emit() - def closeEvent(self, event): + + def closeEvent(self, event: QCloseEvent) -> None: + super(ScreenGrabDialog, self).setWindowOpacity(0.0) super(ScreenGrabDialog, self).closeEvent(event) - self.screen_dialog_signal_quit.emit() + QApplication.processEvents() event.accept() def screen_dialog_quit(self): diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 8e33a323..432fb127 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut -from PyQt5.QtGui import QPixmap, QIcon, QPainter, QImage +from PyQt5.QtGui import QPixmap, QIcon, QPainter, QHideEvent, QShowEvent from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer @@ -234,7 +234,8 @@ def new_timed_screenshot(self): def take_screenshot(self): - self.hide() + # self.hide() + self.setWindowOpacity(0.0) # Start by clean the last image self.img_preview.setImage(None) @@ -255,7 +256,10 @@ def take_screenshot(self): self.update_actions() - self.show() + # self.show() + self.setWindowOpacity(1.0) + # QApplication.processEvents() + def normal_size(self): self.img_preview.clearZoom() @@ -395,12 +399,18 @@ def _showScreenGrabDialog(self): while not self.windowHandle(): QApplication.processEvents() + def hideEvent(self, event: QShowEvent) -> None: + super(Window, self).setWindowOpacity(0.0) + super(Window, self).hideEvent(event) + event.accept() + def showEvent(self, event: QShowEvent) -> None: + super(Window, self).setWindowOpacity(1.0) + super(Window, self).showEvent(event) + event.accept() def _ScreenGrabStart(self): self._CloseAllDialogs() - - QApplication.processEvents() self.take_screenshot() def _showTimedScreenGrabDialog(self): @@ -434,6 +444,8 @@ def _CloseAllDialogs(self): while not self.windowHandle(): QApplication.processEvents() + QApplication.flush() + def _timer_change_for(self, value): self.timer_count = value diff --git a/Under Construction/Grab.app/Resources/widget_transparent_window.py b/Under Construction/Grab.app/Resources/widget_transparent_window.py index e9be56e0..aafbadc9 100644 --- a/Under Construction/Grab.app/Resources/widget_transparent_window.py +++ b/Under Construction/Grab.app/Resources/widget_transparent_window.py @@ -12,7 +12,7 @@ from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication, QShortcut -from PyQt5.QtGui import QMouseEvent, QKeySequence +from PyQt5.QtGui import QMouseEvent, QKeySequence, QCloseEvent import numpy @@ -42,6 +42,11 @@ def __init__(self, screen_grab=None, selection=None): quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.cancel_transparent_window) + def closeEvent(self, event: QCloseEvent) -> None: + super(TransWindow, self).setWindowOpacity(0.0) + super(TransWindow, self).closeEvent(event) + QApplication.processEvents() + event.accept() def mousePressEvent(self, event): event: QMouseEvent @@ -55,6 +60,7 @@ def mousePressEvent(self, event): def mouseReleaseEvent(self, event): self.transparent_window_signal_release.emit() + self.close() def cancel_transparent_window(self): self.transparent_window_signal_quit.emit() From 4e93a049a8772d65761dea488a43124879c9ce68 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sun, 11 Feb 2024 22:38:21 +0100 Subject: [PATCH 05/36] Selection work --- Under Construction/Grab.app/Resources/grab.py | 38 ++-- .../Resources/property_selection_area.py | 52 ++++++ .../Grab.app/Resources/widget_snapping.py | 170 ++++++++++-------- .../Resources/widget_transparent_window.py | 32 +++- 4 files changed, 196 insertions(+), 96 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/property_selection_area.py diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 432fb127..bc9caa5c 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut -from PyQt5.QtGui import QPixmap, QIcon, QPainter, QHideEvent, QShowEvent +from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut, qApp +from PyQt5.QtGui import QPixmap, QIcon, QPainter, QHideEvent, QShowEvent, QGuiApplication from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer @@ -20,7 +20,15 @@ class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(Window, self).__init__() + self.parent = parent + self.screen = qApp self.initialized = False + self.transparent_window_opacity = None + self.selection_color_background = None + self.selection_color_border = None + self.selection_color_opacity = None + self.selection_wight_border = None + self.settings = None self.fileName = None self.printerObj = None @@ -42,6 +50,8 @@ def __init__(self, parent=None): self.initialState() self.initialized = True + + def initialState(self): self.ActionMenuFilePrint.setEnabled(False) @@ -76,7 +86,7 @@ def setupCustomUi(self): self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - self.snippingWidget = SnippingWidget(app=QApplication.instance()) + self.snippingWidget = SnippingWidget() self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted self.sound = QMediaPlayer() @@ -84,6 +94,8 @@ def setupCustomUi(self): os.path.join(os.path.dirname(__file__), "trigger_of_camera.wav") ))) + self.transparent_window_opacity = 1.0 + def setupCustomUiGroups(self): menu_frequency_group = QActionGroup(self) @@ -144,13 +156,6 @@ def onSnippingCompleted(self, frame): if frame is None: return - # image = QImage(frame, frame.shape[1], frame.shape[0], frame.strides[0], QImage.Format_RGB888) - # pixmap = QPixmap.fromImage(image) - - # Start by clean the last image - self.img_preview.setImage(None) - - # Take a screenshot in case the pixmap will stay to Null self.img_preview.setImage(frame) # Inform the application about the contain change @@ -163,7 +168,6 @@ def onSnippingCompleted(self, frame): self.update_actions() - def snipArea(self): self.setWindowState(Qt.WindowMinimized) self.snippingWidget.start() @@ -244,7 +248,17 @@ def take_screenshot(self): self.sound.play() # Take a screenshot in case the pixmap will stay to Null - self.img_preview.setImage(QApplication.primaryScreen().grabWindow(0)) + screen = self.screen.primaryScreen() + win_id = QApplication.desktop().winId() + window = self.windowHandle() + if window: + screen = window.screen() + if not screen: + self.setWindowOpacity(1.0) + return + + self.img_preview.setImage(screen.grabWindow(win_id)) + self.fit_to_window() # Inform the application about the contain change if self.fileName: diff --git a/Under Construction/Grab.app/Resources/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py new file mode 100644 index 00000000..30b0bcc9 --- /dev/null +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -0,0 +1,52 @@ + +from PyQt5.QtCore import Qt, QPoint, QRectF, QRect + +class SelectionArea(object): + + def __init__(self): + self.__x = None + self.__y = None + self.__width = None + self.__height = None + + @property + def x(self): + return self.__x + + @x.setter + def x(self, x: float): + if x != self.x: + self.__x = x + + @property + def y(self): + return self.__y + + @y.setter + def y(self, y: float): + if y != self.y: + self.__y = y + + @property + def width(self): + return self.__width + + @width.setter + def width(self, width: float): + if width != self.width: + self.__width = width + + @property + def height(self): + return self.__height + + @height.setter + def height(self, height: float): + if height != self.height: + self.__height = height + + def setFromQRectF(self, req: QRectF): + self.x = req.x() + self.y = req.y() + self.width = req.width() + self.height = req.height() diff --git a/Under Construction/Grab.app/Resources/widget_snapping.py b/Under Construction/Grab.app/Resources/widget_snapping.py index b33b1500..a8a2adba 100644 --- a/Under Construction/Grab.app/Resources/widget_snapping.py +++ b/Under Construction/Grab.app/Resources/widget_snapping.py @@ -1,42 +1,63 @@ -from PyQt5.QtWidgets import QApplication, QWidget -from PyQt5.QtGui import QPainter, QPen, QColor, QCursor, QPixmap +from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut +from PyQt5.QtGui import QPainter, QPen, QColor, QCursor, QKeySequence from PyQt5.QtCore import Qt, QPoint, QRectF +from property_selection_area import SelectionArea # Refer to https://github.com/harupy/snipping-tool class SnippingWidget(QWidget): - is_snipping = False - def __init__(self, parent=None, app=None): + def __init__(self, parent=None): super(SnippingWidget, self).__init__() self.parent = parent + + self.begin = None + self.end = None + self.onSnippingCompleted = None + self.is_snipping = None + self.qp = None + self.selection_info = None + self.win_id = None + self.screen = None + + self.initialState() + + def initialState(self): + self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowFlags(Qt.WindowStaysOnTopHint) + self.setWindowFlags(Qt.FramelessWindowHint) + + + - self.screen = app.primaryScreen() - self.setGeometry(0, 0, self.screen.size().width(), self.screen.size().height()) self.begin = QPoint() self.end = QPoint() - self.onSnippingCompleted = None + self.qp = QPainter() + self.selection_info = SelectionArea() - self.window_background_color = None - self.window_opacity = None + self.is_snipping = False + self.UpdateScreen() + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.cancel_widget_snapping) - self.selection_color_background = None - self.selection_color_border = None - self.selection_opacity = None - - self.setUp() + def cancel_widget_snapping(self): + self.is_snipping = False + self.begin = QPoint() + self.end = QPoint() + if self.onSnippingCompleted is not None: + self.onSnippingCompleted(None) + self.close() @property def SelectionColorBackground(self): - if SnippingWidget.is_snipping: + if self.is_snipping: return QColor(127, 127, 127, 127) else: return QColor(0, 0, 0, 0) @property def SelectionPen(self): - if SnippingWidget.is_snipping: + if self.is_snipping: return QPen( self.SelectionColorBackground, 1, @@ -53,57 +74,64 @@ def SelectionPen(self): Qt.RoundJoin ) - def setUp(self): - # Selection - # Color - self.setAttribute(Qt.WA_TranslucentBackground) - self.setWindowFlags(Qt.FramelessWindowHint) - self.setStyleSheet("background-color: rgba(255,255,255, 95%);") - self.selection_color_background = QColor(127, 127, 127, 127) - self.selection_color_border = QColor(127, 127, 127, 127) - # Pen + def UpdateScreen(self): + self.screen = qApp.primaryScreen() + self.win_id = QApplication.desktop().winId() + + self.setGeometry( + 0, + 0, + self.screen.size().width(), + self.screen.size().height() + ) + window = self.windowHandle() + if window: + self.screen = window.screen() def fullscreen(self): + self.UpdateScreen() + if not self.screen: + return + try: - img = QPixmap.grabWindow(QApplication.desktop().winId(), - 0, - 0, - self.screen.size().width(), - self.screen.size().height()) - except: + img = self.screen.grabWindow(self.win_id, + 0, + 0, + self.screen.size().width(), + self.screen.size().height() + ) + except (Exception, BaseException): img = None if self.onSnippingCompleted is not None: self.onSnippingCompleted(img) def start(self): - SnippingWidget.is_snipping = True + self.UpdateScreen() + self.is_snipping = True # self.setWindowOpacity(0.3) QApplication.setOverrideCursor(QCursor(Qt.CrossCursor)) - self.show() + self.showFullScreen() def paintEvent(self, event): - qp = QPainter(self) - if SnippingWidget.is_snipping: - # brush_color = (128, 128, 128, 100) - lw = 1 - opacity = 1.0 - else: - self.begin = QPoint() - self.end = QPoint() - brush_color = (0, 0, 0, 0) - lw = 0 - opacity = 0.0 - self.setWindowOpacity(opacity) + if self.isVisible(): + self.qp.begin(self) + if not self.is_snipping: + self.begin = QPoint() + self.end = QPoint() + # self.setWindowOpacity(0.0) - # self.setWindowOpacity(opacity) + # self.setWindowOpacity(opacity) - qp.setPen(self.SelectionPen) - qp.setBrush(self.SelectionColorBackground) + self.qp.setPen(self.SelectionPen) + self.qp.setBrush(self.SelectionColorBackground) + rect = QRectF(self.begin, self.end) + self.qp.drawRect(rect) + self.qp.end() - rect = QRectF(self.begin, self.end) - qp.drawRect(rect) + if rect.width() > 0 and rect.height() > 0: + self.selection_info.setFromQRectF(rect) def mousePressEvent(self, event): self.begin = event.pos() @@ -115,37 +143,25 @@ def mouseMoveEvent(self, event): self.update() def mouseReleaseEvent(self, event): - SnippingWidget.is_snipping = False - QApplication.restoreOverrideCursor() - x1 = min(self.begin.x(), self.end.x()) - y1 = min(self.begin.y(), self.end.y()) - x2 = max(self.begin.x(), self.end.x()) - y2 = max(self.begin.y(), self.end.y()) + self.UpdateScreen() + if not self.screen: + return + self.is_snipping = False + QApplication.restoreOverrideCursor() self.repaint() QApplication.processEvents() - # img = ImageGrab.grab(bbox=(x1, y1, x2, y2)) - - QApplication.primaryScreen().grabWindow(0) - # img = QPixmap.grabWindow(QApplication.desktop().winId(), - # x1, - # y1, - # x2, - # y2) - img = QApplication.screens()[0].grabWindow( - QApplication.desktop().winId(), x1, - y1, - x2, - y2) - # try: - # img = QPixmap.grabWindow(QApplication.desktop().winId(), - # x1, - # y1, - # x2, - # y2) - # except: - # img = None + try: + img = self.screen.grabWindow( + self.win_id, + self.selection_info.x, + self.selection_info.y, + self.selection_info.width, + self.selection_info.height, + ) + except (Exception, BaseException): + img = None if self.onSnippingCompleted is not None: self.onSnippingCompleted(img) diff --git a/Under Construction/Grab.app/Resources/widget_transparent_window.py b/Under Construction/Grab.app/Resources/widget_transparent_window.py index aafbadc9..c8560c59 100644 --- a/Under Construction/Grab.app/Resources/widget_transparent_window.py +++ b/Under Construction/Grab.app/Resources/widget_transparent_window.py @@ -1,18 +1,36 @@ try: from PyQt6.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize - from PyQt6.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen - from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication + from PyQt6.QtGui import ( + QImage, + QPixmap, + QPainterPath, + QMouseEvent, + QPainter, + QPen, + QGuiApplication, + QKeySequence, + QCloseEvent + ) + from PyQt6.QtWidgets import QWidget, QGridLayout, QApplication, QShortcut except ImportError: try: from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize - from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen, QGuiApplication - from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication + from PyQt5.QtGui import ( + QImage, + QPixmap, + QPainterPath, + QMouseEvent, + QPainter, + QPen, + QGuiApplication, + QKeySequence, + QCloseEvent + ) + from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication, QShortcut except ImportError: raise ImportError("Requires PyQt (version 5 or 6)") -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication, QShortcut -from PyQt5.QtGui import QMouseEvent, QKeySequence, QCloseEvent + import numpy From fa8faa9d8ede7f2cef3a339d1a61e06f58a58ceb Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sun, 11 Feb 2024 23:02:12 +0100 Subject: [PATCH 06/36] Create Window Grab and Selection Grab dialog --- .../Resources/dialog_selection_grab.ui | 214 +++++++++++++++++ .../Grab.app/Resources/dialog_window_grab.ui | 221 ++++++++++++++++++ .../Update.app/Resources/update.py | 10 +- 3 files changed, 440 insertions(+), 5 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/dialog_selection_grab.ui create mode 100644 Under Construction/Grab.app/Resources/dialog_window_grab.ui diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui new file mode 100644 index 00000000..3d1a1da3 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -0,0 +1,214 @@ + + + ScreenGrab + + + Qt::NonModal + + + + 0 + 0 + 486 + 178 + + + + Qt::NoFocus + + + Selection Grab + + + true + + + + + + 6 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../../../../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + Nimbus Sans + + + + Drag over the portion of the screen you want to capture. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + Cancel + + + + + + + + + + + + diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui new file mode 100644 index 00000000..f490386b --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -0,0 +1,221 @@ + + + ScreenGrab + + + Qt::NonModal + + + + 0 + 0 + 486 + 178 + + + + Qt::NoFocus + + + Window Grab + + + true + + + + + + 6 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../../../../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + + Nimbus Sans + + + + When the window you want is ready, click Choose Window, then click the window to capture it. + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + Cancel + + + + + + + Choose Window + + + + + + + + + + + + diff --git a/Under Construction/Update.app/Resources/update.py b/Under Construction/Update.app/Resources/update.py index 673ea7c6..5d21d2bb 100755 --- a/Under Construction/Update.app/Resources/update.py +++ b/Under Construction/Update.app/Resources/update.py @@ -376,11 +376,11 @@ def read_file_contents(self, filename): if __name__ == "__main__": # Logging - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', - datefmt='%m-%d %H:%M', - filename='/var/log/update.log', - filemode='w') + # logging.basicConfig(level=logging.DEBUG, + # format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', + # datefmt='%m-%d %H:%M', + # filename='/var/log/update.log', + # filemode='w') # Console handler for logging console = logging.StreamHandler() console.setLevel(logging.INFO) From 98da0427ccbc1e40442ab2d54dbb07053fee0ef5 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sun, 11 Feb 2024 23:02:23 +0100 Subject: [PATCH 07/36] Fixe size --- Under Construction/Grab.app/Resources/dialog_screen_grab.ui | 4 ++-- .../Grab.app/Resources/dialog_timed_screen_grab.ui | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index 54eb22fd..8cf0734e 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -9,8 +9,8 @@ 0 0 - 467 - 213 + 486 + 178 diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui index 1d6dcced..a09b0c95 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -9,8 +9,8 @@ 0 0 - 466 - 212 + 486 + 178 From 0104c00b22cb491e8d6a52be669bece825d7de2e Mon Sep 17 00:00:00 2001 From: Tuuux Date: Sun, 11 Feb 2024 23:30:46 +0100 Subject: [PATCH 08/36] Better is better --- .../Grab.app/Resources/dialog_screen_grab.py | 4 +- .../Grab.app/Resources/dialog_screen_grab.ui | 6 +- .../Resources/dialog_screen_grab_ui.py | 33 ++++--- .../Resources/dialog_selection_grab.ui | 4 +- .../Resources/dialog_selection_grab_ui.py | 93 ++++++++++++++++++ .../Resources/dialog_selection_screen_grab.py | 34 +++++++ .../Resources/dialog_timed_screen_grab.py | 4 +- .../Resources/dialog_timed_screen_grab.ui | 4 +- .../Resources/dialog_timed_screen_grab_ui.py | 38 ++++---- .../Grab.app/Resources/dialog_window_grab.ui | 4 +- .../Resources/dialog_window_grab_ui.py | 97 +++++++++++++++++++ Under Construction/Grab.app/Resources/grab.py | 8 +- 12 files changed, 276 insertions(+), 53 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py create mode 100644 Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py create mode 100644 Under Construction/Grab.app/Resources/dialog_window_grab_ui.py diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index f8b2add4..25c95690 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -3,7 +3,7 @@ from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QCloseEvent from PyQt5.QtWidgets import QDialog, QShortcut, QApplication from PyQt5.QtCore import pyqtSignal, Qt -from dialog_screen_grab_ui import Ui_ScreenGrab +from dialog_screen_grab_ui import Ui_ScreenGrabDialog class ScreenGrabDialog(QDialog): @@ -26,7 +26,7 @@ def __init__(self, parent=None): self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.setWindowModality(Qt.WindowModality.ApplicationModal) - self.ui = Ui_ScreenGrab() + self.ui = Ui_ScreenGrabDialog() self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setFixedSize(self.size()) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index 8cf0734e..b6882106 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -1,9 +1,9 @@ - ScreenGrab - + ScreenGrabDialog + - Qt::NonModal + Qt::WindowModal diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index 984bc0fb..0060c697 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -11,13 +11,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_ScreenGrab(object): - def setupUi(self, ScreenGrab): - ScreenGrab.setObjectName("ScreenGrab") - ScreenGrab.setWindowModality(QtCore.Qt.NonModal) - ScreenGrab.resize(467, 213) - ScreenGrab.setFocusPolicy(QtCore.Qt.NoFocus) - self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrab) +class Ui_ScreenGrabDialog(object): + def setupUi(self, ScreenGrabDialog): + ScreenGrabDialog.setObjectName("ScreenGrabDialog") + ScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) + ScreenGrabDialog.resize(486, 178) + ScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) + ScreenGrabDialog.setModal(True) + self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrabDialog) self.verticalLayout.setObjectName("verticalLayout") self.MainVbox = QtWidgets.QVBoxLayout() self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) @@ -30,7 +31,7 @@ def setupUi(self, ScreenGrab): self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) - self.icon = QtWidgets.QLabel(ScreenGrab) + self.icon = QtWidgets.QLabel(ScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -48,7 +49,7 @@ def setupUi(self, ScreenGrab): self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.Label = QtWidgets.QLabel(ScreenGrab) + self.Label = QtWidgets.QLabel(ScreenGrabDialog) font = QtGui.QFont() font.setFamily("Nimbus Sans") self.Label.setFont(font) @@ -68,7 +69,7 @@ def setupUi(self, ScreenGrab): self.horizontalLayout.setObjectName("horizontalLayout") spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) - self.button_cancel = QtWidgets.QPushButton(ScreenGrab) + self.button_cancel = QtWidgets.QPushButton(ScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -82,11 +83,11 @@ def setupUi(self, ScreenGrab): self.MainVbox.addLayout(self.horizontalLayout) self.verticalLayout.addLayout(self.MainVbox) - self.retranslateUi(ScreenGrab) - QtCore.QMetaObject.connectSlotsByName(ScreenGrab) + self.retranslateUi(ScreenGrabDialog) + QtCore.QMetaObject.connectSlotsByName(ScreenGrabDialog) - def retranslateUi(self, ScreenGrab): + def retranslateUi(self, ScreenGrabDialog): _translate = QtCore.QCoreApplication.translate - ScreenGrab.setWindowTitle(_translate("ScreenGrab", "Screen Grab")) - self.Label.setText(_translate("ScreenGrab", "To capture the screen, click outside this window. (This window will not be included in the screen capture.) If you selected a pointer in Grab preferences, the pointer will be superimposed where you click.")) - self.button_cancel.setText(_translate("ScreenGrab", "Cancel")) + ScreenGrabDialog.setWindowTitle(_translate("ScreenGrabDialog", "Screen Grab")) + self.Label.setText(_translate("ScreenGrabDialog", "To capture the screen, click outside this window. (This window will not be included in the screen capture.) If you selected a pointer in Grab preferences, the pointer will be superimposed where you click.")) + self.button_cancel.setText(_translate("ScreenGrabDialog", "Cancel")) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui index 3d1a1da3..aa3817ed 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -1,7 +1,7 @@ - ScreenGrab - + SelectionGrabDialog + Qt::NonModal diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py new file mode 100644 index 00000000..43e35e61 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './dialog_selection_grab.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_SelectionGrabDialog(object): + def setupUi(self, SelectionGrabDialog): + SelectionGrabDialog.setObjectName("SelectionGrabDialog") + SelectionGrabDialog.setWindowModality(QtCore.Qt.NonModal) + SelectionGrabDialog.resize(486, 178) + SelectionGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) + SelectionGrabDialog.setModal(True) + self.verticalLayout = QtWidgets.QVBoxLayout(SelectionGrabDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.MainVbox = QtWidgets.QVBoxLayout() + self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + self.MainVbox.setSpacing(6) + self.MainVbox.setObjectName("MainVbox") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.icon = QtWidgets.QLabel(SelectionGrabDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.icon.sizePolicy().hasHeightForWidth()) + self.icon.setSizePolicy(sizePolicy) + self.icon.setMinimumSize(QtCore.QSize(64, 64)) + self.icon.setMaximumSize(QtCore.QSize(64, 64)) + self.icon.setText("") + self.icon.setPixmap(QtGui.QPixmap("./../../../../../../Processes.app/Resources/Processes.png")) + self.icon.setScaledContents(True) + self.icon.setObjectName("icon") + self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.Label = QtWidgets.QLabel(SelectionGrabDialog) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.Label.setFont(font) + self.Label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.Label.setWordWrap(True) + self.Label.setObjectName("Label") + self.verticalLayout_2.addWidget(self.Label) + self.horizontalLayout_2.addLayout(self.verticalLayout_2) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem2) + self.MainVbox.addLayout(self.horizontalLayout_2) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + self.MainVbox.addItem(spacerItem3) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout.setSpacing(22) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem4) + self.button_cancel = QtWidgets.QPushButton(SelectionGrabDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.button_cancel.sizePolicy().hasHeightForWidth()) + self.button_cancel.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.button_cancel.setFont(font) + self.button_cancel.setObjectName("button_cancel") + self.horizontalLayout.addWidget(self.button_cancel) + self.MainVbox.addLayout(self.horizontalLayout) + self.verticalLayout.addLayout(self.MainVbox) + + self.retranslateUi(SelectionGrabDialog) + QtCore.QMetaObject.connectSlotsByName(SelectionGrabDialog) + + def retranslateUi(self, SelectionGrabDialog): + _translate = QtCore.QCoreApplication.translate + SelectionGrabDialog.setWindowTitle(_translate("SelectionGrabDialog", "Selection Grab")) + self.Label.setText(_translate("SelectionGrabDialog", "Drag over the portion of the screen you want to capture.")) + self.button_cancel.setText(_translate("SelectionGrabDialog", "Cancel")) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py new file mode 100644 index 00000000..bb47274c --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -0,0 +1,34 @@ +import os + +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent +from PyQt5.QtWidgets import QDialog, QShortcut +from PyQt5.QtCore import pyqtSignal, Qt +from dialog_selection_grab_ui import Ui_SelectionGrabDialog + + +class SelectionGrabDialog(QDialog): + selection_dialog_signal_quit = pyqtSignal() + selection_dialog_signal_start = pyqtSignal() + + def __init__(self, parent=None): + super(SelectionGrabDialog, self).__init__(parent) + self.setWindowFlags(Qt.Dialog) + + self.ui = Ui_SelectionGrabDialog() + self.ui.setupUi(self) + self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + + self.setFixedSize(self.size()) + + self.ui.button_cancel.clicked.connect(self.cancel_dialog) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.cancel_dialog) + + def cancel_dialog(self): + self.selection_dialog_signal_quit.emit() + + def focusOutEvent(self, a0: QFocusEvent) -> None: + self.selection_dialog_signal_start.emit() + + diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index fe0d0d0f..797b6bb1 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -3,7 +3,7 @@ from PyQt5.QtGui import QPixmap, QIcon, QKeySequence from PyQt5.QtWidgets import QDialog, QShortcut from PyQt5.QtCore import pyqtSignal, Qt -from dialog_timed_screen_grab_ui import Ui_TimedScreenGrab +from dialog_timed_screen_grab_ui import Ui_TimedScreenGrabDialog class TimedScreenGrabDialog(QDialog): @@ -14,7 +14,7 @@ def __init__(self, parent=None, timer=None): super(TimedScreenGrabDialog, self).__init__(parent) self.setWindowFlags(Qt.Dialog) self.sec = int(timer / 1000) - self.ui = Ui_TimedScreenGrab() + self.ui = Ui_TimedScreenGrabDialog() self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui index a09b0c95..311fd05c 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -1,7 +1,7 @@ - TimedScreenGrab - + TimedScreenGrabDialog + Qt::NonModal diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py index 8a3f7e88..d8976da3 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py @@ -11,14 +11,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets -class Ui_TimedScreenGrab(object): - def setupUi(self, TimedScreenGrab): - TimedScreenGrab.setObjectName("TimedScreenGrab") - TimedScreenGrab.setWindowModality(QtCore.Qt.NonModal) - TimedScreenGrab.resize(466, 212) - TimedScreenGrab.setFocusPolicy(QtCore.Qt.NoFocus) - TimedScreenGrab.setModal(True) - self.verticalLayout = QtWidgets.QVBoxLayout(TimedScreenGrab) +class Ui_TimedScreenGrabDialog(object): + def setupUi(self, TimedScreenGrabDialog): + TimedScreenGrabDialog.setObjectName("TimedScreenGrabDialog") + TimedScreenGrabDialog.setWindowModality(QtCore.Qt.NonModal) + TimedScreenGrabDialog.resize(486, 178) + TimedScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) + TimedScreenGrabDialog.setModal(True) + self.verticalLayout = QtWidgets.QVBoxLayout(TimedScreenGrabDialog) self.verticalLayout.setObjectName("verticalLayout") self.MainVbox = QtWidgets.QVBoxLayout() self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) @@ -31,7 +31,7 @@ def setupUi(self, TimedScreenGrab): self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) - self.icon = QtWidgets.QLabel(TimedScreenGrab) + self.icon = QtWidgets.QLabel(TimedScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -49,7 +49,7 @@ def setupUi(self, TimedScreenGrab): self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.Label = QtWidgets.QLabel(TimedScreenGrab) + self.Label = QtWidgets.QLabel(TimedScreenGrabDialog) font = QtGui.QFont() font.setFamily("Nimbus Sans") self.Label.setFont(font) @@ -69,7 +69,7 @@ def setupUi(self, TimedScreenGrab): self.horizontalLayout.setObjectName("horizontalLayout") spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) - self.button_cancel = QtWidgets.QPushButton(TimedScreenGrab) + self.button_cancel = QtWidgets.QPushButton(TimedScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -80,7 +80,7 @@ def setupUi(self, TimedScreenGrab): self.button_cancel.setFont(font) self.button_cancel.setObjectName("button_cancel") self.horizontalLayout.addWidget(self.button_cancel) - self.button_start_timer = QtWidgets.QPushButton(TimedScreenGrab) + self.button_start_timer = QtWidgets.QPushButton(TimedScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -95,12 +95,12 @@ def setupUi(self, TimedScreenGrab): self.MainVbox.addLayout(self.horizontalLayout) self.verticalLayout.addLayout(self.MainVbox) - self.retranslateUi(TimedScreenGrab) - QtCore.QMetaObject.connectSlotsByName(TimedScreenGrab) + self.retranslateUi(TimedScreenGrabDialog) + QtCore.QMetaObject.connectSlotsByName(TimedScreenGrabDialog) - def retranslateUi(self, TimedScreenGrab): + def retranslateUi(self, TimedScreenGrabDialog): _translate = QtCore.QCoreApplication.translate - TimedScreenGrab.setWindowTitle(_translate("TimedScreenGrab", "Timed Screen Grab")) - self.Label.setText(_translate("TimedScreenGrab", "Grab will caputure the screen %s after you start the timer. (This window will not be included in the screen capture.)")) - self.button_cancel.setText(_translate("TimedScreenGrab", "Cancel")) - self.button_start_timer.setText(_translate("TimedScreenGrab", "Start Timer")) + TimedScreenGrabDialog.setWindowTitle(_translate("TimedScreenGrabDialog", "Timed Screen Grab")) + self.Label.setText(_translate("TimedScreenGrabDialog", "Grab will caputure the screen %s after you start the timer. (This window will not be included in the screen capture.)")) + self.button_cancel.setText(_translate("TimedScreenGrabDialog", "Cancel")) + self.button_start_timer.setText(_translate("TimedScreenGrabDialog", "Start Timer")) diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui index f490386b..2cfcfb89 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -1,7 +1,7 @@ - ScreenGrab - + WindowGrabDialog + Qt::NonModal diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py new file mode 100644 index 00000000..f3587890 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './dialog_window_grab.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_WindowGrabDialog(object): + def setupUi(self, WindowGrabDialog): + WindowGrabDialog.setObjectName("WindowGrabDialog") + WindowGrabDialog.setWindowModality(QtCore.Qt.NonModal) + WindowGrabDialog.resize(486, 178) + WindowGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) + WindowGrabDialog.setModal(True) + self.verticalLayout = QtWidgets.QVBoxLayout(WindowGrabDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.MainVbox = QtWidgets.QVBoxLayout() + self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + self.MainVbox.setSpacing(6) + self.MainVbox.setObjectName("MainVbox") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.icon = QtWidgets.QLabel(WindowGrabDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.icon.sizePolicy().hasHeightForWidth()) + self.icon.setSizePolicy(sizePolicy) + self.icon.setMinimumSize(QtCore.QSize(64, 64)) + self.icon.setMaximumSize(QtCore.QSize(64, 64)) + self.icon.setText("") + self.icon.setPixmap(QtGui.QPixmap("./../../../../../../Processes.app/Resources/Processes.png")) + self.icon.setScaledContents(True) + self.icon.setObjectName("icon") + self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setSpacing(0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.Label = QtWidgets.QLabel(WindowGrabDialog) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.Label.setFont(font) + self.Label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.Label.setWordWrap(True) + self.Label.setObjectName("Label") + self.verticalLayout_2.addWidget(self.Label) + self.horizontalLayout_2.addLayout(self.verticalLayout_2) + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem2) + self.MainVbox.addLayout(self.horizontalLayout_2) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + self.MainVbox.addItem(spacerItem3) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) + self.horizontalLayout.setSpacing(22) + self.horizontalLayout.setObjectName("horizontalLayout") + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem4) + self.button_cancel = QtWidgets.QPushButton(WindowGrabDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.button_cancel.sizePolicy().hasHeightForWidth()) + self.button_cancel.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setFamily("Nimbus Sans") + self.button_cancel.setFont(font) + self.button_cancel.setObjectName("button_cancel") + self.horizontalLayout.addWidget(self.button_cancel) + self.button_choose_window = QtWidgets.QPushButton(WindowGrabDialog) + self.button_choose_window.setObjectName("button_choose_window") + self.horizontalLayout.addWidget(self.button_choose_window) + self.MainVbox.addLayout(self.horizontalLayout) + self.verticalLayout.addLayout(self.MainVbox) + + self.retranslateUi(WindowGrabDialog) + QtCore.QMetaObject.connectSlotsByName(WindowGrabDialog) + + def retranslateUi(self, WindowGrabDialog): + _translate = QtCore.QCoreApplication.translate + WindowGrabDialog.setWindowTitle(_translate("WindowGrabDialog", "Window Grab")) + self.Label.setText(_translate("WindowGrabDialog", "When the window you want is ready, click Choose Window, then click the window to capture it.")) + self.button_cancel.setText(_translate("WindowGrabDialog", "Cancel")) + self.button_choose_window.setText(_translate("WindowGrabDialog", "Choose Window")) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index bc9caa5c..98d66dcc 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -155,7 +155,7 @@ def onSnippingCompleted(self, frame): self.setWindowState(Qt.WindowActive) if frame is None: return - + self.sound.play() self.img_preview.setImage(frame) # Inform the application about the contain change @@ -238,9 +238,7 @@ def new_timed_screenshot(self): def take_screenshot(self): - # self.hide() - self.setWindowOpacity(0.0) - + self.setWindowState(Qt.WindowMinimized) # Start by clean the last image self.img_preview.setImage(None) @@ -271,7 +269,7 @@ def take_screenshot(self): self.update_actions() # self.show() - self.setWindowOpacity(1.0) + self.setWindowState(Qt.WindowActive) # QApplication.processEvents() From 987e5247a426a806189ebf0520fa06bd597a3d71 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Mon, 12 Feb 2024 17:35:35 +0100 Subject: [PATCH 09/36] Better Title management and Selection work on multiscreen --- .../Grab.app/Resources/QtImageViewer.py | 11 +- Under Construction/Grab.app/Resources/grab.py | 92 +++-------- .../Grab.app/Resources/main_window.ui | 15 +- .../Grab.app/Resources/main_window_ui.py | 9 +- .../Grab.app/Resources/widget_snapping.py | 143 ++++++++++++------ 5 files changed, 141 insertions(+), 129 deletions(-) diff --git a/Under Construction/Grab.app/Resources/QtImageViewer.py b/Under Construction/Grab.app/Resources/QtImageViewer.py index beffc1c5..1a2e413d 100644 --- a/Under Construction/Grab.app/Resources/QtImageViewer.py +++ b/Under Construction/Grab.app/Resources/QtImageViewer.py @@ -5,14 +5,14 @@ import os.path try: - from PyQt6.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize - from PyQt6.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen + from PyQt6.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize, QMargins + from PyQt6.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen, QGuiApplication, QPalette, QColor from PyQt6.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QSizePolicy, \ QGraphicsItem, QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsLineItem, QGraphicsPolygonItem except ImportError: try: - from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize - from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen, QGuiApplication + from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize, QMargins + from PyQt5.QtGui import QImage, QPixmap, QPainterPath, QMouseEvent, QPainter, QPen, QGuiApplication, QPalette, QColor from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QFileDialog, QSizePolicy, \ QGraphicsItem, QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsLineItem, QGraphicsPolygonItem except ImportError: @@ -160,6 +160,9 @@ def __init__(self, parent=None): self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.setBackgroundBrush(Qt.darkGray) + + def sizeHint(self): return QSize(900, 600) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 98d66dcc..61c84c88 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut, qApp -from PyQt5.QtGui import QPixmap, QIcon, QPainter, QHideEvent, QShowEvent, QGuiApplication -from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl +from PyQt5.QtGui import QPixmap, QIcon, QPainter, QHideEvent, QShowEvent, QPalette, QColor +from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl, QMargins from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer import sys @@ -17,6 +17,8 @@ from widget_snapping import SnippingWidget QLoggingCategory.setFilterRules("*.debug=false\nqt.qpa.*=false") + + class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(Window, self).__init__() @@ -50,10 +52,7 @@ def __init__(self, parent=None): self.initialState() self.initialized = True - - def initialState(self): - self.ActionMenuFilePrint.setEnabled(False) self.ActionMenuFilePrintSetup.setEnabled(False) self.ActionMenuViewFitToWindow.setEnabled(False) @@ -73,11 +72,11 @@ def initialState(self): self.img_preview.panButton = Qt.MouseButton.MiddleButton # set to None to disable self.timer_count = 10000 - self.setWindowTitle("Grab - new document[*]") + self.setWindowTitle("Untitled[*]") self.resize(370, 270) self.settings = QSettings("helloSystem", "Grab.app") self.read_settings() - self.take_screenshot() + self.snipFull() def setupCustomUi(self): # creating an object of the QPrinter class @@ -90,13 +89,12 @@ def setupCustomUi(self): self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted self.sound = QMediaPlayer() - self.sound.setMedia(QMediaContent(QUrl.fromLocalFile( - os.path.join(os.path.dirname(__file__), "trigger_of_camera.wav") - ))) + self.sound.setMedia( + QMediaContent(QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__), "trigger_of_camera.wav"))) + ) self.transparent_window_opacity = 1.0 - def setupCustomUiGroups(self): menu_frequency_group = QActionGroup(self) menu_frequency_group.addAction(self.ActionUpdateTimerTo1Sec) @@ -153,16 +151,17 @@ def connectSignalsSlots(self): def onSnippingCompleted(self, frame): self.setWindowState(Qt.WindowActive) + if frame is None: return + self.sound.play() + self.img_preview.setImage(frame) + self.img_preview.clearZoom() - # Inform the application about the contain change - if self.fileName: - self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) - else: - self.setWindowTitle("Grab - new document[*]") + self.fileName = None + self.setWindowTitle("Untitled[*]") self.setWindowModified(True) @@ -209,7 +208,7 @@ def save(self): self.img_preview.pixmap().save(self.fileName, "png") elif self.fileName[-3:] == "jpg": self.img_preview.pixmap().save(self.fileName, "jpg") - self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) + self.setWindowTitle("%s[*]" % (os.path.basename(self.fileName))) self.setWindowModified(False) def save_as(self): @@ -224,7 +223,7 @@ def save_as(self): elif fileName[-3:] == "jpg": self.img_preview.pixmap().save(fileName, "jpg") self.fileName = fileName - self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) + self.setWindowTitle("%s[*]" % (os.path.basename(self.fileName))) self.setWindowModified(False) def copy_to_clipboard(self): @@ -234,44 +233,7 @@ def copy_to_clipboard(self): QApplication.clipboard().setImage(qi) def new_timed_screenshot(self): - QTimer.singleShot(self.timer_count, self.take_screenshot) - - def take_screenshot(self): - - self.setWindowState(Qt.WindowMinimized) - # Start by clean the last image - self.img_preview.setImage(None) - - if self.initialized: - self.sound.play() - - # Take a screenshot in case the pixmap will stay to Null - screen = self.screen.primaryScreen() - win_id = QApplication.desktop().winId() - window = self.windowHandle() - if window: - screen = window.screen() - if not screen: - self.setWindowOpacity(1.0) - return - - self.img_preview.setImage(screen.grabWindow(win_id)) - self.fit_to_window() - - # Inform the application about the contain change - if self.fileName: - self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) - else: - self.setWindowTitle("Grab - new document[*]") - - self.setWindowModified(True) - - self.update_actions() - - # self.show() - self.setWindowState(Qt.WindowActive) - # QApplication.processEvents() - + QTimer.singleShot(self.timer_count, self.snipFull) def normal_size(self): self.img_preview.clearZoom() @@ -291,7 +253,6 @@ def fit_to_window(self): # defining the method to update the actions def update_actions(self): if self.img_preview.pixmap().isNull(): - self.ActionMenuFileSave.setEnabled(False) self.ActionMenuFileSaveAs.setEnabled(False) self.ActionMenuFilePrint.setEnabled(False) @@ -303,7 +264,6 @@ def update_actions(self): self.ActionMenuViewZoomOut.setEnabled(False) self.ActionMenuViewZoomToSelection.setEnabled(False) else: - self.ActionMenuFileSave.setEnabled(True) self.ActionMenuFileSaveAs.setEnabled(True) self.ActionMenuFilePrint.setEnabled(True) @@ -383,16 +343,11 @@ def _showAboutDialog(): msg.exec() def _showScreenGrabDialog(self): - if self.ActionMenuCaptureTimedScreen.isEnabled(): self.hide() # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) - - - - self.ScreenGrabDialog = ScreenGrabDialog(self) self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) @@ -407,7 +362,6 @@ def _showScreenGrabDialog(self): self.TransWindow.hide() self.TransWindow.show() - while not self.windowHandle(): QApplication.processEvents() @@ -423,7 +377,8 @@ def showEvent(self, event: QShowEvent) -> None: def _ScreenGrabStart(self): self._CloseAllDialogs() - self.take_screenshot() + self.snipFull() + # self.take_screenshot() def _showTimedScreenGrabDialog(self): if self.ActionMenuCaptureTimedScreen.isEnabled(): @@ -493,16 +448,19 @@ def _timer_change_for_10_secs(self): def _showHelpDialog(self): if self.ActionMenuHelpAbout.isEnabled(): - self.HelpDialog = HelpDialog() self.HelpDialog.show() + def main(): app = QApplication(sys.argv) + app.setApplicationName("Grab") + app.setApplicationDisplayName("Grab") + app.setApplicationVersion("0.1") window = Window() window.show() sys.exit(app.exec_()) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/Under Construction/Grab.app/Resources/main_window.ui b/Under Construction/Grab.app/Resources/main_window.ui index 016c50e8..a3757f83 100644 --- a/Under Construction/Grab.app/Resources/main_window.ui +++ b/Under Construction/Grab.app/Resources/main_window.ui @@ -17,16 +17,19 @@ Grab - + border: 0px; + + + - - background-color: rgba(255,255,255, 3%) - 0 + + QLayout::SetDefaultConstraint + 0 @@ -112,7 +115,7 @@ - View + Window @@ -123,8 +126,8 @@ - + diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index 0d14853d..a335512f 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -16,11 +16,12 @@ def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(601, 294) MainWindow.setAcceptDrops(True) - MainWindow.setStyleSheet("") + MainWindow.setStyleSheet("border: 0px;") + MainWindow.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)) self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setStyleSheet("background-color: rgba(255,255,255, 3%)") self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) + self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") @@ -177,8 +178,8 @@ def setupUi(self, MainWindow): self.menuView.addAction(self.ActionMenuViewZoomToSelection) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuEdit.menuAction()) - self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuCapture.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(MainWindow) @@ -192,7 +193,7 @@ def retranslateUi(self, MainWindow): self.menuCapture.setTitle(_translate("MainWindow", "Capture")) self.menuTimer.setTitle(_translate("MainWindow", "Timer")) self.menuHelp.setTitle(_translate("MainWindow", "Help")) - self.menuView.setTitle(_translate("MainWindow", "View")) + self.menuView.setTitle(_translate("MainWindow", "Window")) self.ActionMenuEditCopy.setText(_translate("MainWindow", "Copy")) self.ActionMenuEditCopy.setShortcut(_translate("MainWindow", "Ctrl+C")) self.ActionMenuHelpAbout.setText(_translate("MainWindow", "About")) diff --git a/Under Construction/Grab.app/Resources/widget_snapping.py b/Under Construction/Grab.app/Resources/widget_snapping.py index a8a2adba..0135e920 100644 --- a/Under Construction/Grab.app/Resources/widget_snapping.py +++ b/Under Construction/Grab.app/Resources/widget_snapping.py @@ -1,5 +1,5 @@ from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut -from PyQt5.QtGui import QPainter, QPen, QColor, QCursor, QKeySequence +from PyQt5.QtGui import QPainter, QPen, QColor, QCursor, QKeySequence, QFontMetrics, QFont, QPalette from PyQt5.QtCore import Qt, QPoint, QRectF from property_selection_area import SelectionArea @@ -17,18 +17,18 @@ def __init__(self, parent=None): self.is_snipping = None self.qp = None self.selection_info = None + self.selection_rect = None self.win_id = None self.screen = None + self.selection_pen_width = None self.initialState() def initialState(self): self.setAttribute(Qt.WA_TranslucentBackground) - self.setWindowFlags(Qt.WindowStaysOnTopHint) - self.setWindowFlags(Qt.FramelessWindowHint) - - - + # self.setWindowFlags(Qt.WindowStaysOnTopHint) + self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) + # self.setWindowFlags(Qt.FramelessWindowHint) self.begin = QPoint() self.end = QPoint() @@ -39,6 +39,10 @@ def initialState(self): self.UpdateScreen() quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.cancel_widget_snapping) + self.selection_pen_width = 2 + + def get_screen_size(self): + return self.screen.grabWindow(self.win_id).size() def cancel_widget_snapping(self): self.is_snipping = False @@ -51,16 +55,20 @@ def cancel_widget_snapping(self): @property def SelectionColorBackground(self): if self.is_snipping: - return QColor(127, 127, 127, 127) + color = QPalette().color(QPalette.Highlight) + color.setAlpha(0) + return color else: return QColor(0, 0, 0, 0) @property def SelectionPen(self): if self.is_snipping: + color = QPalette().color(QPalette.Base) + color.setAlpha(127) return QPen( - self.SelectionColorBackground, - 1, + color, + self.selection_pen_width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin @@ -77,13 +85,12 @@ def SelectionPen(self): def UpdateScreen(self): self.screen = qApp.primaryScreen() self.win_id = QApplication.desktop().winId() - - self.setGeometry( - 0, - 0, - self.screen.size().width(), - self.screen.size().height() - ) + # screenRect = QApplication.desktop().screenGeometry() + # self.setGeometry( + # screenRect + # ) + # self.move(0, 0) + self.resize(self.get_screen_size()) window = self.windowHandle() if window: @@ -95,12 +102,7 @@ def fullscreen(self): return try: - img = self.screen.grabWindow(self.win_id, - 0, - 0, - self.screen.size().width(), - self.screen.size().height() - ) + img = self.screen.grabWindow(self.win_id) except (Exception, BaseException): img = None @@ -112,26 +114,57 @@ def start(self): self.is_snipping = True # self.setWindowOpacity(0.3) QApplication.setOverrideCursor(QCursor(Qt.CrossCursor)) - self.showFullScreen() + self.show() def paintEvent(self, event): - if self.isVisible(): - self.qp.begin(self) - if not self.is_snipping: - self.begin = QPoint() - self.end = QPoint() - # self.setWindowOpacity(0.0) - # self.setWindowOpacity(opacity) + if not self.is_snipping: + self.begin = QPoint() + self.end = QPoint() + # self.setWindowOpacity(0.0) + self.qp.begin(self) + + # self.setWindowOpacity(opacity) + + self.qp.setPen(self.SelectionPen) + self.qp.setBrush(self.SelectionColorBackground) + rect = QRectF(self.begin, self.end) + self.qp.drawRect(rect) - self.qp.setPen(self.SelectionPen) - self.qp.setBrush(self.SelectionColorBackground) - rect = QRectF(self.begin, self.end) - self.qp.drawRect(rect) - self.qp.end() + font = QFont() + fm = QFontMetrics(font) + text = f"{int(abs(rect.width()))}, {int(abs(rect.height()))}" + pixelsWide = fm.width(text) + pixelsHigh = fm.height() + spacing = 5 - if rect.width() > 0 and rect.height() > 0: - self.selection_info.setFromQRectF(rect) + + + self.qp.setPen(QPen( + QColor(0, 0, 0, 255), + 1, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin + )) + self.qp.drawText( + self.end.x() - pixelsWide - spacing, + self.end.y() - spacing + pixelsHigh, + text + ) + self.qp.setBrush(Qt.NoBrush) + self.qp.drawRect( + QRectF( + self.end.x() - pixelsWide - (spacing * 2), + self.end.y() + ( spacing / 2), + pixelsWide + (spacing * 2), + pixelsHigh - ( self.selection_pen_width * 2) + ) + ) + if not rect.isNull(): + self.selection_rect = rect + self.selection_info.setFromQRectF(rect) + self.qp.end() def mousePressEvent(self, event): self.begin = event.pos() @@ -150,18 +183,32 @@ def mouseReleaseEvent(self, event): self.is_snipping = False QApplication.restoreOverrideCursor() self.repaint() - QApplication.processEvents() - try: - img = self.screen.grabWindow( - self.win_id, - self.selection_info.x, - self.selection_info.y, - self.selection_info.width, - self.selection_info.height, - ) - except (Exception, BaseException): - img = None + QApplication.processEvents() + self.hide() + print(self.selection_rect) + img = self.screen.grabWindow( + self.win_id, + + ).copy( + self.selection_rect.x(), + self.selection_rect.y(), + self.selection_info.width, + self.selection_info.height, + ) + self.show() + # try: + # self.hide() + # img = self.screen.grabWindow( + # self.win_id, + # self.selection_info.x, + # self.selection_info.y, + # self.selection_info.width, + # self.selection_info.height, + # ) + # self.show() + # except (Exception, BaseException): + # img = None if self.onSnippingCompleted is not None: self.onSnippingCompleted(img) From 81b9f747e8ce5eb32c11a0043609f0275c52f09a Mon Sep 17 00:00:00 2001 From: Tuuux Date: Wed, 14 Feb 2024 12:44:33 +0100 Subject: [PATCH 10/36] Better Title management and Selection work on multiscreen --- .../Grab.app/Resources/ArrowCursor.png | Bin 0 -> 171 bytes .../Grab.app/Resources/BlankCursor.png | Bin 0 -> 578 bytes .../Grab.app/Resources/ForbiddenCursor.png | Bin 0 -> 199 bytes .../Grab.app/Resources/IBeamCursor.png | Bin 0 -> 124 bytes .../Grab.app/Resources/OpenHandCursor.png | Bin 0 -> 160 bytes .../Grab.app/Resources/PointingHandCursor.png | Bin 0 -> 159 bytes .../Grab.app/Resources/UpArrowCursor.png | Bin 0 -> 132 bytes .../Grab.app/Resources/WhatsThisCursor.png | Bin 0 -> 191 bytes Under Construction/Grab.app/Resources/grab.py | 42 +++- .../Grab.app/Resources/main_window.ui | 7 + .../Grab.app/Resources/main_window_ui.py | 5 + .../Grab.app/Resources/preference_window.py | 93 ++++++++ .../Grab.app/Resources/preference_window.ui | 205 ++++++++++++++++++ .../Resources/preference_window_ui.py | 100 +++++++++ .../Resources/widget_screenshot_preview.py | 70 ------ .../Grab.app/Resources/windowlistmodel.py | 33 --- .../Grab.app/Resources/worker_snapshots.py | 29 --- 17 files changed, 446 insertions(+), 138 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/ArrowCursor.png create mode 100644 Under Construction/Grab.app/Resources/BlankCursor.png create mode 100644 Under Construction/Grab.app/Resources/ForbiddenCursor.png create mode 100644 Under Construction/Grab.app/Resources/IBeamCursor.png create mode 100644 Under Construction/Grab.app/Resources/OpenHandCursor.png create mode 100644 Under Construction/Grab.app/Resources/PointingHandCursor.png create mode 100644 Under Construction/Grab.app/Resources/UpArrowCursor.png create mode 100644 Under Construction/Grab.app/Resources/WhatsThisCursor.png create mode 100644 Under Construction/Grab.app/Resources/preference_window.py create mode 100644 Under Construction/Grab.app/Resources/preference_window.ui create mode 100644 Under Construction/Grab.app/Resources/preference_window_ui.py delete mode 100644 Under Construction/Grab.app/Resources/widget_screenshot_preview.py delete mode 100644 Under Construction/Grab.app/Resources/windowlistmodel.py delete mode 100644 Under Construction/Grab.app/Resources/worker_snapshots.py diff --git a/Under Construction/Grab.app/Resources/ArrowCursor.png b/Under Construction/Grab.app/Resources/ArrowCursor.png new file mode 100644 index 0000000000000000000000000000000000000000..a69ef4eb6158503c7c67c916aea86e65fdc81f72 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~)3?z59=Y9ZEoB=)|uK&RR$m{!Z_zjR^ED7=p zW^j0RBMrz2@N{tusfe>Zw~@EOfQR+sk_Oou&-fjcEVpj<&$!X3+c~E>J1*JV{%`IM zZgwlFFx}G*?uS>UO1?P#t>x;u4TbmfTg6u$kN3O$qTon=EX>4Tx04R}tkv&MmKpe$iQ>CI62P=p;WT;LS#fms;6^c-y)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1le0HI!Hs@X9CsG4P@ z;xRFkT@?eb5YU4GL@_QgQ=dyF6Yv~g_we!cF2b|C&;2=im7K`{pGZ8*bi*RvAfDc| zbk6(4Ay$+W;&b9LgDyz?$aUG}H_ioz1)do)(y4jk5V2TjW4Vo4(NKw}h{KAiQNECI zS>e3JSuIyt^Pc>L!JM|T%ypW>h+`2;NJ4~+DoQBBLWEY06cZ`hk9qiq9DkBrGPz1% zeSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{000_EL_t(o!|lNV0RR91f*}8|Zb(1@0002*1`r?s0hjkz Q*8l(j07*qoM6N<$f(4xFuK)l5 literal 0 HcmV?d00001 diff --git a/Under Construction/Grab.app/Resources/ForbiddenCursor.png b/Under Construction/Grab.app/Resources/ForbiddenCursor.png new file mode 100644 index 0000000000000000000000000000000000000000..2b08c4e2a3cafc992459a8f484e6c8e6c3e74857 GIT binary patch literal 199 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~)3?z59=Y9ZEoB=)|uK&RR$m{!Z_zjR^ED7=p zW^j0RBMr#O@pN$vsfa5*w~^P`kb@;)w}Ny_&ztDY@kvlRIOU`3qAsV; zTo+`Y{*UQrTXjS>sKxPHd$OYcrG25=M&E9}?0tT1Yy7oqDMB$hU$uYoMqOpDnka4~ qygb6(Z%*F(qx}EZO#W7L6_Nk?#pi+cGI+ZBxvXgTe~DWM4f DkdQT7 literal 0 HcmV?d00001 diff --git a/Under Construction/Grab.app/Resources/PointingHandCursor.png b/Under Construction/Grab.app/Resources/PointingHandCursor.png new file mode 100644 index 0000000000000000000000000000000000000000..d2004aefa7337edc3e15327992e1d69fda5b1831 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~)3?z59=Y9ZEoB=)|uK&RR$m{!Z_zjR^ED7=p zW^j0RBMr!L@pN$v;fPL7NDxSDXz&}>iqs75rOW+zd6_S+ yS|~6}xHpZ9x7T5rL59RLGuTpf8VsdEq+OT~Tw87C;38om{D5bL fNHFu^21W*zFY None: + self.preference_enable_sound = value + + def _preference_pointer_changed(self, value: int) -> None: + self.preference_pointer = value + def _showPreferenceWindow(self): + if self.ActionMenuEditPreference.isEnabled(): + self.PreferenceWindow = PreferenceWindow( + play_sound=self.preference_enable_sound, + pointer=self.preference_pointer + ) + self.PreferenceWindow.checkbox_enable_sound_changed.connect(self._preference_enable_sound_changed) + self.PreferenceWindow.buttongroup_changed.connect(self._preference_pointer_changed) + self.PreferenceWindow.show() + @staticmethod def _showAboutDialog(): msg = QMessageBox() @@ -352,15 +382,15 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) - self.TransWindow = TransWindow(self) - self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) - self.TransWindow.transparent_window_signal_quit.connect(self._CloseAllDialogs) + # self.TransWindow = TransWindow(self) + # self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) + # self.TransWindow.transparent_window_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.hide() self.ScreenGrabDialog.show() - self.TransWindow.hide() - self.TransWindow.show() + # self.TransWindow.hide() + # self.TransWindow.show() while not self.windowHandle(): QApplication.processEvents() diff --git a/Under Construction/Grab.app/Resources/main_window.ui b/Under Construction/Grab.app/Resources/main_window.ui index a3757f83..7c356899 100644 --- a/Under Construction/Grab.app/Resources/main_window.ui +++ b/Under Construction/Grab.app/Resources/main_window.ui @@ -79,6 +79,8 @@ + + @@ -423,6 +425,11 @@ Documentation + + + Preferences + + diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index a335512f..53fd0981 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -142,6 +142,8 @@ def setupUi(self, MainWindow): self.ActionUpdateTimerTo10Secs.setObjectName("ActionUpdateTimerTo10Secs") self.ActionMenuHelpDocumentation = QtWidgets.QAction(MainWindow) self.ActionMenuHelpDocumentation.setObjectName("ActionMenuHelpDocumentation") + self.ActionMenuEditPreference = QtWidgets.QAction(MainWindow) + self.ActionMenuEditPreference.setObjectName("ActionMenuEditPreference") self.menuFile.addAction(self.ActionMenuFileClose) self.menuFile.addAction(self.ActionMenuFileSave) self.menuFile.addAction(self.ActionMenuFileSaveAs) @@ -152,6 +154,8 @@ def setupUi(self, MainWindow): self.menuEdit.addAction(self.actionGrop) self.menuEdit.addAction(self.actionUndo) self.menuEdit.addAction(self.actionRedo) + self.menuEdit.addSeparator() + self.menuEdit.addAction(self.ActionMenuEditPreference) self.menuTimer.addAction(self.ActionUpdateTimerTo1Sec) self.menuTimer.addAction(self.ActionUpdateTimerTo2Secs) self.menuTimer.addAction(self.ActionUpdateTimerTo3Secs) @@ -239,4 +243,5 @@ def retranslateUi(self, MainWindow): self.ActionUpdateTimerTo9Secs.setText(_translate("MainWindow", "9 secs")) self.ActionUpdateTimerTo10Secs.setText(_translate("MainWindow", "10 secs")) self.ActionMenuHelpDocumentation.setText(_translate("MainWindow", "Documentation")) + self.ActionMenuEditPreference.setText(_translate("MainWindow", "Preferences")) from QtImageViewer import QtImageViewer diff --git a/Under Construction/Grab.app/Resources/preference_window.py b/Under Construction/Grab.app/Resources/preference_window.py new file mode 100644 index 00000000..fd784c63 --- /dev/null +++ b/Under Construction/Grab.app/Resources/preference_window.py @@ -0,0 +1,93 @@ +import os + +from PyQt5.QtGui import QPixmap, QKeySequence, QCursor, QIcon +from PyQt5.QtWidgets import QDialog, QShortcut, QWidget +from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot +from preference_window_ui import Ui_PreferenceWindow + + +class PreferenceWindow(QWidget): + checkbox_enable_sound_changed = pyqtSignal(object) + buttongroup_changed = pyqtSignal(object) + cursor_changed = pyqtSignal(object) + + def __init__(self, parent=None, pointer: int = None, play_sound: bool = None): + super(PreferenceWindow, self).__init__(parent) + + self.cursor_id = { + "ArrowCursor": Qt.ArrowCursor, + "BlankCursor": Qt.BlankCursor, + "ForbiddenCursor": Qt.ForbiddenCursor, + "IBeamCursor": Qt.IBeamCursor, + "OpenHandCursor": Qt.OpenHandCursor, + "PointingHandCursor": Qt.PointingHandCursor, + "UpArrowCursor": Qt.UpArrowCursor, + "WhatsThisCursor": Qt.WhatsThisCursor, + } + + self.cursor_name = { + Qt.ArrowCursor: "ArrowCursor", + Qt.BlankCursor: "BlankCursor", + Qt.ForbiddenCursor: "ForbiddenCursor", + Qt.IBeamCursor: "IBeamCursor", + Qt.OpenHandCursor: "OpenHandCursor", + Qt.PointingHandCursor: "PointingHandCursor", + Qt.UpArrowCursor: "UpArrowCursor", + Qt.WhatsThisCursor: "WhatsThisCursor", + } + + self.ui = Ui_PreferenceWindow() + self.ui.setupUi(self) + self.setup() + self.setupCustomUI() + + self.ui.checkbox_enable_sound.setChecked(play_sound) + self.select_cursor(pointer) + self.__play_sound = None + self.play_sound = play_sound + self.pointer = pointer + + + def select_cursor(self, cursor_id): + for button in self.ui.buttonGroup.buttons(): + if self.get_qt_cname_by_id(cursor_id) == button.objectName(): + button.setChecked(True) + + def setupCustomUI(self): + for button in self.ui.buttonGroup.buttons(): + if os.path.exists(os.path.join(os.path.dirname(__file__), f"{button.objectName()}.png")): + button.setIcon(QIcon(os.path.join(os.path.dirname(__file__), f"{button.objectName()}.png"))) + + def setup(self): + + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.setFocus() + self.ui.checkbox_enable_sound.toggled.connect(self.enable_sound_changed) + self.ui.buttonGroup.buttonClicked.connect(self.pointer_changed) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.preference_window_cancel) + + def preference_window_cancel(self) -> None: + self.close() + + def pointer_changed(self): + cursor_id = self.get_qt_cursor_id_by_name(self.ui.buttonGroup.checkedButton().objectName()) + cursor_name = self.get_qt_cname_by_id(cursor_id) + print(f"id: {cursor_id}, name: {cursor_name}") + self.buttongroup_changed.emit(cursor_id) + + def enable_sound_changed(self): + self.checkbox_enable_sound_changed.emit(self.ui.checkbox_enable_sound.isChecked()) + + def get_qt_cursor_id_by_name(self, name: str) -> int: + try: + return self.cursor_id[name] + except KeyError: + return Qt.BlankCursor + + def get_qt_cname_by_id(self, cursor_id: int) -> str: + try: + return self.cursor_name[cursor_id] + except KeyError: + return "BlankCursor" + diff --git a/Under Construction/Grab.app/Resources/preference_window.ui b/Under Construction/Grab.app/Resources/preference_window.ui new file mode 100644 index 00000000..9a324a7d --- /dev/null +++ b/Under Construction/Grab.app/Resources/preference_window.ui @@ -0,0 +1,205 @@ + + + PreferenceWindow + + + + 0 + 0 + 220 + 187 + + + + Preferences + + + + Grab.pngGrab.png + + + + + + Pointer Type + + + + + + ... + + + + 32 + 32 + + + + true + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + ... + + + + 32 + 32 + + + + true + + + buttonGroup + + + + + + + + + + CrossCursor + + + Enable Sound + + + true + + + + + + + + + + + diff --git a/Under Construction/Grab.app/Resources/preference_window_ui.py b/Under Construction/Grab.app/Resources/preference_window_ui.py new file mode 100644 index 00000000..39aecda5 --- /dev/null +++ b/Under Construction/Grab.app/Resources/preference_window_ui.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './preference_window.ui' +# +# Created by: PyQt5 UI code generator 5.15.9 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_PreferenceWindow(object): + def setupUi(self, PreferenceWindow): + PreferenceWindow.setObjectName("PreferenceWindow") + PreferenceWindow.resize(220, 187) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap("./Grab.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + PreferenceWindow.setWindowIcon(icon) + self.verticalLayout = QtWidgets.QVBoxLayout(PreferenceWindow) + self.verticalLayout.setObjectName("verticalLayout") + self.groupBox = QtWidgets.QGroupBox(PreferenceWindow) + self.groupBox.setObjectName("groupBox") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox) + self.gridLayout_2.setObjectName("gridLayout_2") + self.BlankCursor = QtWidgets.QToolButton(self.groupBox) + self.BlankCursor.setIconSize(QtCore.QSize(32, 32)) + self.BlankCursor.setCheckable(True) + self.BlankCursor.setChecked(True) + self.BlankCursor.setObjectName("BlankCursor") + self.buttonGroup = QtWidgets.QButtonGroup(PreferenceWindow) + self.buttonGroup.setObjectName("buttonGroup") + self.buttonGroup.addButton(self.BlankCursor) + self.gridLayout_2.addWidget(self.BlankCursor, 0, 0, 1, 1) + self.ArrowCursor = QtWidgets.QToolButton(self.groupBox) + self.ArrowCursor.setIconSize(QtCore.QSize(32, 32)) + self.ArrowCursor.setCheckable(True) + self.ArrowCursor.setObjectName("ArrowCursor") + self.buttonGroup.addButton(self.ArrowCursor) + self.gridLayout_2.addWidget(self.ArrowCursor, 0, 1, 1, 1) + self.IBeamCursor = QtWidgets.QToolButton(self.groupBox) + self.IBeamCursor.setIconSize(QtCore.QSize(32, 32)) + self.IBeamCursor.setCheckable(True) + self.IBeamCursor.setObjectName("IBeamCursor") + self.buttonGroup.addButton(self.IBeamCursor) + self.gridLayout_2.addWidget(self.IBeamCursor, 0, 2, 1, 1) + self.WhatsThisCursor = QtWidgets.QToolButton(self.groupBox) + self.WhatsThisCursor.setIconSize(QtCore.QSize(32, 32)) + self.WhatsThisCursor.setCheckable(True) + self.WhatsThisCursor.setObjectName("WhatsThisCursor") + self.buttonGroup.addButton(self.WhatsThisCursor) + self.gridLayout_2.addWidget(self.WhatsThisCursor, 0, 3, 1, 1) + self.UpArrowCursor = QtWidgets.QToolButton(self.groupBox) + self.UpArrowCursor.setIconSize(QtCore.QSize(32, 32)) + self.UpArrowCursor.setCheckable(True) + self.UpArrowCursor.setObjectName("UpArrowCursor") + self.buttonGroup.addButton(self.UpArrowCursor) + self.gridLayout_2.addWidget(self.UpArrowCursor, 1, 0, 1, 1) + self.ForbiddenCursor = QtWidgets.QToolButton(self.groupBox) + self.ForbiddenCursor.setIconSize(QtCore.QSize(32, 32)) + self.ForbiddenCursor.setCheckable(True) + self.ForbiddenCursor.setObjectName("ForbiddenCursor") + self.buttonGroup.addButton(self.ForbiddenCursor) + self.gridLayout_2.addWidget(self.ForbiddenCursor, 1, 1, 1, 1) + self.OpenHandCursor = QtWidgets.QToolButton(self.groupBox) + self.OpenHandCursor.setIconSize(QtCore.QSize(32, 32)) + self.OpenHandCursor.setCheckable(True) + self.OpenHandCursor.setObjectName("OpenHandCursor") + self.buttonGroup.addButton(self.OpenHandCursor) + self.gridLayout_2.addWidget(self.OpenHandCursor, 1, 2, 1, 1) + self.PointingHandCursor = QtWidgets.QToolButton(self.groupBox) + self.PointingHandCursor.setIconSize(QtCore.QSize(32, 32)) + self.PointingHandCursor.setCheckable(True) + self.PointingHandCursor.setObjectName("PointingHandCursor") + self.buttonGroup.addButton(self.PointingHandCursor) + self.gridLayout_2.addWidget(self.PointingHandCursor, 1, 3, 1, 1) + self.verticalLayout.addWidget(self.groupBox) + self.checkbox_enable_sound = QtWidgets.QCheckBox(PreferenceWindow) + self.checkbox_enable_sound.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor)) + self.checkbox_enable_sound.setChecked(True) + self.checkbox_enable_sound.setObjectName("checkbox_enable_sound") + self.verticalLayout.addWidget(self.checkbox_enable_sound) + + self.retranslateUi(PreferenceWindow) + QtCore.QMetaObject.connectSlotsByName(PreferenceWindow) + + def retranslateUi(self, PreferenceWindow): + _translate = QtCore.QCoreApplication.translate + PreferenceWindow.setWindowTitle(_translate("PreferenceWindow", "Preferences")) + self.groupBox.setTitle(_translate("PreferenceWindow", "Pointer Type")) + self.BlankCursor.setText(_translate("PreferenceWindow", "...")) + self.ArrowCursor.setText(_translate("PreferenceWindow", "...")) + self.IBeamCursor.setText(_translate("PreferenceWindow", "...")) + self.WhatsThisCursor.setText(_translate("PreferenceWindow", "...")) + self.UpArrowCursor.setText(_translate("PreferenceWindow", "...")) + self.ForbiddenCursor.setText(_translate("PreferenceWindow", "...")) + self.OpenHandCursor.setText(_translate("PreferenceWindow", "...")) + self.PointingHandCursor.setText(_translate("PreferenceWindow", "...")) + self.checkbox_enable_sound.setText(_translate("PreferenceWindow", "Enable Sound")) diff --git a/Under Construction/Grab.app/Resources/widget_screenshot_preview.py b/Under Construction/Grab.app/Resources/widget_screenshot_preview.py deleted file mode 100644 index 2dbd7b1f..00000000 --- a/Under Construction/Grab.app/Resources/widget_screenshot_preview.py +++ /dev/null @@ -1,70 +0,0 @@ -# importing libraries -from PyQt5.QtWidgets import ( - QWidget, QApplication, QLabel -) -from PyQt5.QtCore import ( - Qt, pyqtSignal, QTime, QTimer, QRect, QTime -) -from PyQt5.QtGui import ( - QPolygon, QColor, QPainter, QImage, QBrush, QPen, QPalette, QPixmap -) -import sys -import os - -class ScreenShotPreview(QLabel): - pixmapChanged = pyqtSignal() - - - # constructor - def __init__(self, parent=None): - QLabel.__init__(self, parent) - - self.__pixmap = None - self.qp = None - - # self.setupUi() - # self.setup() - # - # - # def setupUi(self): - # # setting window title - # self.setWindowTitle('ScreenShotViewer') - # - # # setting window geometry - # # self.setGeometry(200, 200, 300, 300) - # - # def setup(self): - # # self.qp = QPainter() - # self.show() - # - # def setPixmap(self, pixmap): - # if pixmap != self.__pixmap: - # self.__pixmap = pixmap - # self.pixmapChanged.emit() - # - # def pixmap(self): - # return self.__pixmap - - # method for paint event - # def paintEvent(self, event): - # - # self.qp.begin(self) - # - # self.qp.setBrush(Qt.darkGray) - # self.qp.drawRect(0, 0, self.width(), self.height()) - # - # # tune up painter - # self.qp.setRenderHint(QPainter.Antialiasing) - # - # # # drawing background - # if isinstance(self.__pixmap, QPixmap): - # bg = self.pixmap().scaled(self.width(), self.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) - # - # # drawing background - # self.qp.drawImage(int((self.width() - bg.width()) / 2), - # int((self.height() - bg.height()) / 2), - # bg.toImage() - # ) - # - # # ending the painter - # self.qp.end() diff --git a/Under Construction/Grab.app/Resources/windowlistmodel.py b/Under Construction/Grab.app/Resources/windowlistmodel.py deleted file mode 100644 index cdfb85e2..00000000 --- a/Under Construction/Grab.app/Resources/windowlistmodel.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2023 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from PySide6.QtCore import QAbstractListModel, Qt, Slot -from PySide6.QtMultimedia import QWindowCapture - -from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut -from PyQt5.QtMultimedia import QWindowCapture -from PyQt5.QtCore import QAbstractListModel, Qt, Slot - -class WindowListModel(QAbstractListModel): - - def __init__(self, parent=None): - super().__init__(parent) - self._window_list = QWindowCapture.capturableWindows() - - def rowCount(self, QModelIndex): - return len(self._window_list) - - def data(self, index, role): - if role == Qt.DisplayRole: - window = self._window_list[index.row()] - return window.description() - return None - - def window(self, index): - return self._window_list[index.row()] - - @Slot() - def populate(self): - self.beginResetModel() - self._window_list = QWindowCapture.capturableWindows() - self.endResetModel() \ No newline at end of file diff --git a/Under Construction/Grab.app/Resources/worker_snapshots.py b/Under Construction/Grab.app/Resources/worker_snapshots.py deleted file mode 100644 index 4761afbd..00000000 --- a/Under Construction/Grab.app/Resources/worker_snapshots.py +++ /dev/null @@ -1,29 +0,0 @@ -import time - -from PyQt5.QtCore import QThread, QThreadPool - - -class SnapShots(QThread): - def __init__(self, main_window, fps=25): - QThread.__init__(self) - self.fps = fps - self.main_window = main_window - self.halt = False - self.queue = Queue.Queue() - - def run(self, fps=None): - if fps: - self.fps = fps - period = 1.0 / self.fps - while not self.halt: - st = time.time() - while not self.queue.empty(): - pass - self.queue.put("capture") - self.emit(SIGNAL("capture()")) - td = time.time() - st - wait = period - td - if wait > 0: time.sleep(wait) - # empty the queue here (thread safe) - with self.queue.mutex: - self.queue.queue.clear() From 2ffdf443d127be9c15376de6031d36c77e9a552f Mon Sep 17 00:00:00 2001 From: Tuuux Date: Wed, 14 Feb 2024 14:22:30 +0100 Subject: [PATCH 11/36] Add sound from preference window --- Under Construction/Grab.app/Resources/grab.py | 10 ++++++---- .../Grab.app/Resources/widget_snapping.py | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 79c2045b..573b8d1f 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, QShortcut, qApp -from PyQt5.QtGui import QPixmap, QIcon, QPainter, QHideEvent, QShowEvent, QPalette, QCursor -from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl, QMargins +from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, qApp +from PyQt5.QtGui import QPixmap, QIcon, QPainter, QShowEvent +from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer import sys @@ -165,7 +165,8 @@ def onSnippingCompleted(self, frame): if frame is None: return - self.sound.play() + if self.preference_enable_sound: + self.sound.play() self.img_preview.setImage(frame) self.img_preview.clearZoom() @@ -337,6 +338,7 @@ def drawImage(printer): def _preference_enable_sound_changed(self, value: bool) -> None: self.preference_enable_sound = value + self.snippingWidget.enable_sound = self.preference_enable_sound def _preference_pointer_changed(self, value: int) -> None: self.preference_pointer = value diff --git a/Under Construction/Grab.app/Resources/widget_snapping.py b/Under Construction/Grab.app/Resources/widget_snapping.py index 0135e920..7a2473a2 100644 --- a/Under Construction/Grab.app/Resources/widget_snapping.py +++ b/Under Construction/Grab.app/Resources/widget_snapping.py @@ -21,6 +21,7 @@ def __init__(self, parent=None): self.win_id = None self.screen = None self.selection_pen_width = None + self.enable_sound = None self.initialState() From e953918307c3eb3ecb12396380c0eb38a0ad222a Mon Sep 17 00:00:00 2001 From: Tuuux Date: Fri, 16 Feb 2024 11:32:00 +0100 Subject: [PATCH 12/36] Coordinate position fixe and selection work on each direction --- .../Grab.app/Resources/dialog_screen_grab.py | 35 ++---- .../Grab.app/Resources/dialog_screen_grab.ui | 15 +-- .../Resources/dialog_screen_grab_ui.py | 10 +- .../Resources/dialog_selection_grab.ui | 8 +- .../Resources/dialog_selection_grab_ui.py | 4 +- .../Resources/dialog_selection_screen_grab.py | 22 ++-- Under Construction/Grab.app/Resources/grab.py | 48 ++++++-- ...et_snapping.py => widget_snipping_tool.py} | 104 +++++++++--------- 8 files changed, 121 insertions(+), 125 deletions(-) rename Under Construction/Grab.app/Resources/{widget_snapping.py => widget_snipping_tool.py} (66%) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 25c95690..2b763f24 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,8 +1,9 @@ import os -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QCloseEvent -from PyQt5.QtWidgets import QDialog, QShortcut, QApplication from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence +from PyQt5.QtWidgets import QDialog, QShortcut + from dialog_screen_grab_ui import Ui_ScreenGrabDialog @@ -12,16 +13,6 @@ class ScreenGrabDialog(QDialog): def __init__(self, parent=None): super(ScreenGrabDialog, self).__init__(parent) - # self.setWindowFlags( - # Qt.Window | Qt.WindowTitleHint | Qt.WindowCloseButtonHint - # ) - # self.setWindowFlags( - # Qt.Window | - # Qt.CustomizeWindowHint | - # Qt.WindowTitleHint | - # Qt.WindowCloseButtonHint - # ) - #self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) self.setWindowModality(Qt.WindowModality.ApplicationModal) @@ -29,30 +20,20 @@ def __init__(self, parent=None): self.ui = Ui_ScreenGrabDialog() self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setFixedSize(self.size()) self.ui.button_cancel.clicked.connect(self.screen_dialog_quit) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.screen_dialog_quit) - self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - self.setFocus() - - # def focusOutEvent(self, event): - # self.screen_dialog_signal_quit.emit() - - - def closeEvent(self, event: QCloseEvent) -> None: - super(ScreenGrabDialog, self).setWindowOpacity(0.0) - super(ScreenGrabDialog, self).closeEvent(event) - QApplication.processEvents() + def focusOutEvent(self, event): + super(ScreenGrabDialog, self).close() event.accept() + self.screen_dialog_signal_start.emit() def screen_dialog_quit(self): self.screen_dialog_signal_quit.emit() - - def screen_dialog_start(self): - self.screen_dialog_signal_start.emit() - + self.close() diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index b6882106..102f8510 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -9,8 +9,8 @@ 0 0 - 486 - 178 + 488 + 203 @@ -187,17 +187,6 @@ - - - 0 - 0 - - - - - Nimbus Sans - - Cancel diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index 0060c697..f11dbc66 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -15,7 +15,7 @@ class Ui_ScreenGrabDialog(object): def setupUi(self, ScreenGrabDialog): ScreenGrabDialog.setObjectName("ScreenGrabDialog") ScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) - ScreenGrabDialog.resize(486, 178) + ScreenGrabDialog.resize(488, 203) ScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) ScreenGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrabDialog) @@ -70,14 +70,6 @@ def setupUi(self, ScreenGrabDialog): spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) self.button_cancel = QtWidgets.QPushButton(ScreenGrabDialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.button_cancel.sizePolicy().hasHeightForWidth()) - self.button_cancel.setSizePolicy(sizePolicy) - font = QtGui.QFont() - font.setFamily("Nimbus Sans") - self.button_cancel.setFont(font) self.button_cancel.setObjectName("button_cancel") self.horizontalLayout.addWidget(self.button_cancel) self.MainVbox.addLayout(self.horizontalLayout) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui index aa3817ed..e6616d2f 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -3,7 +3,7 @@ SelectionGrabDialog - Qt::NonModal + Qt::WindowModal @@ -201,6 +201,12 @@ Cancel + + true + + + false + diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py index 43e35e61..df3ab416 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -14,7 +14,7 @@ class Ui_SelectionGrabDialog(object): def setupUi(self, SelectionGrabDialog): SelectionGrabDialog.setObjectName("SelectionGrabDialog") - SelectionGrabDialog.setWindowModality(QtCore.Qt.NonModal) + SelectionGrabDialog.setWindowModality(QtCore.Qt.WindowModal) SelectionGrabDialog.resize(486, 178) SelectionGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) SelectionGrabDialog.setModal(True) @@ -78,6 +78,8 @@ def setupUi(self, SelectionGrabDialog): font = QtGui.QFont() font.setFamily("Nimbus Sans") self.button_cancel.setFont(font) + self.button_cancel.setDefault(True) + self.button_cancel.setFlat(False) self.button_cancel.setObjectName("button_cancel") self.horizontalLayout.addWidget(self.button_cancel) self.MainVbox.addLayout(self.horizontalLayout) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py index bb47274c..7a7fe449 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -1,8 +1,9 @@ import os +from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent from PyQt5.QtWidgets import QDialog, QShortcut -from PyQt5.QtCore import pyqtSignal, Qt + from dialog_selection_grab_ui import Ui_SelectionGrabDialog @@ -12,23 +13,26 @@ class SelectionGrabDialog(QDialog): def __init__(self, parent=None): super(SelectionGrabDialog, self).__init__(parent) - self.setWindowFlags(Qt.Dialog) + self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + self.setWindowModality(Qt.WindowModality.ApplicationModal) self.ui = Ui_SelectionGrabDialog() self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - self.setFixedSize(self.size()) - self.ui.button_cancel.clicked.connect(self.cancel_dialog) + self.ui.button_cancel.clicked.connect(self.selection_dialog_signal_quit) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.cancel_dialog) + quitShortcut1.activated.connect(self.selection_dialog_signal_quit) - def cancel_dialog(self): - self.selection_dialog_signal_quit.emit() + self.setFocus() - def focusOutEvent(self, a0: QFocusEvent) -> None: + def focusOutEvent(self, event: QFocusEvent) -> None: + super(SelectionGrabDialog, self).close() + event.accept() self.selection_dialog_signal_start.emit() - + def screen_dialog_quit(self): + self.close() diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 573b8d1f..df2cdf36 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, qApp +from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, qApp, QPushButton from PyQt5.QtGui import QPixmap, QIcon, QPainter, QShowEvent from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog @@ -13,8 +13,9 @@ from dialog_timed_screen_grab import TimedScreenGrabDialog from dialog_screen_grab import ScreenGrabDialog from dialog_help import HelpDialog +from dialog_selection_screen_grab import SelectionGrabDialog from widget_transparent_window import TransWindow -from widget_snapping import SnippingWidget +from widget_snipping_tool import SnippingWidget from preference_window import PreferenceWindow QLoggingCategory.setFilterRules("*.debug=false\nqt.qpa.*=false") @@ -44,6 +45,7 @@ def __init__(self, parent=None): self.TimedScreenGrabDialog = None self.ScreenGrabDialog = None + self.SelectionGrabDialog = None self.TransWindow = None self.PreferenceWindow = None self.preference_pointer = None @@ -53,8 +55,8 @@ def __init__(self, parent=None): self.setupCustomUi() self.setupCustomUiGroups() self.connectSignalsSlots() + self.initialized = False self.initialState() - self.initialized = True def initialState(self): self.ActionMenuFilePrint.setEnabled(False) @@ -81,7 +83,9 @@ def initialState(self): self.settings = QSettings("helloSystem", "Grab.app") self.read_settings() + self.snipFull() + self.initialized = True def setupCustomUi(self): # creating an object of the QPrinter class @@ -98,8 +102,6 @@ def setupCustomUi(self): QMediaContent(QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__), "trigger_of_camera.wav"))) ) - self.transparent_window_opacity = 1.0 - def setupCustomUiGroups(self): menu_frequency_group = QActionGroup(self) menu_frequency_group.addAction(self.ActionUpdateTimerTo1Sec) @@ -132,6 +134,7 @@ def connectSignalsSlots(self): # Capture self.ActionMenuCaptureScreen.triggered.connect(self._showScreenGrabDialog) + self.ActionMenuCaptureSelection.triggered.connect(self._showSelectionGrabDialog) self.ActionMenuCaptureTimedScreen.triggered.connect(self._showTimedScreenGrabDialog) # Capture / Timer @@ -147,7 +150,7 @@ def connectSignalsSlots(self): self.ActionUpdateTimerTo10Secs.triggered.connect(self._timer_change_for_10_secs) # Capture / Area - self.ActionMenuCaptureSelection.triggered.connect(self.snipArea) + # self.ui.pushButton_area.clicked.connect(self.snipArea) # self.ui.pushButton_full.clicked.connect(self.snipFull) @@ -165,7 +168,7 @@ def onSnippingCompleted(self, frame): if frame is None: return - if self.preference_enable_sound: + if self.preference_enable_sound and self.initialized: self.sound.play() self.img_preview.setImage(frame) @@ -342,6 +345,8 @@ def _preference_enable_sound_changed(self, value: bool) -> None: def _preference_pointer_changed(self, value: int) -> None: self.preference_pointer = value + self.img_preview.cursor = self.preference_pointer + def _showPreferenceWindow(self): if self.ActionMenuEditPreference.isEnabled(): self.PreferenceWindow = PreferenceWindow( @@ -361,15 +366,16 @@ def _showAboutDialog(): 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation ) ) + msg.addButton(QPushButton("Ok"), QMessageBox.AcceptRole) candidates = ["COPYRIGHT", "COPYING", "LICENSE"] for candidate in candidates: if os.path.exists(os.path.join(os.path.dirname(__file__), candidate)): with open(os.path.join(os.path.dirname(__file__), candidate), "r") as file: data = file.read() msg.setDetailedText(data) - msg.setText("

Grab

") + msg.setText(f"

{qApp.applicationName()}

") msg.setInformativeText( - "Grab is an application write in pyQt5 that can capture screen shots.

" + f"{qApp.applicationName()} is an application write in pyQt5 that can capture screen shots.

" "https://github.com/helloSystem/Utilities" ) msg.exec() @@ -384,6 +390,7 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) + # self.TransWindow = TransWindow(self) # self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) # self.TransWindow.transparent_window_signal_quit.connect(self._CloseAllDialogs) @@ -397,6 +404,21 @@ def _showScreenGrabDialog(self): while not self.windowHandle(): QApplication.processEvents() + def _showSelectionGrabDialog(self): + if self.ActionMenuCaptureSelection.isEnabled(): + self.hide() + + # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) + + self.SelectionGrabDialog = SelectionGrabDialog(self) + self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) + self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) + + + + self.SelectionGrabDialog.show() + + def hideEvent(self, event: QShowEvent) -> None: super(Window, self).setWindowOpacity(0.0) super(Window, self).hideEvent(event) @@ -407,6 +429,10 @@ def showEvent(self, event: QShowEvent) -> None: super(Window, self).showEvent(event) event.accept() + def _SelectionGrabStart(self): + self._CloseAllDialogs() + self.snipArea() + def _ScreenGrabStart(self): self._CloseAllDialogs() self.snipFull() @@ -436,7 +462,9 @@ def _CloseAllDialogs(self): if self.TransWindow and isinstance(self.TransWindow, TransWindow): self.TransWindow.close() self.TransWindow = None - + if self.SelectionGrabDialog and isinstance(self.SelectionGrabDialog, SelectionGrabDialog): + self.SelectionGrabDialog.close() + self.SelectionGrabDialog = None if self.isHidden(): self.show() diff --git a/Under Construction/Grab.app/Resources/widget_snapping.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py similarity index 66% rename from Under Construction/Grab.app/Resources/widget_snapping.py rename to Under Construction/Grab.app/Resources/widget_snipping_tool.py index 7a2473a2..36e45484 100644 --- a/Under Construction/Grab.app/Resources/widget_snapping.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -22,14 +22,13 @@ def __init__(self, parent=None): self.screen = None self.selection_pen_width = None self.enable_sound = None + self.cursor = Qt.BlankCursor self.initialState() def initialState(self): self.setAttribute(Qt.WA_TranslucentBackground) - # self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) - # self.setWindowFlags(Qt.FramelessWindowHint) self.begin = QPoint() self.end = QPoint() @@ -42,14 +41,11 @@ def initialState(self): quitShortcut1.activated.connect(self.cancel_widget_snapping) self.selection_pen_width = 2 - def get_screen_size(self): - return self.screen.grabWindow(self.win_id).size() - def cancel_widget_snapping(self): self.is_snipping = False self.begin = QPoint() self.end = QPoint() - if self.onSnippingCompleted is not None: + if self.onSnippingCompleted: self.onSnippingCompleted(None) self.close() @@ -84,30 +80,29 @@ def SelectionPen(self): ) def UpdateScreen(self): + # Be sure the screen is the Parent of the Desktop self.screen = qApp.primaryScreen() self.win_id = QApplication.desktop().winId() - # screenRect = QApplication.desktop().screenGeometry() - # self.setGeometry( - # screenRect - # ) - # self.move(0, 0) - self.resize(self.get_screen_size()) - window = self.windowHandle() - if window: - self.screen = window.screen() + # Impose the screen size of the snipping widget + self.resize(self.screen.grabWindow(self.win_id).size()) + + # Back to normal situation where screen is the parent of the MainWindow + if self.windowHandle(): + self.screen = self.windowHandle().screen() def fullscreen(self): self.UpdateScreen() if not self.screen: return - + QApplication.setOverrideCursor(self.cursor) try: img = self.screen.grabWindow(self.win_id) except (Exception, BaseException): img = None - if self.onSnippingCompleted is not None: + QApplication.restoreOverrideCursor() + if self.onSnippingCompleted: self.onSnippingCompleted(img) def start(self): @@ -127,19 +122,30 @@ def paintEvent(self, event): # self.setWindowOpacity(opacity) + # Draw the selection self.qp.setPen(self.SelectionPen) self.qp.setBrush(self.SelectionColorBackground) - rect = QRectF(self.begin, self.end) - self.qp.drawRect(rect) - font = QFont() - fm = QFontMetrics(font) - text = f"{int(abs(rect.width()))}, {int(abs(rect.height()))}" - pixelsWide = fm.width(text) - pixelsHigh = fm.height() - spacing = 5 + selection_rect = QRectF(self.begin, self.end) + self.qp.drawRect(selection_rect) + + # Draw text coordinate + coordinate_spacing = 5 + coordinate_font = QFont() + coordinate_font_metrics = QFontMetrics(coordinate_font) + coordinate_x = max(self.end.x(), self.begin.x()) + coordinate_y = max(self.end.y(), self.begin.y()) + coordinate_text = f"{int(abs(selection_rect.width()))}, {int(abs(selection_rect.height()))}" + coordinate_text_height = coordinate_font_metrics.height() + coordinate_text_width = coordinate_font_metrics.width(coordinate_text) + coordinate_text_x = coordinate_x - coordinate_text_width - coordinate_spacing + coordinate_text_y = coordinate_y - coordinate_spacing + coordinate_text_height + coordinate_rect_x = coordinate_x - coordinate_text_width - (coordinate_spacing * 2) + coordinate_rect_y = coordinate_y + (coordinate_spacing / 2) + coordinate_rect_width = coordinate_text_width + (coordinate_spacing * 2) + coordinate_rect_height = coordinate_text_height - (self.selection_pen_width * 2) self.qp.setPen(QPen( QColor(0, 0, 0, 255), @@ -149,22 +155,22 @@ def paintEvent(self, event): Qt.RoundJoin )) self.qp.drawText( - self.end.x() - pixelsWide - spacing, - self.end.y() - spacing + pixelsHigh, - text + coordinate_text_x, + coordinate_text_y, + coordinate_text ) self.qp.setBrush(Qt.NoBrush) self.qp.drawRect( QRectF( - self.end.x() - pixelsWide - (spacing * 2), - self.end.y() + ( spacing / 2), - pixelsWide + (spacing * 2), - pixelsHigh - ( self.selection_pen_width * 2) + coordinate_rect_x, + coordinate_rect_y, + coordinate_rect_width, + coordinate_rect_height ) ) - if not rect.isNull(): - self.selection_rect = rect - self.selection_info.setFromQRectF(rect) + if not selection_rect.isNull(): + self.selection_info.setFromQRectF(selection_rect.normalized()) + self.qp.end() def mousePressEvent(self, event): @@ -184,34 +190,22 @@ def mouseReleaseEvent(self, event): self.is_snipping = False QApplication.restoreOverrideCursor() self.repaint() - + self.setWindowOpacity(0.0) QApplication.processEvents() - self.hide() - print(self.selection_rect) + img = self.screen.grabWindow( self.win_id, ).copy( - self.selection_rect.x(), - self.selection_rect.y(), + self.selection_info.x, + self.selection_info.y, self.selection_info.width, self.selection_info.height, ) - self.show() - # try: - # self.hide() - # img = self.screen.grabWindow( - # self.win_id, - # self.selection_info.x, - # self.selection_info.y, - # self.selection_info.width, - # self.selection_info.height, - # ) - # self.show() - # except (Exception, BaseException): - # img = None - - if self.onSnippingCompleted is not None: + # self.setWindowOpacity(1.0) + + if self.onSnippingCompleted: self.onSnippingCompleted(img) + QApplication.restoreOverrideCursor() self.close() From 52889fc64937e0ac26083578d19b5a9924bd9e49 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Fri, 16 Feb 2024 14:42:06 +0100 Subject: [PATCH 13/36] Move selection coordinate into property_selection_area.py --- Under Construction/Grab.app/Resources/grab.py | 1 - .../Resources/property_selection_area.py | 95 ++++++++++++- .../Resources/widget_snipping_tool.py | 129 +++++------------- 3 files changed, 131 insertions(+), 94 deletions(-) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index df2cdf36..7c0d70d7 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -418,7 +418,6 @@ def _showSelectionGrabDialog(self): self.SelectionGrabDialog.show() - def hideEvent(self, event: QShowEvent) -> None: super(Window, self).setWindowOpacity(0.0) super(Window, self).hideEvent(event) diff --git a/Under Construction/Grab.app/Resources/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py index 30b0bcc9..910dee60 100644 --- a/Under Construction/Grab.app/Resources/property_selection_area.py +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -1,5 +1,6 @@ +from PyQt5.QtCore import Qt, QRectF +from PyQt5.QtGui import QFont, QFontMetrics, QColor, QPen, QPalette -from PyQt5.QtCore import Qt, QPoint, QRectF, QRect class SelectionArea(object): @@ -8,6 +9,36 @@ def __init__(self): self.__y = None self.__width = None self.__height = None + self.__is_snipping = None + + self.coordinate_spacing = None + self.coordinate_font = None + self.coordinate_font_metrics = None + self.coordinate_pen = None + self.coordinate_pen_width = 1 + self.coordinate_pen_color = QColor(0, 0, 0, 255) + + self.coordinate_spacing = 5 + self.coordinate_font = QFont() + self.coordinate_font_metrics = QFontMetrics(self.coordinate_font) + self.coordinate_pen = QPen( + self.coordinate_pen_color, + self.coordinate_pen_width, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin + ) + self.selection_pen_width = 2 + + + @property + def is_snipping(self): + return self.__is_snipping + + @is_snipping.setter + def is_snipping(self, value: bool): + if value != self.is_snipping: + self.__is_snipping = value @property def x(self): @@ -45,6 +76,68 @@ def height(self, height: float): if height != self.height: self.__height = height + @property + def coordinate_text(self): + return f"{int(abs(self.width))}, {int(abs(self.height))}" + + @property + def coordinate_text_width(self): + return self.coordinate_font_metrics.width(self.coordinate_text) + + @property + def coordinate_text_x(self): + return self.x + self.width - self.coordinate_text_width - self.coordinate_spacing + + @property + def coordinate_text_y(self): + return self.y + self.height - self.coordinate_spacing + self.coordinate_font_metrics.height() + + @property + def coordinate_rect_x(self): + return self.x + self.width - self.coordinate_text_width - (self.coordinate_spacing * 2) + + @property + def coordinate_rect_y(self): + return self.y + self.height + (self.coordinate_spacing / 2) + + @property + def coordinate_rect_width(self): + return self.coordinate_text_width + (self.coordinate_spacing * 2) + + @property + def coordinate_rect_height(self): + return self.coordinate_font_metrics.height() - (self.selection_pen_width * 2) + + @property + def SelectionColorBackground(self): + if self.is_snipping: + color = QPalette().color(QPalette.Highlight) + color.setAlpha(0) + return color + else: + return QColor(0, 0, 0, 0) + + @property + def SelectionPen(self): + if self.is_snipping: + color = QPalette().color(QPalette.Base) + color.setAlpha(127) + return QPen( + color, + self.selection_pen_width, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin + ) + else: + return QPen( + QColor(0, 0, 0, 0), + 0, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin + ) + def setFromQRectF(self, req: QRectF): self.x = req.x() self.y = req.y() diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index 36e45484..095c369a 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -1,6 +1,7 @@ -from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut -from PyQt5.QtGui import QPainter, QPen, QColor, QCursor, QKeySequence, QFontMetrics, QFont, QPalette from PyQt5.QtCore import Qt, QPoint, QRectF +from PyQt5.QtGui import QPainter, QCursor, QKeySequence +from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut + from property_selection_area import SelectionArea @@ -14,9 +15,9 @@ def __init__(self, parent=None): self.begin = None self.end = None self.onSnippingCompleted = None - self.is_snipping = None + self.qp = None - self.selection_info = None + self.selection = None self.selection_rect = None self.win_id = None self.screen = None @@ -33,52 +34,21 @@ def initialState(self): self.begin = QPoint() self.end = QPoint() self.qp = QPainter() - self.selection_info = SelectionArea() + self.selection = SelectionArea() - self.is_snipping = False + self.selection.is_snipping = False self.UpdateScreen() quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.cancel_widget_snapping) - self.selection_pen_width = 2 def cancel_widget_snapping(self): - self.is_snipping = False + self.selection.is_snipping = False self.begin = QPoint() self.end = QPoint() if self.onSnippingCompleted: self.onSnippingCompleted(None) self.close() - @property - def SelectionColorBackground(self): - if self.is_snipping: - color = QPalette().color(QPalette.Highlight) - color.setAlpha(0) - return color - else: - return QColor(0, 0, 0, 0) - - @property - def SelectionPen(self): - if self.is_snipping: - color = QPalette().color(QPalette.Base) - color.setAlpha(127) - return QPen( - color, - self.selection_pen_width, - Qt.SolidLine, - Qt.RoundCap, - Qt.RoundJoin - ) - else: - return QPen( - QColor(0, 0, 0, 0), - 0, - Qt.SolidLine, - Qt.RoundCap, - Qt.RoundJoin - ) - def UpdateScreen(self): # Be sure the screen is the Parent of the Desktop self.screen = qApp.primaryScreen() @@ -107,69 +77,44 @@ def fullscreen(self): def start(self): self.UpdateScreen() - self.is_snipping = True + self.selection.is_snipping = True # self.setWindowOpacity(0.3) QApplication.setOverrideCursor(QCursor(Qt.CrossCursor)) self.show() def paintEvent(self, event): - if not self.is_snipping: + if not self.selection.is_snipping: self.begin = QPoint() self.end = QPoint() # self.setWindowOpacity(0.0) self.qp.begin(self) # self.setWindowOpacity(opacity) - - # Draw the selection - self.qp.setPen(self.SelectionPen) - self.qp.setBrush(self.SelectionColorBackground) - - - selection_rect = QRectF(self.begin, self.end) - self.qp.drawRect(selection_rect) - - # Draw text coordinate - coordinate_spacing = 5 - coordinate_font = QFont() - coordinate_font_metrics = QFontMetrics(coordinate_font) - coordinate_x = max(self.end.x(), self.begin.x()) - coordinate_y = max(self.end.y(), self.begin.y()) - coordinate_text = f"{int(abs(selection_rect.width()))}, {int(abs(selection_rect.height()))}" - coordinate_text_height = coordinate_font_metrics.height() - coordinate_text_width = coordinate_font_metrics.width(coordinate_text) - coordinate_text_x = coordinate_x - coordinate_text_width - coordinate_spacing - coordinate_text_y = coordinate_y - coordinate_spacing + coordinate_text_height - coordinate_rect_x = coordinate_x - coordinate_text_width - (coordinate_spacing * 2) - coordinate_rect_y = coordinate_y + (coordinate_spacing / 2) - coordinate_rect_width = coordinate_text_width + (coordinate_spacing * 2) - coordinate_rect_height = coordinate_text_height - (self.selection_pen_width * 2) - - self.qp.setPen(QPen( - QColor(0, 0, 0, 255), - 1, - Qt.SolidLine, - Qt.RoundCap, - Qt.RoundJoin - )) - self.qp.drawText( - coordinate_text_x, - coordinate_text_y, - coordinate_text - ) - self.qp.setBrush(Qt.NoBrush) - self.qp.drawRect( - QRectF( - coordinate_rect_x, - coordinate_rect_y, - coordinate_rect_width, - coordinate_rect_height - ) - ) if not selection_rect.isNull(): - self.selection_info.setFromQRectF(selection_rect.normalized()) + # Draw the selection + self.selection.setFromQRectF(selection_rect.normalized()) + self.qp.setPen(self.selection.SelectionPen) + self.qp.setBrush(self.selection.SelectionColorBackground) + self.qp.drawRect(selection_rect) + + # Draw text coordinate + self.qp.setPen(self.selection.coordinate_pen) + self.qp.drawText( + self.selection.coordinate_text_x, + self.selection.coordinate_text_y, + self.selection.coordinate_text + ) + self.qp.setBrush(Qt.NoBrush) + self.qp.drawRect( + QRectF( + self.selection.coordinate_rect_x, + self.selection.coordinate_rect_y, + self.selection.coordinate_rect_width, + self.selection.coordinate_rect_height + ) + ) self.qp.end() @@ -187,7 +132,7 @@ def mouseReleaseEvent(self, event): if not self.screen: return - self.is_snipping = False + self.selection.is_snipping = False QApplication.restoreOverrideCursor() self.repaint() self.setWindowOpacity(0.0) @@ -197,10 +142,10 @@ def mouseReleaseEvent(self, event): self.win_id, ).copy( - self.selection_info.x, - self.selection_info.y, - self.selection_info.width, - self.selection_info.height, + self.selection.x, + self.selection.y, + self.selection.width, + self.selection.height, ) # self.setWindowOpacity(1.0) From a7c36d3b4d54a468fc5d625b51702ee890ccb788 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Fri, 16 Feb 2024 15:20:26 +0100 Subject: [PATCH 14/36] Use signal --- Under Construction/Grab.app/Resources/grab.py | 11 ++--- .../Resources/property_selection_area.py | 40 +++++++++-------- .../Resources/widget_snipping_tool.py | 44 +++++++++---------- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 7c0d70d7..d4007259 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -95,7 +95,7 @@ def setupCustomUi(self): self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.snippingWidget = SnippingWidget() - self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted + # self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted self.sound = QMediaPlayer() self.sound.setMedia( @@ -158,20 +158,21 @@ def connectSignalsSlots(self): self.ActionMenuHelpAbout.triggered.connect(self._showAboutDialog) self.ActionMenuHelpDocumentation.triggered.connect(self._showHelpDialog) - # Preferences + # Snipping widget + self.snippingWidget.snipping_completed.connect(self.onSnippingCompleted) - def onSnippingCompleted(self, frame): + def onSnippingCompleted(self, img): self.setWindowState(Qt.WindowActive) - if frame is None: + if img is None: return if self.preference_enable_sound and self.initialized: self.sound.play() - self.img_preview.setImage(frame) + self.img_preview.setImage(img) self.img_preview.clearZoom() self.fileName = None diff --git a/Under Construction/Grab.app/Resources/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py index 910dee60..fe62270e 100644 --- a/Under Construction/Grab.app/Resources/property_selection_area.py +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -20,7 +20,7 @@ def __init__(self): self.coordinate_spacing = 5 self.coordinate_font = QFont() - self.coordinate_font_metrics = QFontMetrics(self.coordinate_font) + self.coordinate_font_metrics = QFontMetrics(QFont()) self.coordinate_pen = QPen( self.coordinate_pen_color, self.coordinate_pen_width, @@ -30,9 +30,9 @@ def __init__(self): ) self.selection_pen_width = 2 - + # Status @property - def is_snipping(self): + def is_snipping(self) -> bool: return self.__is_snipping @is_snipping.setter @@ -40,8 +40,9 @@ def is_snipping(self, value: bool): if value != self.is_snipping: self.__is_snipping = value + # Position of the selection @property - def x(self): + def x(self) -> float: return self.__x @x.setter @@ -50,7 +51,7 @@ def x(self, x: float): self.__x = x @property - def y(self): + def y(self) -> float: return self.__y @y.setter @@ -59,7 +60,7 @@ def y(self, y: float): self.__y = y @property - def width(self): + def width(self) -> float: return self.__width @width.setter @@ -68,7 +69,7 @@ def width(self, width: float): self.__width = width @property - def height(self): + def height(self) -> float: return self.__height @height.setter @@ -76,49 +77,50 @@ def height(self, height: float): if height != self.height: self.__height = height + # Coordinate @property - def coordinate_text(self): + def coordinate_text(self) -> str: return f"{int(abs(self.width))}, {int(abs(self.height))}" @property - def coordinate_text_width(self): + def coordinate_text_width(self) -> int: return self.coordinate_font_metrics.width(self.coordinate_text) @property - def coordinate_text_x(self): + def coordinate_text_x(self) -> float: return self.x + self.width - self.coordinate_text_width - self.coordinate_spacing @property - def coordinate_text_y(self): + def coordinate_text_y(self) -> float: return self.y + self.height - self.coordinate_spacing + self.coordinate_font_metrics.height() @property - def coordinate_rect_x(self): + def coordinate_rect_x(self) -> float: return self.x + self.width - self.coordinate_text_width - (self.coordinate_spacing * 2) @property - def coordinate_rect_y(self): + def coordinate_rect_y(self) -> float: return self.y + self.height + (self.coordinate_spacing / 2) @property - def coordinate_rect_width(self): + def coordinate_rect_width(self) -> float: return self.coordinate_text_width + (self.coordinate_spacing * 2) @property - def coordinate_rect_height(self): + def coordinate_rect_height(self) -> float: return self.coordinate_font_metrics.height() - (self.selection_pen_width * 2) @property - def SelectionColorBackground(self): + def SelectionColorBackground(self) -> QColor: if self.is_snipping: color = QPalette().color(QPalette.Highlight) - color.setAlpha(0) + color.setAlpha(5) return color else: return QColor(0, 0, 0, 0) @property - def SelectionPen(self): + def SelectionPen(self) -> QPen: if self.is_snipping: color = QPalette().color(QPalette.Base) color.setAlpha(127) @@ -138,7 +140,7 @@ def SelectionPen(self): Qt.RoundJoin ) - def setFromQRectF(self, req: QRectF): + def setFromQRectF(self, req: QRectF) -> None: self.x = req.x() self.y = req.y() self.width = req.width() diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index 095c369a..c73b82ef 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -1,4 +1,4 @@ -from PyQt5.QtCore import Qt, QPoint, QRectF +from PyQt5.QtCore import Qt, QPoint, QRectF, pyqtSignal from PyQt5.QtGui import QPainter, QCursor, QKeySequence from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut @@ -7,6 +7,7 @@ # Refer to https://github.com/harupy/snipping-tool class SnippingWidget(QWidget): + snipping_completed = pyqtSignal(object) def __init__(self, parent=None): super(SnippingWidget, self).__init__() @@ -14,7 +15,7 @@ def __init__(self, parent=None): self.begin = None self.end = None - self.onSnippingCompleted = None + # self.onSnippingCompleted = None self.qp = None self.selection = None @@ -45,8 +46,8 @@ def cancel_widget_snapping(self): self.selection.is_snipping = False self.begin = QPoint() self.end = QPoint() - if self.onSnippingCompleted: - self.onSnippingCompleted(None) + self.snipping_completed.emit(None) + QApplication.restoreOverrideCursor() self.close() def UpdateScreen(self): @@ -72,8 +73,7 @@ def fullscreen(self): img = None QApplication.restoreOverrideCursor() - if self.onSnippingCompleted: - self.onSnippingCompleted(img) + self.snipping_completed.emit(img) def start(self): self.UpdateScreen() @@ -83,7 +83,6 @@ def start(self): self.show() def paintEvent(self, event): - if not self.selection.is_snipping: self.begin = QPoint() self.end = QPoint() @@ -101,18 +100,16 @@ def paintEvent(self, event): # Draw text coordinate self.qp.setPen(self.selection.coordinate_pen) + self.qp.setBrush(Qt.NoBrush) self.qp.drawText( - self.selection.coordinate_text_x, - self.selection.coordinate_text_y, - self.selection.coordinate_text + self.selection.coordinate_text_x, self.selection.coordinate_text_y, self.selection.coordinate_text ) - self.qp.setBrush(Qt.NoBrush) self.qp.drawRect( QRectF( self.selection.coordinate_rect_x, self.selection.coordinate_rect_y, self.selection.coordinate_rect_width, - self.selection.coordinate_rect_height + self.selection.coordinate_rect_height, ) ) @@ -138,19 +135,18 @@ def mouseReleaseEvent(self, event): self.setWindowOpacity(0.0) QApplication.processEvents() - img = self.screen.grabWindow( - self.win_id, - - ).copy( - self.selection.x, - self.selection.y, - self.selection.width, - self.selection.height, - ) + try: + img = self.screen.grabWindow( + self.win_id, + self.selection.x, + self.selection.y, + self.selection.width, + self.selection.height, + ) + except (Exception, BaseException): + img = None # self.setWindowOpacity(1.0) - if self.onSnippingCompleted: - self.onSnippingCompleted(img) - + self.snipping_completed.emit(img) QApplication.restoreOverrideCursor() self.close() From 0e03747be44b308e91f610e5549d8f49a808a6c9 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Fri, 16 Feb 2024 15:29:12 +0100 Subject: [PATCH 15/36] Restore visibility --- .../Grab.app/Resources/widget_snipping_tool.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index c73b82ef..96a899b0 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -15,7 +15,6 @@ def __init__(self, parent=None): self.begin = None self.end = None - # self.onSnippingCompleted = None self.qp = None self.selection = None @@ -98,12 +97,14 @@ def paintEvent(self, event): self.qp.setBrush(self.selection.SelectionColorBackground) self.qp.drawRect(selection_rect) - # Draw text coordinate + # Draw coordinate + # Draw coordinate text self.qp.setPen(self.selection.coordinate_pen) self.qp.setBrush(Qt.NoBrush) self.qp.drawText( self.selection.coordinate_text_x, self.selection.coordinate_text_y, self.selection.coordinate_text ) + # Draw coordinate border self.qp.drawRect( QRectF( self.selection.coordinate_rect_x, @@ -145,7 +146,8 @@ def mouseReleaseEvent(self, event): ) except (Exception, BaseException): img = None - # self.setWindowOpacity(1.0) + + self.setWindowOpacity(1.0) self.snipping_completed.emit(img) QApplication.restoreOverrideCursor() From d33da9b4a24e165d22a2b8735070c1bc745ec928 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Fri, 16 Feb 2024 17:14:19 +0100 Subject: [PATCH 16/36] It can draw cursor --- Under Construction/Grab.app/Resources/grab.py | 17 +++------ .../Grab.app/Resources/preference_window.py | 2 - .../Resources/widget_snipping_tool.py | 38 ++++++++++++++++--- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index d4007259..f3359cef 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -89,13 +89,11 @@ def initialState(self): def setupCustomUi(self): # creating an object of the QPrinter class - self.printerObj = QPrinter() self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.snippingWidget = SnippingWidget() - # self.snippingWidget.onSnippingCompleted = self.onSnippingCompleted self.sound = QMediaPlayer() self.sound.setMedia( @@ -161,8 +159,6 @@ def connectSignalsSlots(self): # Snipping widget self.snippingWidget.snipping_completed.connect(self.onSnippingCompleted) - - def onSnippingCompleted(self, img): self.setWindowState(Qt.WindowActive) @@ -193,15 +189,15 @@ def snipFull(self): def write_settings(self): self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) - self.settings.setValue('preference_enable_sound', self.preference_enable_sound) - self.settings.setValue('preference_pointer', self.preference_pointer) + self.settings.setValue("preference_enable_sound", self.preference_enable_sound) + self.settings.setValue("preference_pointer", self.preference_pointer) def read_settings(self): self.restoreGeometry(self.settings.value("geometry", QByteArray())) self.restoreState(self.settings.value("windowState", QByteArray())) self.preference_enable_sound = self.settings.value("preference_enable_sound", defaultValue=True, type=bool) self.preference_pointer = self.settings.value("preference_pointer", defaultValue=10, type=int) - + self.snippingWidget.cursor = self.preference_pointer def closeEvent(self, event): self.write_settings() @@ -347,12 +343,12 @@ def _preference_enable_sound_changed(self, value: bool) -> None: def _preference_pointer_changed(self, value: int) -> None: self.preference_pointer = value self.img_preview.cursor = self.preference_pointer + self.snippingWidget.cursor = self.preference_pointer def _showPreferenceWindow(self): if self.ActionMenuEditPreference.isEnabled(): self.PreferenceWindow = PreferenceWindow( - play_sound=self.preference_enable_sound, - pointer=self.preference_pointer + play_sound=self.preference_enable_sound, pointer=self.preference_pointer ) self.PreferenceWindow.checkbox_enable_sound_changed.connect(self._preference_enable_sound_changed) self.PreferenceWindow.buttongroup_changed.connect(self._preference_pointer_changed) @@ -391,7 +387,6 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) - # self.TransWindow = TransWindow(self) # self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) # self.TransWindow.transparent_window_signal_quit.connect(self._CloseAllDialogs) @@ -415,8 +410,6 @@ def _showSelectionGrabDialog(self): self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) - - self.SelectionGrabDialog.show() def hideEvent(self, event: QShowEvent) -> None: diff --git a/Under Construction/Grab.app/Resources/preference_window.py b/Under Construction/Grab.app/Resources/preference_window.py index fd784c63..b43a2107 100644 --- a/Under Construction/Grab.app/Resources/preference_window.py +++ b/Under Construction/Grab.app/Resources/preference_window.py @@ -72,8 +72,6 @@ def preference_window_cancel(self) -> None: def pointer_changed(self): cursor_id = self.get_qt_cursor_id_by_name(self.ui.buttonGroup.checkedButton().objectName()) - cursor_name = self.get_qt_cname_by_id(cursor_id) - print(f"id: {cursor_id}, name: {cursor_name}") self.buttongroup_changed.emit(cursor_id) def enable_sound_changed(self): diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index 96a899b0..5afb5b9c 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -1,5 +1,7 @@ +import os + from PyQt5.QtCore import Qt, QPoint, QRectF, pyqtSignal -from PyQt5.QtGui import QPainter, QCursor, QKeySequence +from PyQt5.QtGui import QPainter, QCursor, QKeySequence, QPixmap from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut from property_selection_area import SelectionArea @@ -9,7 +11,7 @@ class SnippingWidget(QWidget): snipping_completed = pyqtSignal(object) - def __init__(self, parent=None): + def __init__(self, parent=None, cursor=None): super(SnippingWidget, self).__init__() self.parent = parent @@ -23,8 +25,17 @@ def __init__(self, parent=None): self.screen = None self.selection_pen_width = None self.enable_sound = None - self.cursor = Qt.BlankCursor - + self.cursor = cursor + self.cursor_name = { + Qt.ArrowCursor: "ArrowCursor", + Qt.BlankCursor: "BlankCursor", + Qt.ForbiddenCursor: "ForbiddenCursor", + Qt.IBeamCursor: "IBeamCursor", + Qt.OpenHandCursor: "OpenHandCursor", + Qt.PointingHandCursor: "PointingHandCursor", + Qt.UpArrowCursor: "UpArrowCursor", + Qt.WhatsThisCursor: "WhatsThisCursor", + } self.initialState() def initialState(self): @@ -63,15 +74,30 @@ def UpdateScreen(self): def fullscreen(self): self.UpdateScreen() + self.show() + if not self.screen: return - QApplication.setOverrideCursor(self.cursor) try: img = self.screen.grabWindow(self.win_id) except (Exception, BaseException): img = None - QApplication.restoreOverrideCursor() + if img and self.cursor != Qt.BlankCursor: + pm = QPixmap(self.width(), self.height()) + cursor_pixmap = QPixmap(os.path.join(os.path.dirname(__file__), f"{self.cursor_name[self.cursor]}.png")) + painter = QPainter(pm) + painter.drawPixmap(0, 0, self.width(), self.height(), img) + painter.drawPixmap( + QCursor().pos().x() - (cursor_pixmap.width() / 2), + QCursor().pos().y() - (cursor_pixmap.height() / 2), + cursor_pixmap.width(), + cursor_pixmap.height(), + cursor_pixmap, + ) + painter.end() + img = pm + self.snipping_completed.emit(img) def start(self): From bd072e82005ba10fd4b2f0d4b5195ceebc5b1587 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 10:47:19 +0100 Subject: [PATCH 17/36] Small clean up --- .../Grab.app/Resources/dialog_help.py | 6 +- .../Grab.app/Resources/dialog_screen_grab.py | 14 +- .../Grab.app/Resources/dialog_screen_grab.ui | 14 +- .../Resources/dialog_screen_grab_ui.py | 5 +- .../Resources/dialog_selection_screen_grab.py | 20 +- .../Resources/dialog_timed_screen_grab.py | 29 +- .../Grab.app/Resources/dialog_window_grab.ui | 5 +- Under Construction/Grab.app/Resources/grab.py | 23 +- .../Grab.app/Resources/preference_window.py | 2 +- .../Grab.app/Resources/preference_window.ui | 5 +- .../Resources/preference_window_ui.py | 3 +- .../Grab.app/Resources/stylesheet.qss | 385 ++++++++++++++++++ 12 files changed, 470 insertions(+), 41 deletions(-) create mode 100644 Under Construction/Grab.app/Resources/stylesheet.qss diff --git a/Under Construction/Grab.app/Resources/dialog_help.py b/Under Construction/Grab.app/Resources/dialog_help.py index af9f24fe..30249afa 100644 --- a/Under Construction/Grab.app/Resources/dialog_help.py +++ b/Under Construction/Grab.app/Resources/dialog_help.py @@ -1,8 +1,8 @@ import os -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence -from PyQt5.QtWidgets import QDialog, QShortcut, qApp -from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QDialog +from PyQt5.QtCore import pyqtSignal from dialog_help_ui import Ui_HelpDialog diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 2b763f24..41e9fa4d 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,7 +1,8 @@ import os +import sys from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent from PyQt5.QtWidgets import QDialog, QShortcut from dialog_screen_grab_ui import Ui_ScreenGrabDialog @@ -29,10 +30,13 @@ def __init__(self, parent=None): self.setFocus() - def focusOutEvent(self, event): - super(ScreenGrabDialog, self).close() - event.accept() - self.screen_dialog_signal_start.emit() + def focusOutEvent(self, event: QFocusEvent) -> None: + if self.hasFocus() or self.ui.button_cancel.hasFocus(): + event.accept() + else: + event.accept() + self.screen_dialog_signal_start.emit() + self.close() def screen_dialog_quit(self): self.screen_dialog_signal_quit.emit() diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index 102f8510..5ec851f5 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -9,8 +9,8 @@ 0 0 - 488 - 203 + 493 + 206 @@ -22,7 +22,7 @@ true - + @@ -88,7 +88,7 @@ - ../../Processes.app/Resources/Processes.png + ../../../../../../Processes.app/Resources/Processes.png true @@ -177,6 +177,9 @@ Qt::Horizontal + + QSizePolicy::Expanding + 40 @@ -190,6 +193,9 @@ Cancel + + true + diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index f11dbc66..312189d7 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -15,7 +15,7 @@ class Ui_ScreenGrabDialog(object): def setupUi(self, ScreenGrabDialog): ScreenGrabDialog.setObjectName("ScreenGrabDialog") ScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) - ScreenGrabDialog.resize(488, 203) + ScreenGrabDialog.resize(493, 206) ScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) ScreenGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrabDialog) @@ -40,7 +40,7 @@ def setupUi(self, ScreenGrabDialog): self.icon.setMinimumSize(QtCore.QSize(64, 64)) self.icon.setMaximumSize(QtCore.QSize(64, 64)) self.icon.setText("") - self.icon.setPixmap(QtGui.QPixmap("./../../Processes.app/Resources/Processes.png")) + self.icon.setPixmap(QtGui.QPixmap("./../../../../../../Processes.app/Resources/Processes.png")) self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) @@ -70,6 +70,7 @@ def setupUi(self, ScreenGrabDialog): spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) self.button_cancel = QtWidgets.QPushButton(ScreenGrabDialog) + self.button_cancel.setDefault(True) self.button_cancel.setObjectName("button_cancel") self.horizontalLayout.addWidget(self.button_cancel) self.MainVbox.addLayout(self.horizontalLayout) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py index 7a7fe449..be11cf14 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -1,4 +1,5 @@ import os +import sys from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent @@ -21,18 +22,21 @@ def __init__(self, parent=None): self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - self.setFixedSize(self.size()) - self.ui.button_cancel.clicked.connect(self.selection_dialog_signal_quit) + self.ui.button_cancel.clicked.connect(self.selection_dialog_quit) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.selection_dialog_signal_quit) + quitShortcut1.activated.connect(self.selection_dialog_quit) self.setFocus() def focusOutEvent(self, event: QFocusEvent) -> None: - super(SelectionGrabDialog, self).close() - event.accept() - self.selection_dialog_signal_start.emit() - - def screen_dialog_quit(self): + if self.hasFocus() or self.ui.button_cancel.hasFocus(): + event.accept() + else: + event.accept() + self.selection_dialog_signal_start.emit() + self.close() + + def selection_dialog_quit(self): + self.selection_dialog_signal_quit.emit() self.close() diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index 797b6bb1..8941747c 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -1,7 +1,7 @@ import os - -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence -from PyQt5.QtWidgets import QDialog, QShortcut +import sys +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QColor +from PyQt5.QtWidgets import QDialog, QShortcut, QGraphicsDropShadowEffect from PyQt5.QtCore import pyqtSignal, Qt from dialog_timed_screen_grab_ui import Ui_TimedScreenGrabDialog @@ -19,20 +19,33 @@ def __init__(self, parent=None, timer=None): self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + effect1 = QGraphicsDropShadowEffect() + effect1.setBlurRadius(2) + effect1.setOffset(0, 1) + effect1.setColor(QColor(69, 69, 69, 127)) + self.ui.button_cancel.setGraphicsEffect(effect1) + + effect2 = QGraphicsDropShadowEffect() + effect2.setBlurRadius(2) + effect2.setOffset(0, 1) + effect2.setColor(QColor(69, 69, 69, 127)) + self.ui.button_start_timer.setGraphicsEffect(effect2) + if self.sec > 1: self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} seconds") else: self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} second") self.setFixedSize(self.size()) - self.ui.button_cancel.clicked.connect(self.cancel_dialog) - self.ui.button_start_timer.clicked.connect(self.start_timer_dialog) + self.ui.button_cancel.clicked.connect(self.timed_dialog_quit) + self.ui.button_start_timer.clicked.connect(self.timed_dialog_start) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.cancel_dialog) + quitShortcut1.activated.connect(self.timed_dialog_quit) - def cancel_dialog(self): + def timed_dialog_quit(self): self.timer_dialog_signal_quit.emit() - def start_timer_dialog(self): + def timed_dialog_start(self): self.timer_dialog_signal_start.emit() diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui index 2cfcfb89..70eaba99 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -88,7 +88,7 @@ - ../../../../../../Processes.app/Resources/Processes.png + ../../../../../../../../../../Processes.app/Resources/Processes.png true @@ -208,6 +208,9 @@ Choose Window + + true + diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index f3359cef..e37352c6 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -2,7 +2,7 @@ from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, qApp, QPushButton from PyQt5.QtGui import QPixmap, QIcon, QPainter, QShowEvent -from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl +from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl, QEvent from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer import sys @@ -375,10 +375,11 @@ def _showAboutDialog(): f"{qApp.applicationName()} is an application write in pyQt5 that can capture screen shots.

" "https://github.com/helloSystem/Utilities" ) + msg.exec() def _showScreenGrabDialog(self): - if self.ActionMenuCaptureTimedScreen.isEnabled(): + if self.ActionMenuCaptureScreen.isEnabled(): self.hide() # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) @@ -386,6 +387,7 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog = ScreenGrabDialog(self) self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) + self.ScreenGrabDialog.installEventFilter(self) # self.TransWindow = TransWindow(self) # self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) @@ -405,12 +407,13 @@ def _showSelectionGrabDialog(self): self.hide() # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) + if self.SelectionGrabDialog is None: + self.SelectionGrabDialog = SelectionGrabDialog(self) + self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) + self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) - self.SelectionGrabDialog = SelectionGrabDialog(self) - self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) - self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) - - self.SelectionGrabDialog.show() + self.SelectionGrabDialog.exec_() + self.show() def hideEvent(self, event: QShowEvent) -> None: super(Window, self).setWindowOpacity(0.0) @@ -422,6 +425,12 @@ def showEvent(self, event: QShowEvent) -> None: super(Window, self).showEvent(event) event.accept() + # def eventFilter(self, source, event): + # if event.type() == QEvent.FocusOut and source is self.ScreenGrabDialog: + # print('eventFilter: focus out') + # # return true here to bypass default behaviour + # return super(Window, self).eventFilter(source, event) + def _SelectionGrabStart(self): self._CloseAllDialogs() self.snipArea() diff --git a/Under Construction/Grab.app/Resources/preference_window.py b/Under Construction/Grab.app/Resources/preference_window.py index b43a2107..f0dc2f22 100644 --- a/Under Construction/Grab.app/Resources/preference_window.py +++ b/Under Construction/Grab.app/Resources/preference_window.py @@ -1,4 +1,5 @@ import os +import sys from PyQt5.QtGui import QPixmap, QKeySequence, QCursor, QIcon from PyQt5.QtWidgets import QDialog, QShortcut, QWidget @@ -47,7 +48,6 @@ def __init__(self, parent=None, pointer: int = None, play_sound: bool = None): self.play_sound = play_sound self.pointer = pointer - def select_cursor(self, cursor_id): for button in self.ui.buttonGroup.buttons(): if self.get_qt_cname_by_id(cursor_id) == button.objectName(): diff --git a/Under Construction/Grab.app/Resources/preference_window.ui b/Under Construction/Grab.app/Resources/preference_window.ui index 9a324a7d..4eeb434e 100644 --- a/Under Construction/Grab.app/Resources/preference_window.ui +++ b/Under Construction/Grab.app/Resources/preference_window.ui @@ -15,7 +15,7 @@
- Grab.pngGrab.png + ../../../../../../.designer/backup/Grab.png../../../../../../.designer/backup/Grab.png @@ -26,6 +26,9 @@ + + No Cursor + ... diff --git a/Under Construction/Grab.app/Resources/preference_window_ui.py b/Under Construction/Grab.app/Resources/preference_window_ui.py index 39aecda5..3caecb4f 100644 --- a/Under Construction/Grab.app/Resources/preference_window_ui.py +++ b/Under Construction/Grab.app/Resources/preference_window_ui.py @@ -16,7 +16,7 @@ def setupUi(self, PreferenceWindow): PreferenceWindow.setObjectName("PreferenceWindow") PreferenceWindow.resize(220, 187) icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap("./Grab.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + icon.addPixmap(QtGui.QPixmap("./../../../../../../.designer/backup/Grab.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) PreferenceWindow.setWindowIcon(icon) self.verticalLayout = QtWidgets.QVBoxLayout(PreferenceWindow) self.verticalLayout.setObjectName("verticalLayout") @@ -89,6 +89,7 @@ def retranslateUi(self, PreferenceWindow): _translate = QtCore.QCoreApplication.translate PreferenceWindow.setWindowTitle(_translate("PreferenceWindow", "Preferences")) self.groupBox.setTitle(_translate("PreferenceWindow", "Pointer Type")) + self.BlankCursor.setToolTip(_translate("PreferenceWindow", "No Cursor")) self.BlankCursor.setText(_translate("PreferenceWindow", "...")) self.ArrowCursor.setText(_translate("PreferenceWindow", "...")) self.IBeamCursor.setText(_translate("PreferenceWindow", "...")) diff --git a/Under Construction/Grab.app/Resources/stylesheet.qss b/Under Construction/Grab.app/Resources/stylesheet.qss new file mode 100644 index 00000000..e1362583 --- /dev/null +++ b/Under Construction/Grab.app/Resources/stylesheet.qss @@ -0,0 +1,385 @@ +/* https://doc.qt.io/qt-5/stylesheet-customizing.html */ +/* https://doc.qt.io/qt-5/stylesheet-examples.html */ +/* https://github.com/nphase/qt-ping-grapher/blob/master/resources/darkorange.stylesheet */ + +/* --------------------------------------------------------------------------- */ +/* Dark Style - QDarkStyleSheet ------------------------------------------ */ +/* + +See Qt documentation: + + - https://doc.qt.io/qt-5/stylesheet.html + - https://doc.qt.io/qt-5/stylesheet-reference.html + - https://doc.qt.io/qt-5/stylesheet-examples.html + +/* --------------------------------------------------------------------------- */ +/* Reset elements ------------------------------------------------------------ + +Resetting everything helps to unify styles across different operating systems + +--------------------------------------------------------------------------- */ +* { + padding: 0px; + margin: 0px; + border: 0px; + border-style: none; + border-image: none; + outline: 0; +} + +/* specific reset for elements inside QToolBar */ +QToolBar * { + margin: 0px; + padding: 0px; +} + +/* QWidget ---------------------------------------------------------------- + +--------------------------------------------------------------------------- */ +/* Works: Set selected items (e.g., selected text, selected list view items,...) to white text with intensive blue background */ + +QWidget { + background-color: #eeeeee; + border: 0px solid #bebebe; + padding: 0px; + color: #010101; + show-decoration-selected: 1; + selection-color: white; + selection-background-color: #336fc9; /* Intensive blue */ + alternate-background-color: #eef1f5; +} + +QWidget:disabled { + background-color: #eeeeee; + color: #a2a2a2; + selection-background-color: #DAEDFF; + selection-color: #9DA9B5; +} + +QWidget::item:selected { + background-color: #336fc9; +} + +QWidget::item:hover:!selected { + background-color: #eef1f5; +} + + +/* QMainWindow ------------------------------------------------------------ + +This adjusts the splitter in the dock widget, not qsplitter +https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmainwindow + +--------------------------------------------------------------------------- */ +QMainWindow::separator { + background-color: #8d8d8d; + border: 0px solid #eeeeee; + spacing: 0px; + padding: 2px; +} + +QMainWindow::separator:hover { + background-color: #ACB1B6; + border: 0px solid #73C7FF; +} + +QMainWindow::separator:horizontal { + width: 5px; + margin-top: 2px; + margin-bottom: 2px; +} + +QMainWindow::separator:vertical { + height: 5px; + margin-left: 2px; + margin-right: 2px; +} + +/* QToolTip --------------------------------------------------------------- + +https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtooltip + +--------------------------------------------------------------------------- */ +QToolTip { + background-color: #fffdc2; + color: #000000; + border: 1px solid; + border-color: QLinearGradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #c0c1be, + stop: 1 #7a7e68); + /* Remove padding, for fix combo box tooltip */ + padding: 0px; + +} + + +/* QStatusBar ------------------------------------------------------------- + +https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qstatusbar + +--------------------------------------------------------------------------- */ +QStatusBar { + border: 1px solid #bebebe; + /* Fixes Spyder #9120, #9121 */ + background: #ececec; + /* Fixes #205, white vertical borders separating items */ +} + +QStatusBar::item { + border: none; +} + +QStatusBar QToolTip { + background-color: #fffdc2; + border: 1px solid; + color: #000000; + border-color: QLinearGradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #c0c1be, + stop: 1 #7a7e68); + /* Remove padding, for fix combo box tooltip */ + padding: 0px; + /* Reducing transparency to read better */ + opacity: 230; +} + +QStatusBar QLabel { + /* Fixes Spyder #9120, #9121 */ + background: transparent; +} + +/* QCheckBox -------------------------------------------------------------- + +https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcheckbox + +--------------------------------------------------------------------------- */ +QCheckBox { + background-color: #eeeeee; + color: #010101; + spacing: 4px; + outline: none; + padding-top: 4px; + padding-bottom: 4px; +} + +QCheckBox QWidget:disabled { + background-color: #eeeeee; + color: #a2a2a2; +} + +QCheckBox::indicator { + margin-left: 2px; + margin-bottom: 4px; + height: 16px; + width: 16px; +} + + + + + + +/* FIXME: Would like a gradient from #e7e8eb to #f7f7f7 +/* but running into a black background issue: +/* https://forum.qt.io/topic/90348/setting-qlineargradient-with-stylesheet-always-shows-black/3 */ +QToolBar { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #7c90ff, stop: 0.1 #6f84f1, stop: 0.9 #4655f0, stop: 1 #3b44ab); + background-color: #e7e8eb; +} + +QStatusBar { + background-color: #e7e8eb; +} + +QProgressBar { + height: 20px; + background-color: white; + text-align: center; + color: transparent; /* The percentage text */ +} + +/* FIXME: We end up with a black background for the QProgressBar and the text is no longer centered. Why? Probably because if we start customizing some aspects of a complex widget like QProgressBar, we need to customize ALL of them */ + +/* Make default QPushButtons blue. Works */ +QPushButton[default="true"], QProgressBar::chunk { + color: white; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9595d3, stop: 0.1 #b9cbf9, stop: 0.39 #69ace3, stop: 0.4 #4c95d9, stop: 1 #8dd2fb); +} + +/* Make QPushButtons yellowish glowing when hovered over. Works */ +/* Note that the order matters, so if we want default buttons to have the hover effect we need to do the hover effect after the default button stuff */ +QPushButton:hover { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fff, stop: 0.1 #ddd, stop: 0.39 #eef, stop: 0.4 #ddb, stop: 1 #fff); + color: black; +} + +/* Seems to work but if we start customizing some aspects, we need to customize ALL of them */ +QPushButton { + font-size: 11.5pt; + height: 20px; + padding-top: 1px; + padding-bottom: 0px; + padding-left: 20px; + padding-right: 20px; + border-radius: 11px; /* If we make this too large, then we get no rounded corners at all... why? */ + border-width: 1px; + border-style: solid; + border-color: #5b5b5b; + background-color: QLinearGradient( x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #fcfcfc, + stop: 0.1 #f1f1f1, + stop: 0.39 #e4e5e4, + stop: 0.4 #e7e7e6, + stop: 1 #ffffff); + shadow: #b0b0b0; +} +/* Make default QPushButtons blue. Works */ +QPushButton[default="true"] { + color: black; + background-color: QLinearGradient( x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #cde0f5, + stop: 0.33 #b2d6f5, + stop: 0.39 #a0cdf2, + stop: 0.66 #85beef, + stop: 1 #d1faff); + border-color: QLinearGradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #313399, + stop: 1 #5b5b5b); +} + +QPushButton[default="true"]:hover { + color: black; + background-color: QLinearGradient( x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #cee0f5, + stop: 0.33 #b2d6f5, + stop: 0.39 #a0cdf2, + stop: 0.66 #85beef, + stop: 1 #d1faff); + border-color: QLinearGradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #313399, + stop: 1 #5b5b5b); +} + +QPushButton:disabled{ background-color: QLinearGradient( x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #f3f3f3, + stop: 0.1 #ddd, + stop: 0.39 #eef, + stop: 0.4 #ddb, + stop: 1 #f2f2f2); +} +QPushButton:pressed{ background-color: orange; } + +QPushButton:hover{ background-color: QLinearGradient( x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #fff, + stop: 0.1 #ddd, + stop: 0.39 #eef, + stop: 0.4 #ddb, + stop: 1 #fff); +} +QPushButton:checked{ background-color: pink; } + +QDialog { + background-color: #ececec; +} + +/* FIXME: Seems not to work yet. We want more space between the Cancel, OK,... buttons in dialog boxes and the text above them */ +QDialogButtonBox { + margin-top: 30px; + padding-top: 30px; +} + + + +/* Menubar, menus, and menu items */ +/* https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenubar */ + +QMenu::item:pressed, QMenuBar::item:pressed, QMenu::item:selected { + color: white; + background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #7c90ff, stop: 0.1 #6f84f1, stop: 0.9 #4655f0, stop: 1 #3b44ab); /* Intensive blue */ +} + +QMenuBar, QMenuBar::item { + background: transparent; /* Otherwise it gets colored when hovering */ +} + +QMenu, QMenu::item { + background: #eee; +} + + +/* Filer left-hand pane, FIXME: Does not work (DOES work in qt-creator GUI editor!) */ +/* TODO: Check Filer with https://github.com/robertknight/Qt-Inspector or GammaRay on Linux */ +QObject[objectName="sidePane"] { + background-color: yellow; +} + +/* 'searchLineEdit->setObjectName("actionSearch");' is used in source code (DOES work!) */ +/* FIXME: are we using setStyleSheet in the source code somewhere, ovveriding parts of this? */ +QLineEdit#actionSearch { + border-radius: 0px; /* FIXME: Does not work, why? */ + background-color: transparent; /* Works */ + padding-left: 10px; /* Works */ + padding-right: 10px; /* Works */ + height: 100%; /* Works */ + border-width: 0px; /* Works */ + border-style: solid; + border-color: grey; +} + +QLineEdit#actionSearch:focus { + color: white; /* The text */ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #7c90ff, stop: 0.1 #6f84f1, stop: 0.9 #4655f0, stop: 1 #3b44ab); /* Like menu */ + height: 100%; /* Works */ +} + +QListView#actionCompleterPopup { + background: #eee; /* Works */ + selection-background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #7c90ff, stop: 0.1 #6f84f1, stop: 0.9 #4655f0, stop: 1 #3b44ab); /* Works, Intensive blue */ +} + +/* FIXME: Works for other QListViews but not the one in Action Search. Why? +QListView::item { + padding: 10px; +} + +QListView::item:hover { + background: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #7c90ff, stop: 0.1 #6f84f1, stop: 0.9 #4655f0, stop: 1 #3b44ab); +} +*/ + +MainWindow { + background-color: #ececec; +} +MainWindow#menuBar { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #fff, stop: 0.1 #eee, stop: 0.39 #eee, stop: 0.4 #ddd, stop: 0.954 #eee, stop: 1 #bbb); +} From 52b2815781973af96e6184596c78cc97adc20c89 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 11:05:31 +0100 Subject: [PATCH 18/36] no numpy usage --- .../Grab.app/Resources/QtImageViewer.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Under Construction/Grab.app/Resources/QtImageViewer.py b/Under Construction/Grab.app/Resources/QtImageViewer.py index 1a2e413d..dac3fa33 100644 --- a/Under Construction/Grab.app/Resources/QtImageViewer.py +++ b/Under Construction/Grab.app/Resources/QtImageViewer.py @@ -19,19 +19,19 @@ raise ImportError("Requires PyQt (version 5 or 6)") # numpy is optional: only needed if you want to display numpy 2d arrays as images. -try: - import numpy as np -except ImportError: - np = None +# try: +# import numpy as np +# except ImportError: +# np = None # qimage2ndarray is optional: useful for displaying numpy 2d arrays as images. # !!! qimage2ndarray requires PyQt5. # Some custom code in the viewer appears to handle the conversion from numpy 2d arrays, # so qimage2ndarray probably is not needed anymore. I've left it here just in case. -try: - import qimage2ndarray -except ImportError: - qimage2ndarray = None +# try: +# import qimage2ndarray +# except ImportError: +# qimage2ndarray = None __author__ = ["Marcel Goldschen-Ohm ", "Tuxa alias Hierosme"] From 75f00021720dc6336feeeb8c31bf442928fdc963 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 11:11:52 +0100 Subject: [PATCH 19/36] no numpy usage --- .../Grab.app/Resources/QtImageViewer.py | 2 +- Under Construction/Grab.app/Resources/grab.py | 21 ----- .../Resources/widget_transparent_window.py | 84 ------------------- 3 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 Under Construction/Grab.app/Resources/widget_transparent_window.py diff --git a/Under Construction/Grab.app/Resources/QtImageViewer.py b/Under Construction/Grab.app/Resources/QtImageViewer.py index dac3fa33..a4ca8e55 100644 --- a/Under Construction/Grab.app/Resources/QtImageViewer.py +++ b/Under Construction/Grab.app/Resources/QtImageViewer.py @@ -26,7 +26,7 @@ # qimage2ndarray is optional: useful for displaying numpy 2d arrays as images. # !!! qimage2ndarray requires PyQt5. -# Some custom code in the viewer appears to handle the conversion from numpy 2d arrays, +# ./Some custom code in the viewer appears to handle the conversion from numpy 2d arrays, # so qimage2ndarray probably is not needed anymore. I've left it here just in case. # try: # import qimage2ndarray diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index e37352c6..da0e5264 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -14,7 +14,6 @@ from dialog_screen_grab import ScreenGrabDialog from dialog_help import HelpDialog from dialog_selection_screen_grab import SelectionGrabDialog -from widget_transparent_window import TransWindow from widget_snipping_tool import SnippingWidget from preference_window import PreferenceWindow @@ -46,7 +45,6 @@ def __init__(self, parent=None): self.TimedScreenGrabDialog = None self.ScreenGrabDialog = None self.SelectionGrabDialog = None - self.TransWindow = None self.PreferenceWindow = None self.preference_pointer = None self.preference_enable_sound = None @@ -382,23 +380,13 @@ def _showScreenGrabDialog(self): if self.ActionMenuCaptureScreen.isEnabled(): self.hide() - # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) - self.ScreenGrabDialog = ScreenGrabDialog(self) self.ScreenGrabDialog.screen_dialog_signal_quit.connect(self._CloseAllDialogs) self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) self.ScreenGrabDialog.installEventFilter(self) - # self.TransWindow = TransWindow(self) - # self.TransWindow.transparent_window_signal_release.connect(self._ScreenGrabStart) - # self.TransWindow.transparent_window_signal_quit.connect(self._CloseAllDialogs) - - self.ScreenGrabDialog.hide() self.ScreenGrabDialog.show() - # self.TransWindow.hide() - # self.TransWindow.show() - while not self.windowHandle(): QApplication.processEvents() @@ -425,12 +413,6 @@ def showEvent(self, event: QShowEvent) -> None: super(Window, self).showEvent(event) event.accept() - # def eventFilter(self, source, event): - # if event.type() == QEvent.FocusOut and source is self.ScreenGrabDialog: - # print('eventFilter: focus out') - # # return true here to bypass default behaviour - # return super(Window, self).eventFilter(source, event) - def _SelectionGrabStart(self): self._CloseAllDialogs() self.snipArea() @@ -461,9 +443,6 @@ def _CloseAllDialogs(self): if self.ScreenGrabDialog and isinstance(self.ScreenGrabDialog, ScreenGrabDialog): self.ScreenGrabDialog.close() self.ScreenGrabDialog = None - if self.TransWindow and isinstance(self.TransWindow, TransWindow): - self.TransWindow.close() - self.TransWindow = None if self.SelectionGrabDialog and isinstance(self.SelectionGrabDialog, SelectionGrabDialog): self.SelectionGrabDialog.close() self.SelectionGrabDialog = None diff --git a/Under Construction/Grab.app/Resources/widget_transparent_window.py b/Under Construction/Grab.app/Resources/widget_transparent_window.py deleted file mode 100644 index c8560c59..00000000 --- a/Under Construction/Grab.app/Resources/widget_transparent_window.py +++ /dev/null @@ -1,84 +0,0 @@ -try: - from PyQt6.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize - from PyQt6.QtGui import ( - QImage, - QPixmap, - QPainterPath, - QMouseEvent, - QPainter, - QPen, - QGuiApplication, - QKeySequence, - QCloseEvent - ) - from PyQt6.QtWidgets import QWidget, QGridLayout, QApplication, QShortcut -except ImportError: - try: - from PyQt5.QtCore import Qt, QRectF, QPoint, QPointF, pyqtSignal, QEvent, QSize - from PyQt5.QtGui import ( - QImage, - QPixmap, - QPainterPath, - QMouseEvent, - QPainter, - QPen, - QGuiApplication, - QKeySequence, - QCloseEvent - ) - from PyQt5.QtWidgets import QWidget, QGridLayout, QApplication, QShortcut - except ImportError: - raise ImportError("Requires PyQt (version 5 or 6)") - - -import numpy - - -class TransWindow(QWidget): - mouse_press = pyqtSignal() - transparent_window_signal_quit = pyqtSignal() - transparent_window_signal_release = pyqtSignal() - - def __init__(self, screen_grab=None, selection=None): - QWidget.__init__(self, None, Qt.Window) - self.layout = QGridLayout() - self.setLayout(self.layout) - self.screen_grab = screen_grab - self.selection = selection - self.showMaximized() - self.activateWindow() - self.raise_() - self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) - self.setWindowFlags(Qt.Window | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) - # self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) - - screenGeometry = QApplication.desktop().availableGeometry() - self.setGeometry(screenGeometry) - self.setStyleSheet("QWidget { background-color: rgba(255,255,255, 5%); }") - self.setWindowOpacity(0.5) - self.pos = None - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.cancel_transparent_window) - - def closeEvent(self, event: QCloseEvent) -> None: - super(TransWindow, self).setWindowOpacity(0.0) - super(TransWindow, self).closeEvent(event) - QApplication.processEvents() - event.accept() - - def mousePressEvent(self, event): - event: QMouseEvent - - if self.selection: - xd = QApplication.desktop().screenGeometry().x() - QApplication.desktop().availableGeometry().x() - yd = QApplication.desktop().screenGeometry().y() - QApplication.desktop().availableGeometry().y() - self.pos = numpy.array([event.pos().x() - xd, - event.pos().y() - yd]) - print(self.pos) - - def mouseReleaseEvent(self, event): - self.transparent_window_signal_release.emit() - self.close() - - def cancel_transparent_window(self): - self.transparent_window_signal_quit.emit() From bfeb01569636229877d09e672f2654edaf82417b Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 11:28:15 +0100 Subject: [PATCH 20/36] Mini fixe for True hellosystem --- Under Construction/Grab.app/Resources/grab.py | 2 ++ Under Construction/Grab.app/Resources/main_window.ui | 5 +---- Under Construction/Grab.app/Resources/main_window_ui.py | 3 --- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index da0e5264..6ac312d4 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -446,6 +446,8 @@ def _CloseAllDialogs(self): if self.SelectionGrabDialog and isinstance(self.SelectionGrabDialog, SelectionGrabDialog): self.SelectionGrabDialog.close() self.SelectionGrabDialog = None + if self.snippingWidget and isinstance(self.snippingWidget, SnippingWidget): + self.snippingWidget.initialState() if self.isHidden(): self.show() diff --git a/Under Construction/Grab.app/Resources/main_window.ui b/Under Construction/Grab.app/Resources/main_window.ui index 7c356899..b2137a10 100644 --- a/Under Construction/Grab.app/Resources/main_window.ui +++ b/Under Construction/Grab.app/Resources/main_window.ui @@ -76,9 +76,6 @@ Edit - - - @@ -173,7 +170,7 @@ - .. + ../../../../../../.designer/backup../../../../../../.designer/backup Print diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index 53fd0981..d8c05a35 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -151,9 +151,6 @@ def setupUi(self, MainWindow): self.menuFile.addAction(self.ActionMenuFilePrint) self.menuFile.addAction(self.ActionMenuFilePrintSetup) self.menuEdit.addAction(self.ActionMenuEditCopy) - self.menuEdit.addAction(self.actionGrop) - self.menuEdit.addAction(self.actionUndo) - self.menuEdit.addAction(self.actionRedo) self.menuEdit.addSeparator() self.menuEdit.addAction(self.ActionMenuEditPreference) self.menuTimer.addAction(self.ActionUpdateTimerTo1Sec) From 875652d89a3ad332cd12e71a3c698b4a7bd9336e Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 12:14:28 +0100 Subject: [PATCH 21/36] Better dialog size optimisation --- .../Grab.app/Resources/dialog_screen_grab.py | 5 ++++- .../Grab.app/Resources/dialog_screen_grab.ui | 20 ++++++++++++------- .../Resources/dialog_screen_grab_ui.py | 17 ++++++++++------ .../Resources/dialog_selection_grab.ui | 20 ++++++++++++------- .../Resources/dialog_selection_grab_ui.py | 19 +++++++++++------- .../Resources/dialog_selection_screen_grab.py | 3 +++ .../Resources/dialog_timed_screen_grab.py | 15 +++----------- .../Resources/dialog_timed_screen_grab.ui | 14 ++++++------- .../Resources/dialog_timed_screen_grab_ui.py | 12 +++++------ .../Grab.app/Resources/dialog_window_grab.ui | 16 +++++++-------- .../Resources/dialog_window_grab_ui.py | 15 +++++++------- Under Construction/Grab.app/Resources/grab.py | 6 +----- 12 files changed, 89 insertions(+), 73 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 41e9fa4d..ee1ceca5 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -22,14 +22,17 @@ def __init__(self, parent=None): self.ui.setupUi(self) self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - self.setFixedSize(self.size()) self.ui.button_cancel.clicked.connect(self.screen_dialog_quit) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.screen_dialog_quit) + self.adjustSize() + self.setFixedSize(self.size()) + self.setFocus() + def focusOutEvent(self, event: QFocusEvent) -> None: if self.hasFocus() or self.ui.button_cancel.hasFocus(): event.accept() diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index 5ec851f5..0a17dfa1 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -9,10 +9,16 @@ 0 0 - 493 - 206 + 486 + 203 + + + 0 + 0 + + Qt::NoFocus @@ -58,7 +64,7 @@ - 40 + 0 20 @@ -102,7 +108,7 @@ - 40 + 0 20 @@ -140,7 +146,7 @@ - 40 + 0 20 @@ -159,7 +165,7 @@ 20 - 20 + 0 @@ -182,7 +188,7 @@ - 40 + 0 20 diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index 312189d7..a27a541d 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -15,7 +15,12 @@ class Ui_ScreenGrabDialog(object): def setupUi(self, ScreenGrabDialog): ScreenGrabDialog.setObjectName("ScreenGrabDialog") ScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) - ScreenGrabDialog.resize(493, 206) + ScreenGrabDialog.resize(486, 203) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(ScreenGrabDialog.sizePolicy().hasHeightForWidth()) + ScreenGrabDialog.setSizePolicy(sizePolicy) ScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) ScreenGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(ScreenGrabDialog) @@ -29,7 +34,7 @@ def setupUi(self, ScreenGrabDialog): self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setObjectName("horizontalLayout_2") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.icon = QtWidgets.QLabel(ScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) @@ -44,7 +49,7 @@ def setupUi(self, ScreenGrabDialog): self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) @@ -58,16 +63,16 @@ def setupUi(self, ScreenGrabDialog): self.Label.setObjectName("Label") self.verticalLayout_2.addWidget(self.Label) self.horizontalLayout_2.addLayout(self.verticalLayout_2) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout.setSpacing(22) self.horizontalLayout.setObjectName("horizontalLayout") - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem4 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) self.button_cancel = QtWidgets.QPushButton(ScreenGrabDialog) self.button_cancel.setDefault(True) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui index e6616d2f..95ca174f 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -13,6 +13,12 @@ 178 + + + 0 + 0 + + Qt::NoFocus @@ -58,7 +64,7 @@ - 40 + 0 20 @@ -88,7 +94,7 @@ - ../../../../../../Processes.app/Resources/Processes.png + ../../../../../../../../../../Processes.app/Resources/Processes.png true @@ -102,7 +108,7 @@ - 40 + 0 20 @@ -121,7 +127,7 @@ - Drag over the portion of the screen you want to capture. + To capture a selection, click outside this window. (This window will be close) then drag over the portion of the screen you want to capture. Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -140,7 +146,7 @@ - 40 + 0 20 @@ -159,7 +165,7 @@ 20 - 20 + 0 @@ -179,7 +185,7 @@ - 40 + 0 20 diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py index df3ab416..8356f6a6 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -16,6 +16,11 @@ def setupUi(self, SelectionGrabDialog): SelectionGrabDialog.setObjectName("SelectionGrabDialog") SelectionGrabDialog.setWindowModality(QtCore.Qt.WindowModal) SelectionGrabDialog.resize(486, 178) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(SelectionGrabDialog.sizePolicy().hasHeightForWidth()) + SelectionGrabDialog.setSizePolicy(sizePolicy) SelectionGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) SelectionGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(SelectionGrabDialog) @@ -29,7 +34,7 @@ def setupUi(self, SelectionGrabDialog): self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setObjectName("horizontalLayout_2") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.icon = QtWidgets.QLabel(SelectionGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) @@ -40,11 +45,11 @@ def setupUi(self, SelectionGrabDialog): self.icon.setMinimumSize(QtCore.QSize(64, 64)) self.icon.setMaximumSize(QtCore.QSize(64, 64)) self.icon.setText("") - self.icon.setPixmap(QtGui.QPixmap("./../../../../../../Processes.app/Resources/Processes.png")) + self.icon.setPixmap(QtGui.QPixmap("./../../../../../../../../../../Processes.app/Resources/Processes.png")) self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) @@ -58,16 +63,16 @@ def setupUi(self, SelectionGrabDialog): self.Label.setObjectName("Label") self.verticalLayout_2.addWidget(self.Label) self.horizontalLayout_2.addLayout(self.verticalLayout_2) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout.setSpacing(22) self.horizontalLayout.setObjectName("horizontalLayout") - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem4 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) self.button_cancel = QtWidgets.QPushButton(SelectionGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) @@ -91,5 +96,5 @@ def setupUi(self, SelectionGrabDialog): def retranslateUi(self, SelectionGrabDialog): _translate = QtCore.QCoreApplication.translate SelectionGrabDialog.setWindowTitle(_translate("SelectionGrabDialog", "Selection Grab")) - self.Label.setText(_translate("SelectionGrabDialog", "Drag over the portion of the screen you want to capture.")) + self.Label.setText(_translate("SelectionGrabDialog", "To capture a selection, click outside this window. (This window will be close) then drag over the portion of the screen you want to capture.")) self.button_cancel.setText(_translate("SelectionGrabDialog", "Cancel")) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py index be11cf14..92b7b392 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -27,6 +27,9 @@ def __init__(self, parent=None): quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.selection_dialog_quit) + self.adjustSize() + self.setFixedSize(self.size()) + self.setFocus() def focusOutEvent(self, event: QFocusEvent) -> None: diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index 8941747c..635dc888 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -19,18 +19,6 @@ def __init__(self, parent=None, timer=None): self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) - effect1 = QGraphicsDropShadowEffect() - effect1.setBlurRadius(2) - effect1.setOffset(0, 1) - effect1.setColor(QColor(69, 69, 69, 127)) - self.ui.button_cancel.setGraphicsEffect(effect1) - - effect2 = QGraphicsDropShadowEffect() - effect2.setBlurRadius(2) - effect2.setOffset(0, 1) - effect2.setColor(QColor(69, 69, 69, 127)) - self.ui.button_start_timer.setGraphicsEffect(effect2) - if self.sec > 1: self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} seconds") else: @@ -43,6 +31,9 @@ def __init__(self, parent=None, timer=None): quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.timed_dialog_quit) + self.adjustSize() + self.setFixedSize(self.size()) + def timed_dialog_quit(self): self.timer_dialog_signal_quit.emit() diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui index 311fd05c..50e8dec4 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -3,7 +3,7 @@ TimedScreenGrabDialog - Qt::NonModal + Qt::WindowModal @@ -58,7 +58,7 @@ - 40 + 0 20 @@ -88,7 +88,7 @@ - ../../Processes.app/Resources/Processes.png + ../../../../../../Processes.app/Resources/Processes.png true @@ -102,7 +102,7 @@ - 40 + 0 20 @@ -140,7 +140,7 @@ - 40 + 0 20 @@ -159,7 +159,7 @@ 20 - 20 + 0 @@ -179,7 +179,7 @@ - 40 + 0 20 diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py index d8976da3..5a6f4862 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py @@ -29,7 +29,7 @@ def setupUi(self, TimedScreenGrabDialog): self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setObjectName("horizontalLayout_2") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.icon = QtWidgets.QLabel(TimedScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) @@ -40,11 +40,11 @@ def setupUi(self, TimedScreenGrabDialog): self.icon.setMinimumSize(QtCore.QSize(64, 64)) self.icon.setMaximumSize(QtCore.QSize(64, 64)) self.icon.setText("") - self.icon.setPixmap(QtGui.QPixmap("./../../Processes.app/Resources/Processes.png")) + self.icon.setPixmap(QtGui.QPixmap("./../../../../../../Processes.app/Resources/Processes.png")) self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) @@ -58,16 +58,16 @@ def setupUi(self, TimedScreenGrabDialog): self.Label.setObjectName("Label") self.verticalLayout_2.addWidget(self.Label) self.horizontalLayout_2.addLayout(self.verticalLayout_2) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout.setSpacing(22) self.horizontalLayout.setObjectName("horizontalLayout") - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem4 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) self.button_cancel = QtWidgets.QPushButton(TimedScreenGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui index 70eaba99..6c47252e 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -3,14 +3,14 @@ WindowGrabDialog - Qt::NonModal + Qt::WindowModal 0 0 - 486 - 178 + 493 + 188 @@ -58,7 +58,7 @@ - 40 + 0 20 @@ -102,7 +102,7 @@ - 40 + 0 20 @@ -140,7 +140,7 @@ - 40 + 0 20 @@ -159,7 +159,7 @@ 20 - 20 + 0 @@ -179,7 +179,7 @@ - 40 + 0 20 diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py index f3587890..546a8d64 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py @@ -15,7 +15,7 @@ class Ui_WindowGrabDialog(object): def setupUi(self, WindowGrabDialog): WindowGrabDialog.setObjectName("WindowGrabDialog") WindowGrabDialog.setWindowModality(QtCore.Qt.NonModal) - WindowGrabDialog.resize(486, 178) + WindowGrabDialog.resize(493, 188) WindowGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) WindowGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(WindowGrabDialog) @@ -29,7 +29,7 @@ def setupUi(self, WindowGrabDialog): self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) self.horizontalLayout_2.setObjectName("horizontalLayout_2") - spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.icon = QtWidgets.QLabel(WindowGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) @@ -40,11 +40,11 @@ def setupUi(self, WindowGrabDialog): self.icon.setMinimumSize(QtCore.QSize(64, 64)) self.icon.setMaximumSize(QtCore.QSize(64, 64)) self.icon.setText("") - self.icon.setPixmap(QtGui.QPixmap("./../../../../../../Processes.app/Resources/Processes.png")) + self.icon.setPixmap(QtGui.QPixmap("./../../../../../../../../../../Processes.app/Resources/Processes.png")) self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) @@ -58,16 +58,16 @@ def setupUi(self, WindowGrabDialog): self.Label.setObjectName("Label") self.verticalLayout_2.addWidget(self.Label) self.horizontalLayout_2.addLayout(self.verticalLayout_2) - spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout.setSpacing(22) self.horizontalLayout.setObjectName("horizontalLayout") - spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem4 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem4) self.button_cancel = QtWidgets.QPushButton(WindowGrabDialog) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) @@ -81,6 +81,7 @@ def setupUi(self, WindowGrabDialog): self.button_cancel.setObjectName("button_cancel") self.horizontalLayout.addWidget(self.button_cancel) self.button_choose_window = QtWidgets.QPushButton(WindowGrabDialog) + self.button_choose_window.setDefault(True) self.button_choose_window.setObjectName("button_choose_window") self.horizontalLayout.addWidget(self.button_choose_window) self.MainVbox.addLayout(self.horizontalLayout) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 6ac312d4..185d9c22 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -145,11 +145,6 @@ def connectSignalsSlots(self): self.ActionUpdateTimerTo9Secs.triggered.connect(self._timer_change_for_9_secs) self.ActionUpdateTimerTo10Secs.triggered.connect(self._timer_change_for_10_secs) - # Capture / Area - - # self.ui.pushButton_area.clicked.connect(self.snipArea) - # self.ui.pushButton_full.clicked.connect(self.snipFull) - # About self.ActionMenuHelpAbout.triggered.connect(self._showAboutDialog) self.ActionMenuHelpDocumentation.triggered.connect(self._showHelpDialog) @@ -175,6 +170,7 @@ def onSnippingCompleted(self, img): self.setWindowModified(True) self.update_actions() + self.show() def snipArea(self): self.setWindowState(Qt.WindowMinimized) From eb19d9f53f1e090eb64b6755b95b62d72da6cd9d Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 13:19:50 +0100 Subject: [PATCH 22/36] Better dialog size optimisation take2 --- .../Grab.app/Resources/dialog_screen_grab.py | 23 ++++++++++---- .../Grab.app/Resources/dialog_screen_grab.ui | 5 ++- .../Resources/dialog_screen_grab_ui.py | 3 +- .../Resources/dialog_selection_grab.ui | 5 ++- .../Resources/dialog_selection_grab_ui.py | 5 +-- .../Resources/dialog_selection_screen_grab.py | 16 ++++++++-- .../Resources/dialog_timed_screen_grab.py | 31 ++++++++++++++----- .../Resources/dialog_timed_screen_grab.ui | 9 ++++-- .../Resources/dialog_timed_screen_grab_ui.py | 7 +++-- .../Grab.app/Resources/dialog_window_grab.ui | 5 ++- .../Resources/dialog_window_grab_ui.py | 5 +-- .../Grab.app/Resources/grab-x11.py | 0 .../Resources/widget_snipping_tool.py | 13 ++++++-- 13 files changed, 94 insertions(+), 33 deletions(-) delete mode 100644 Under Construction/Grab.app/Resources/grab-x11.py diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index ee1ceca5..dc871ee2 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,5 +1,4 @@ import os -import sys from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent @@ -14,25 +13,37 @@ class ScreenGrabDialog(QDialog): def __init__(self, parent=None): super(ScreenGrabDialog, self).__init__(parent) - self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) - self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) - self.setWindowModality(Qt.WindowModality.ApplicationModal) self.ui = Ui_ScreenGrabDialog() self.ui.setupUi(self) - self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) + + self.setupCustomUi() + self.connectSignalsSlots() + self.initialState() + + def setupCustomUi(self): + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + self.setWindowModality(Qt.WindowModality.ApplicationModal) + self.setWindowFlags(Qt.Dialog) + self.ui.icon.setPixmap( + QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( + 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation + ) + ) + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.screen_dialog_quit) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.screen_dialog_quit) + def initialState(self): self.adjustSize() self.setFixedSize(self.size()) self.setFocus() - def focusOutEvent(self, event: QFocusEvent) -> None: if self.hasFocus() or self.ui.button_cancel.hasFocus(): event.accept() diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index 0a17dfa1..ec958bd3 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -32,7 +32,7 @@ - 6 + 12 QLayout::SetNoConstraint @@ -51,6 +51,9 @@ + + 12 + QLayout::SetDefaultConstraint diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index a27a541d..0934ffcb 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -28,11 +28,12 @@ def setupUi(self, ScreenGrabDialog): self.MainVbox = QtWidgets.QVBoxLayout() self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) self.MainVbox.setContentsMargins(0, 0, 0, 0) - self.MainVbox.setSpacing(6) + self.MainVbox.setSpacing(12) self.MainVbox.setObjectName("MainVbox") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(12) self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui index 95ca174f..7c2499de 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -32,7 +32,7 @@ - 6 + 12 QLayout::SetNoConstraint @@ -51,6 +51,9 @@ + + 12 + QLayout::SetDefaultConstraint diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py index 8356f6a6..b60ca063 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -16,7 +16,7 @@ def setupUi(self, SelectionGrabDialog): SelectionGrabDialog.setObjectName("SelectionGrabDialog") SelectionGrabDialog.setWindowModality(QtCore.Qt.WindowModal) SelectionGrabDialog.resize(486, 178) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(SelectionGrabDialog.sizePolicy().hasHeightForWidth()) @@ -28,11 +28,12 @@ def setupUi(self, SelectionGrabDialog): self.MainVbox = QtWidgets.QVBoxLayout() self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) self.MainVbox.setContentsMargins(0, 0, 0, 0) - self.MainVbox.setSpacing(6) + self.MainVbox.setSpacing(12) self.MainVbox.setObjectName("MainVbox") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(12) self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py index 92b7b392..1d590164 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -1,5 +1,4 @@ import os -import sys from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent @@ -20,13 +19,26 @@ def __init__(self, parent=None): self.ui = Ui_SelectionGrabDialog() self.ui.setupUi(self) - self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) + + self.setupCustomUi() + self.connectSignalsSlots() + self.initialState() + + def setupCustomUi(self): + self.ui.icon.setPixmap( + QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( + 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation + ) + ) + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.selection_dialog_quit) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.selection_dialog_quit) + def initialState(self): self.adjustSize() self.setFixedSize(self.size()) diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index 635dc888..a2ae4f2b 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -1,8 +1,9 @@ import os -import sys -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QColor -from PyQt5.QtWidgets import QDialog, QShortcut, QGraphicsDropShadowEffect + from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtGui import QPixmap, QIcon, QKeySequence +from PyQt5.QtWidgets import QDialog, QShortcut + from dialog_timed_screen_grab_ui import Ui_TimedScreenGrabDialog @@ -12,31 +13,45 @@ class TimedScreenGrabDialog(QDialog): def __init__(self, parent=None, timer=None): super(TimedScreenGrabDialog, self).__init__(parent) - self.setWindowFlags(Qt.Dialog) + self.sec = int(timer / 1000) self.ui = Ui_TimedScreenGrabDialog() self.ui.setupUi(self) - self.ui.icon.setPixmap(QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png"))) - self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) if self.sec > 1: self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} seconds") else: self.ui.Label.setText(self.ui.Label.text() % f"{self.sec} second") - self.setFixedSize(self.size()) + self.setupCustomUi() + self.connectSignalsSlots() + self.initialState() + + def setupCustomUi(self): + self.setWindowFlags(Qt.Dialog) + self.ui.icon.setPixmap( + QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( + 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation + ) + ) + + self.setWindowIcon(QIcon(os.path.join(os.path.dirname(__file__), "Grab.png"))) + + def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.timed_dialog_quit) self.ui.button_start_timer.clicked.connect(self.timed_dialog_start) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) quitShortcut1.activated.connect(self.timed_dialog_quit) + def initialState(self): self.adjustSize() self.setFixedSize(self.size()) + self.setFocus() + def timed_dialog_quit(self): self.timer_dialog_signal_quit.emit() def timed_dialog_start(self): self.timer_dialog_signal_start.emit() - diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui index 50e8dec4..da56ae00 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -9,8 +9,8 @@ 0 0 - 486 - 178 + 446 + 180 @@ -26,7 +26,7 @@ - 6 + 12 QLayout::SetNoConstraint @@ -45,6 +45,9 @@ + + 12 + QLayout::SetDefaultConstraint diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py index 5a6f4862..6298baf8 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py @@ -14,8 +14,8 @@ class Ui_TimedScreenGrabDialog(object): def setupUi(self, TimedScreenGrabDialog): TimedScreenGrabDialog.setObjectName("TimedScreenGrabDialog") - TimedScreenGrabDialog.setWindowModality(QtCore.Qt.NonModal) - TimedScreenGrabDialog.resize(486, 178) + TimedScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) + TimedScreenGrabDialog.resize(446, 180) TimedScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) TimedScreenGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(TimedScreenGrabDialog) @@ -23,11 +23,12 @@ def setupUi(self, TimedScreenGrabDialog): self.MainVbox = QtWidgets.QVBoxLayout() self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) self.MainVbox.setContentsMargins(0, 0, 0, 0) - self.MainVbox.setSpacing(6) + self.MainVbox.setSpacing(12) self.MainVbox.setObjectName("MainVbox") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(12) self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui index 6c47252e..1025a4c4 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -26,7 +26,7 @@ - 6 + 12 QLayout::SetNoConstraint @@ -45,6 +45,9 @@ + + 12 + QLayout::SetDefaultConstraint diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py index 546a8d64..5bf29d5b 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py @@ -14,7 +14,7 @@ class Ui_WindowGrabDialog(object): def setupUi(self, WindowGrabDialog): WindowGrabDialog.setObjectName("WindowGrabDialog") - WindowGrabDialog.setWindowModality(QtCore.Qt.NonModal) + WindowGrabDialog.setWindowModality(QtCore.Qt.WindowModal) WindowGrabDialog.resize(493, 188) WindowGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) WindowGrabDialog.setModal(True) @@ -23,11 +23,12 @@ def setupUi(self, WindowGrabDialog): self.MainVbox = QtWidgets.QVBoxLayout() self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) self.MainVbox.setContentsMargins(0, 0, 0, 0) - self.MainVbox.setSpacing(6) + self.MainVbox.setSpacing(12) self.MainVbox.setObjectName("MainVbox") self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) + self.horizontalLayout_2.setSpacing(12) self.horizontalLayout_2.setObjectName("horizontalLayout_2") spacerItem = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) diff --git a/Under Construction/Grab.app/Resources/grab-x11.py b/Under Construction/Grab.app/Resources/grab-x11.py deleted file mode 100644 index e69de29b..00000000 diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index 5afb5b9c..19aa9301 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -36,12 +36,21 @@ def __init__(self, parent=None, cursor=None): Qt.UpArrowCursor: "UpArrowCursor", Qt.WhatsThisCursor: "WhatsThisCursor", } + + self.setupCustomUi() + self.connectSignalsSlots() self.initialState() - def initialState(self): + def setupCustomUi(self): self.setAttribute(Qt.WA_TranslucentBackground) self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint | Qt.FramelessWindowHint) + def connectSignalsSlots(self): + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.cancel_widget_snapping) + + def initialState(self): + self.begin = QPoint() self.end = QPoint() self.qp = QPainter() @@ -49,8 +58,6 @@ def initialState(self): self.selection.is_snipping = False self.UpdateScreen() - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.cancel_widget_snapping) def cancel_widget_snapping(self): self.selection.is_snipping = False From f6e66913913dfce8decbe37b9cf2e337927e22fa Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 14:57:15 +0100 Subject: [PATCH 23/36] Have a Escape shortcut for each dialog --- .../Grab.app/Resources/dialog_screen_grab.py | 9 +++-- .../Resources/dialog_selection_screen_grab.py | 11 +++++-- .../Resources/dialog_timed_screen_grab.py | 7 ++-- Under Construction/Grab.app/Resources/grab.py | 33 ++++++++++++++----- 4 files changed, 40 insertions(+), 20 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index dc871ee2..a0644ccc 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -22,9 +22,9 @@ def __init__(self, parent=None): self.initialState() def setupCustomUi(self): - self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) - self.setWindowModality(Qt.WindowModality.ApplicationModal) - self.setWindowFlags(Qt.Dialog) + self.setWindowFlags( + Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowStaysOnTopHint + ) self.ui.icon.setPixmap( QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation @@ -35,8 +35,6 @@ def setupCustomUi(self): def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.screen_dialog_quit) - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.screen_dialog_quit) def initialState(self): self.adjustSize() @@ -53,5 +51,6 @@ def focusOutEvent(self, event: QFocusEvent) -> None: self.close() def screen_dialog_quit(self): + self.ui.button_cancel.setFocus(True) self.screen_dialog_signal_quit.emit() self.close() diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py index 1d590164..10187764 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -13,9 +13,6 @@ class SelectionGrabDialog(QDialog): def __init__(self, parent=None): super(SelectionGrabDialog, self).__init__(parent) - self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) - self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) - self.setWindowModality(Qt.WindowModality.ApplicationModal) self.ui = Ui_SelectionGrabDialog() self.ui.setupUi(self) @@ -25,6 +22,9 @@ def __init__(self, parent=None): self.initialState() def setupCustomUi(self): + self.setWindowFlags( + Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowStaysOnTopHint + ) self.ui.icon.setPixmap( QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation @@ -44,11 +44,16 @@ def initialState(self): self.setFocus() + def closeEvent(self, event): + super(SelectionGrabDialog, self).closeEvent(event) + event.accept() + def focusOutEvent(self, event: QFocusEvent) -> None: if self.hasFocus() or self.ui.button_cancel.hasFocus(): event.accept() else: event.accept() + self.selection_dialog_signal_start.emit() self.close() diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index a2ae4f2b..005b5014 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -28,7 +28,9 @@ def __init__(self, parent=None, timer=None): self.initialState() def setupCustomUi(self): - self.setWindowFlags(Qt.Dialog) + self.setWindowFlags( + Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowStaysOnTopHint + ) self.ui.icon.setPixmap( QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation @@ -41,9 +43,6 @@ def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.timed_dialog_quit) self.ui.button_start_timer.clicked.connect(self.timed_dialog_start) - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.timed_dialog_quit) - def initialState(self): self.adjustSize() self.setFixedSize(self.size()) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 185d9c22..842af9e1 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,7 +1,16 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox, QActionGroup, qApp, QPushButton -from PyQt5.QtGui import QPixmap, QIcon, QPainter, QShowEvent +from PyQt5.QtWidgets import ( + QApplication, + QMainWindow, + QFileDialog, + QMessageBox, + QActionGroup, + qApp, + QPushButton, + QShortcut, +) +from PyQt5.QtGui import QPixmap, QIcon, QPainter, QShowEvent, QKeySequence from PyQt5.QtCore import Qt, QTimer, QLoggingCategory, QByteArray, QSettings, QUrl, QEvent from PyQt5.QtPrintSupport import QPrintDialog, QPrinter, QPrintPreviewDialog from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer @@ -381,6 +390,9 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) self.ScreenGrabDialog.installEventFilter(self) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self._CloseAllDialogs) + self.ScreenGrabDialog.show() while not self.windowHandle(): @@ -390,13 +402,14 @@ def _showSelectionGrabDialog(self): if self.ActionMenuCaptureSelection.isEnabled(): self.hide() - # self.ScreenGrabDialog.setWindowFlags(self.ScreenGrabDialog.windowFlags() & Qt.WindowStaysOnTopHint) - if self.SelectionGrabDialog is None: - self.SelectionGrabDialog = SelectionGrabDialog(self) - self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) - self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) + self.SelectionGrabDialog = SelectionGrabDialog(self) + self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) + self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) + + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self._CloseAllDialogs) - self.SelectionGrabDialog.exec_() + self.SelectionGrabDialog.exec_() self.show() def hideEvent(self, event: QShowEvent) -> None: @@ -425,6 +438,10 @@ def _showTimedScreenGrabDialog(self): self.TimedScreenGrabDialog = TimedScreenGrabDialog(timer=self.timer_count) self.TimedScreenGrabDialog.timer_dialog_signal_start.connect(self._TimedScreenGrabStart) self.TimedScreenGrabDialog.timer_dialog_signal_quit.connect(self._CloseAllDialogs) + + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self._CloseAllDialogs) + self.TimedScreenGrabDialog.exec_() self.show() From e6d6ebff55e7012ce37e452b5fa4eb829160844a Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 15:33:38 +0100 Subject: [PATCH 24/36] Better dialog button position --- .../Grab.app/Resources/dialog_screen_grab.ui | 2 +- .../Resources/dialog_screen_grab_ui.py | 2 +- .../Resources/dialog_selection_grab.ui | 2 +- .../Resources/dialog_selection_grab_ui.py | 2 +- .../Resources/dialog_timed_screen_grab.ui | 11 +++++++---- .../Resources/dialog_timed_screen_grab_ui.py | 6 +++--- .../Grab.app/Resources/dialog_window_grab.ui | 2 +- .../Resources/dialog_window_grab_ui.py | 2 +- Under Construction/Grab.app/Resources/grab.py | 19 +++++++------------ 9 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index ec958bd3..eb26c076 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -168,7 +168,7 @@ 20 - 0 + 20 diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index 0934ffcb..ea40dfaf 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -67,7 +67,7 @@ def setupUi(self, ScreenGrabDialog): spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui index 7c2499de..162a6d1c 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -168,7 +168,7 @@ 20 - 0 + 20 diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py index b60ca063..e2abf5a4 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -67,7 +67,7 @@ def setupUi(self, SelectionGrabDialog): spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui index da56ae00..aed17d90 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -9,8 +9,8 @@ 0 0 - 446 - 180 + 490 + 163 @@ -103,9 +103,12 @@ Qt::Horizontal + + QSizePolicy::MinimumExpanding + - 0 + 12 20 @@ -162,7 +165,7 @@ 20 - 0 + 20 diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py index 6298baf8..c6767344 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py @@ -15,7 +15,7 @@ class Ui_TimedScreenGrabDialog(object): def setupUi(self, TimedScreenGrabDialog): TimedScreenGrabDialog.setObjectName("TimedScreenGrabDialog") TimedScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) - TimedScreenGrabDialog.resize(446, 180) + TimedScreenGrabDialog.resize(490, 163) TimedScreenGrabDialog.setFocusPolicy(QtCore.Qt.NoFocus) TimedScreenGrabDialog.setModal(True) self.verticalLayout = QtWidgets.QVBoxLayout(TimedScreenGrabDialog) @@ -45,7 +45,7 @@ def setupUi(self, TimedScreenGrabDialog): self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) @@ -62,7 +62,7 @@ def setupUi(self, TimedScreenGrabDialog): spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui index 1025a4c4..ce1ee996 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -162,7 +162,7 @@ 20 - 0 + 20 diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py index 5bf29d5b..833f7a57 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py @@ -62,7 +62,7 @@ def setupUi(self, WindowGrabDialog): spacerItem2 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem2) self.MainVbox.addLayout(self.horizontalLayout_2) - spacerItem3 = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) + spacerItem3 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding) self.MainVbox.addItem(spacerItem3) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 842af9e1..5aa89c98 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 from PyQt5.QtWidgets import ( + QActionGroup, + qApp, QApplication, - QMainWindow, QFileDialog, + QMainWindow, QMessageBox, - QActionGroup, - qApp, QPushButton, QShortcut, ) @@ -35,11 +35,6 @@ def __init__(self, parent=None): self.parent = parent self.screen = qApp self.initialized = False - self.transparent_window_opacity = None - self.selection_color_background = None - self.selection_color_border = None - self.selection_color_opacity = None - self.selection_wight_border = None self.settings = None self.fileName = None @@ -48,7 +43,6 @@ def __init__(self, parent=None): self.scale_factor = None self.snippingWidget = None - self._pixmap = None self.sound = None self.TimedScreenGrabDialog = None @@ -163,7 +157,7 @@ def connectSignalsSlots(self): def onSnippingCompleted(self, img): self.setWindowState(Qt.WindowActive) - + self.show() if img is None: return @@ -179,7 +173,7 @@ def onSnippingCompleted(self, img): self.setWindowModified(True) self.update_actions() - self.show() + # self.show() def snipArea(self): self.setWindowState(Qt.WindowMinimized) @@ -351,7 +345,8 @@ def _preference_pointer_changed(self, value: int) -> None: def _showPreferenceWindow(self): if self.ActionMenuEditPreference.isEnabled(): self.PreferenceWindow = PreferenceWindow( - play_sound=self.preference_enable_sound, pointer=self.preference_pointer + play_sound=self.preference_enable_sound, + pointer=self.preference_pointer ) self.PreferenceWindow.checkbox_enable_sound_changed.connect(self._preference_enable_sound_changed) self.PreferenceWindow.buttongroup_changed.connect(self._preference_pointer_changed) From 81a766fbc00b6825f84b01287b1c024002392aea Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 15:39:50 +0100 Subject: [PATCH 25/36] Add spacing for icon on dialog --- Under Construction/Grab.app/Resources/dialog_screen_grab.ui | 5 ++++- .../Grab.app/Resources/dialog_screen_grab_ui.py | 2 +- .../Grab.app/Resources/dialog_selection_grab.ui | 5 ++++- .../Grab.app/Resources/dialog_selection_grab_ui.py | 2 +- Under Construction/Grab.app/Resources/dialog_window_grab.ui | 5 ++++- .../Grab.app/Resources/dialog_window_grab_ui.py | 2 +- Under Construction/Grab.app/Resources/grab.py | 1 + 7 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui index eb26c076..8b092e96 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -109,9 +109,12 @@ Qt::Horizontal + + QSizePolicy::MinimumExpanding + - 0 + 12 20 diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py index ea40dfaf..2eb90115 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -50,7 +50,7 @@ def setupUi(self, ScreenGrabDialog): self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui index 162a6d1c..21333f24 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -109,9 +109,12 @@ Qt::Horizontal + + QSizePolicy::MinimumExpanding + - 0 + 12 20 diff --git a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py index e2abf5a4..48475e69 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -50,7 +50,7 @@ def setupUi(self, SelectionGrabDialog): self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab.ui b/Under Construction/Grab.app/Resources/dialog_window_grab.ui index ce1ee996..8f7dba66 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab.ui +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -103,9 +103,12 @@ Qt::Horizontal + + QSizePolicy::MinimumExpanding + - 0 + 12 20 diff --git a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py index 833f7a57..8fb4d285 100644 --- a/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py +++ b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py @@ -45,7 +45,7 @@ def setupUi(self, WindowGrabDialog): self.icon.setScaledContents(True) self.icon.setObjectName("icon") self.horizontalLayout_2.addWidget(self.icon, 0, QtCore.Qt.AlignLeft) - spacerItem1 = QtWidgets.QSpacerItem(0, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + spacerItem1 = QtWidgets.QSpacerItem(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.verticalLayout_2 = QtWidgets.QVBoxLayout() self.verticalLayout_2.setSpacing(0) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 5aa89c98..29fd0dc9 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -167,6 +167,7 @@ def onSnippingCompleted(self, img): self.img_preview.setImage(img) self.img_preview.clearZoom() + self.fileName = None self.setWindowTitle("Untitled[*]") From 1799b0899e6de7fa44417728ad9d33dcc97eea78 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 15:56:14 +0100 Subject: [PATCH 26/36] Use super for focusOut event --- Under Construction/Grab.app/Resources/dialog_screen_grab.py | 2 +- Under Construction/Grab.app/Resources/grab.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index a0644ccc..80e6771a 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -46,9 +46,9 @@ def focusOutEvent(self, event: QFocusEvent) -> None: if self.hasFocus() or self.ui.button_cancel.hasFocus(): event.accept() else: + super(ScreenGrabDialog, self).close() event.accept() self.screen_dialog_signal_start.emit() - self.close() def screen_dialog_quit(self): self.ui.button_cancel.setFocus(True) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 29fd0dc9..5958c874 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -157,7 +157,7 @@ def connectSignalsSlots(self): def onSnippingCompleted(self, img): self.setWindowState(Qt.WindowActive) - self.show() + if img is None: return @@ -167,14 +167,13 @@ def onSnippingCompleted(self, img): self.img_preview.setImage(img) self.img_preview.clearZoom() - self.fileName = None self.setWindowTitle("Untitled[*]") self.setWindowModified(True) self.update_actions() - # self.show() + self.show() def snipArea(self): self.setWindowState(Qt.WindowMinimized) From 7381e53da5b3d47b6030db9d16e118127a1d6ccd Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 15:58:30 +0100 Subject: [PATCH 27/36] Use super for focusOut event --- Under Construction/Grab.app/Resources/dialog_screen_grab.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 80e6771a..a6950987 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -25,6 +25,8 @@ def setupCustomUi(self): self.setWindowFlags( Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowStaysOnTopHint ) + self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + self.setWindowModality(Qt.WindowModality.ApplicationModal) self.ui.icon.setPixmap( QPixmap(os.path.join(os.path.dirname(__file__), "Grab.png")).scaled( 48, 48, Qt.KeepAspectRatio, Qt.SmoothTransformation From cbfe4c96988537d2832c75abf282999dc03025b4 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 16:13:50 +0100 Subject: [PATCH 28/36] add timer --- .../Grab.app/Resources/dialog_screen_grab.py | 1 - Under Construction/Grab.app/Resources/grab.py | 10 ++++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index a6950987..7129fa5b 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -53,6 +53,5 @@ def focusOutEvent(self, event: QFocusEvent) -> None: self.screen_dialog_signal_start.emit() def screen_dialog_quit(self): - self.ui.button_cancel.setFocus(True) self.screen_dialog_signal_quit.emit() self.close() diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 5958c874..912dffbb 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -245,9 +245,6 @@ def copy_to_clipboard(self): qi = self.img_preview.pixmap().toImage() QApplication.clipboard().setImage(qi) - def new_timed_screenshot(self): - QTimer.singleShot(self.timer_count, self.snipFull) - def normal_size(self): self.img_preview.clearZoom() @@ -423,8 +420,8 @@ def _SelectionGrabStart(self): def _ScreenGrabStart(self): self._CloseAllDialogs() - self.snipFull() - # self.take_screenshot() + self.setWindowState(Qt.WindowMinimized) + QTimer.singleShot(1000, self.snipFull) def _showTimedScreenGrabDialog(self): if self.ActionMenuCaptureTimedScreen.isEnabled(): @@ -442,7 +439,8 @@ def _showTimedScreenGrabDialog(self): def _TimedScreenGrabStart(self): self._CloseAllDialogs() - self.new_timed_screenshot() + self.setWindowState(Qt.WindowMinimized) + QTimer.singleShot(self.timer_count, self.snipFull) def _CloseAllDialogs(self): if self.TimedScreenGrabDialog and isinstance(self.TimedScreenGrabDialog, TimedScreenGrabDialog): From 0ba936b88886196d6f51adfc4244bbcccdeae1cf Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 16:43:47 +0100 Subject: [PATCH 29/36] rename every preference cursor images --- .../Grab.app/Resources/main_window_ui.py | 2 +- .../{ArrowCursor.png => preference_ArrowCursor.png} | Bin .../{BlankCursor.png => preference_BlankCursor.png} | Bin ...denCursor.png => preference_ForbiddenCursor.png} | Bin .../{IBeamCursor.png => preference_IBeamCursor.png} | Bin ...HandCursor.png => preference_OpenHandCursor.png} | Bin ...Cursor.png => preference_PointingHandCursor.png} | Bin ...ArrowCursor.png => preference_UpArrowCursor.png} | Bin ...hisCursor.png => preference_WhatsThisCursor.png} | Bin .../Grab.app/Resources/preference_window.py | 4 ++-- .../{QtImageViewer.py => widget_QtImageViewer.py} | 0 11 files changed, 3 insertions(+), 3 deletions(-) rename Under Construction/Grab.app/Resources/{ArrowCursor.png => preference_ArrowCursor.png} (100%) rename Under Construction/Grab.app/Resources/{BlankCursor.png => preference_BlankCursor.png} (100%) rename Under Construction/Grab.app/Resources/{ForbiddenCursor.png => preference_ForbiddenCursor.png} (100%) rename Under Construction/Grab.app/Resources/{IBeamCursor.png => preference_IBeamCursor.png} (100%) rename Under Construction/Grab.app/Resources/{OpenHandCursor.png => preference_OpenHandCursor.png} (100%) rename Under Construction/Grab.app/Resources/{PointingHandCursor.png => preference_PointingHandCursor.png} (100%) rename Under Construction/Grab.app/Resources/{UpArrowCursor.png => preference_UpArrowCursor.png} (100%) rename Under Construction/Grab.app/Resources/{WhatsThisCursor.png => preference_WhatsThisCursor.png} (100%) rename Under Construction/Grab.app/Resources/{QtImageViewer.py => widget_QtImageViewer.py} (100%) diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index d8c05a35..4f15fd8b 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -241,4 +241,4 @@ def retranslateUi(self, MainWindow): self.ActionUpdateTimerTo10Secs.setText(_translate("MainWindow", "10 secs")) self.ActionMenuHelpDocumentation.setText(_translate("MainWindow", "Documentation")) self.ActionMenuEditPreference.setText(_translate("MainWindow", "Preferences")) -from QtImageViewer import QtImageViewer +from widget_QtImageViewer import QtImageViewer diff --git a/Under Construction/Grab.app/Resources/ArrowCursor.png b/Under Construction/Grab.app/Resources/preference_ArrowCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/ArrowCursor.png rename to Under Construction/Grab.app/Resources/preference_ArrowCursor.png diff --git a/Under Construction/Grab.app/Resources/BlankCursor.png b/Under Construction/Grab.app/Resources/preference_BlankCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/BlankCursor.png rename to Under Construction/Grab.app/Resources/preference_BlankCursor.png diff --git a/Under Construction/Grab.app/Resources/ForbiddenCursor.png b/Under Construction/Grab.app/Resources/preference_ForbiddenCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/ForbiddenCursor.png rename to Under Construction/Grab.app/Resources/preference_ForbiddenCursor.png diff --git a/Under Construction/Grab.app/Resources/IBeamCursor.png b/Under Construction/Grab.app/Resources/preference_IBeamCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/IBeamCursor.png rename to Under Construction/Grab.app/Resources/preference_IBeamCursor.png diff --git a/Under Construction/Grab.app/Resources/OpenHandCursor.png b/Under Construction/Grab.app/Resources/preference_OpenHandCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/OpenHandCursor.png rename to Under Construction/Grab.app/Resources/preference_OpenHandCursor.png diff --git a/Under Construction/Grab.app/Resources/PointingHandCursor.png b/Under Construction/Grab.app/Resources/preference_PointingHandCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/PointingHandCursor.png rename to Under Construction/Grab.app/Resources/preference_PointingHandCursor.png diff --git a/Under Construction/Grab.app/Resources/UpArrowCursor.png b/Under Construction/Grab.app/Resources/preference_UpArrowCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/UpArrowCursor.png rename to Under Construction/Grab.app/Resources/preference_UpArrowCursor.png diff --git a/Under Construction/Grab.app/Resources/WhatsThisCursor.png b/Under Construction/Grab.app/Resources/preference_WhatsThisCursor.png similarity index 100% rename from Under Construction/Grab.app/Resources/WhatsThisCursor.png rename to Under Construction/Grab.app/Resources/preference_WhatsThisCursor.png diff --git a/Under Construction/Grab.app/Resources/preference_window.py b/Under Construction/Grab.app/Resources/preference_window.py index f0dc2f22..5d3ae425 100644 --- a/Under Construction/Grab.app/Resources/preference_window.py +++ b/Under Construction/Grab.app/Resources/preference_window.py @@ -55,8 +55,8 @@ def select_cursor(self, cursor_id): def setupCustomUI(self): for button in self.ui.buttonGroup.buttons(): - if os.path.exists(os.path.join(os.path.dirname(__file__), f"{button.objectName()}.png")): - button.setIcon(QIcon(os.path.join(os.path.dirname(__file__), f"{button.objectName()}.png"))) + if os.path.exists(os.path.join(os.path.dirname(__file__), f"preference_{button.objectName()}.png")): + button.setIcon(QIcon(os.path.join(os.path.dirname(__file__), f"preference_{button.objectName()}.png"))) def setup(self): diff --git a/Under Construction/Grab.app/Resources/QtImageViewer.py b/Under Construction/Grab.app/Resources/widget_QtImageViewer.py similarity index 100% rename from Under Construction/Grab.app/Resources/QtImageViewer.py rename to Under Construction/Grab.app/Resources/widget_QtImageViewer.py From fe60032e692d2dd5048a2e1dd603fa1434228102 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 16:47:49 +0100 Subject: [PATCH 30/36] Restore name for .ui import usage --- .../Resources/{widget_QtImageViewer.py => QtImageViewer.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Under Construction/Grab.app/Resources/{widget_QtImageViewer.py => QtImageViewer.py} (100%) diff --git a/Under Construction/Grab.app/Resources/widget_QtImageViewer.py b/Under Construction/Grab.app/Resources/QtImageViewer.py similarity index 100% rename from Under Construction/Grab.app/Resources/widget_QtImageViewer.py rename to Under Construction/Grab.app/Resources/QtImageViewer.py From 57a8033f51618f378d33f5a54b89caaaf198d00f Mon Sep 17 00:00:00 2001 From: Tuuux Date: Tue, 20 Feb 2024 16:48:13 +0100 Subject: [PATCH 31/36] Restore name for .ui import usage --- Under Construction/Grab.app/Resources/main_window_ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Under Construction/Grab.app/Resources/main_window_ui.py b/Under Construction/Grab.app/Resources/main_window_ui.py index 4f15fd8b..d8c05a35 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -241,4 +241,4 @@ def retranslateUi(self, MainWindow): self.ActionUpdateTimerTo10Secs.setText(_translate("MainWindow", "10 secs")) self.ActionMenuHelpDocumentation.setText(_translate("MainWindow", "Documentation")) self.ActionMenuEditPreference.setText(_translate("MainWindow", "Preferences")) -from widget_QtImageViewer import QtImageViewer +from QtImageViewer import QtImageViewer From 07b95f52a114f051688df0b35056e2eb7c64501e Mon Sep 17 00:00:00 2001 From: Tuuux Date: Thu, 29 Feb 2024 14:31:05 +0100 Subject: [PATCH 32/36] Better coord text loaction --- .../Grab.app/Resources/dialog_screen_grab.py | 48 ++++++++++++++----- .../Resources/dialog_selection_screen_grab.py | 13 +++-- .../Resources/dialog_timed_screen_grab.py | 11 ++++- Under Construction/Grab.app/Resources/grab.py | 2 +- .../Resources/property_selection_area.py | 20 ++++++-- 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 7129fa5b..587b19d2 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,8 +1,8 @@ import os -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent -from PyQt5.QtWidgets import QDialog, QShortcut +from PyQt5.QtCore import pyqtSignal, Qt, QEvent +from PyQt5.QtGui import QPixmap, QIcon, QFocusEvent +from PyQt5.QtWidgets import QDialog, QDesktopWidget from dialog_screen_grab_ui import Ui_ScreenGrabDialog @@ -41,17 +41,43 @@ def connectSignalsSlots(self): def initialState(self): self.adjustSize() self.setFixedSize(self.size()) - + self.center() self.setFocus() - def focusOutEvent(self, event: QFocusEvent) -> None: - if self.hasFocus() or self.ui.button_cancel.hasFocus(): - event.accept() - else: - super(ScreenGrabDialog, self).close() - event.accept() - self.screen_dialog_signal_start.emit() + def center(self): + qr = self.frameGeometry() + cp = QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + self.move(qr.topLeft()) + + def eventFilter(self, source, event): + if event.type() == QEvent.Close and source is self: + self.close() + + elif event.type() == QEvent.FocusOut: + print('eventFilter: focus out') + if self.hasFocus() or self.ui.button_cancel.hasFocus(): + event.accept() + + else: + super(ScreenGrabDialog, self).close() + event.accept() + self.screen_dialog_signal_start.emit() + + + return super(ScreenGrabDialog, self).eventFilter(source, event) + + # def focusOutEvent(self, event: QFocusEvent) -> None: + # if self.hasFocus() or self.ui.button_cancel.hasFocus(): + # event.accept() + # else: + # super(ScreenGrabDialog, self).close() + # event.accept() + # self.screen_dialog_signal_start.emit() def screen_dialog_quit(self): self.screen_dialog_signal_quit.emit() self.close() + + def screen_dialog_cancel(self): + self.close() diff --git a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py index 10187764..677c9437 100644 --- a/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -1,8 +1,8 @@ import os from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFocusEvent -from PyQt5.QtWidgets import QDialog, QShortcut +from PyQt5.QtGui import QPixmap, QIcon, QFocusEvent +from PyQt5.QtWidgets import QDialog, QDesktopWidget from dialog_selection_grab_ui import Ui_SelectionGrabDialog @@ -35,15 +35,20 @@ def setupCustomUi(self): def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.selection_dialog_quit) - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.selection_dialog_quit) def initialState(self): self.adjustSize() self.setFixedSize(self.size()) + self.center() self.setFocus() + def center(self): + qr = self.frameGeometry() + cp = QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + self.move(qr.topLeft()) + def closeEvent(self, event): super(SelectionGrabDialog, self).closeEvent(event) event.accept() diff --git a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py index 005b5014..da258fde 100644 --- a/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -1,8 +1,8 @@ import os from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtGui import QPixmap, QIcon, QKeySequence -from PyQt5.QtWidgets import QDialog, QShortcut +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import QDialog, QDesktopWidget from dialog_timed_screen_grab_ui import Ui_TimedScreenGrabDialog @@ -46,9 +46,16 @@ def connectSignalsSlots(self): def initialState(self): self.adjustSize() self.setFixedSize(self.size()) + self.center() self.setFocus() + def center(self): + qr = self.frameGeometry() + cp = QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + self.move(qr.topLeft()) + def timed_dialog_quit(self): self.timer_dialog_signal_quit.emit() diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index 912dffbb..c45fc7ed 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -383,7 +383,7 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog.installEventFilter(self) quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self._CloseAllDialogs) + quitShortcut1.activated.connect(self.ScreenGrabDialog.screen_dialog_cancel) self.ScreenGrabDialog.show() diff --git a/Under Construction/Grab.app/Resources/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py index fe62270e..d0f049dc 100644 --- a/Under Construction/Grab.app/Resources/property_selection_area.py +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -19,8 +19,15 @@ def __init__(self): self.coordinate_pen_color = QColor(0, 0, 0, 255) self.coordinate_spacing = 5 - self.coordinate_font = QFont() - self.coordinate_font_metrics = QFontMetrics(QFont()) + + + + self.coordinate_font = QFont('Consolas', 11, QFont.Light) + # font metrics. assume font is monospaced + self.coordinate_font.setKerning(False) + self.coordinate_font.setFixedPitch(True) + + self.coordinate_font_metrics = QFontMetrics(self.coordinate_font) self.coordinate_pen = QPen( self.coordinate_pen_color, self.coordinate_pen_width, @@ -92,7 +99,7 @@ def coordinate_text_x(self) -> float: @property def coordinate_text_y(self) -> float: - return self.y + self.height - self.coordinate_spacing + self.coordinate_font_metrics.height() + return self.y + self.height + self.coordinate_font_metrics.height() @property def coordinate_rect_x(self) -> float: @@ -104,11 +111,11 @@ def coordinate_rect_y(self) -> float: @property def coordinate_rect_width(self) -> float: - return self.coordinate_text_width + (self.coordinate_spacing * 2) + return self.coordinate_text_width @property def coordinate_rect_height(self) -> float: - return self.coordinate_font_metrics.height() - (self.selection_pen_width * 2) + return self.coordinate_font_metrics.height() + (self.coordinate_spacing / 2) @property def SelectionColorBackground(self) -> QColor: @@ -145,3 +152,6 @@ def setFromQRectF(self, req: QRectF) -> None: self.y = req.y() self.width = req.width() self.height = req.height() + + def QRectF(self) -> QRectF: + return QRectF(self.x, self.y, self.width, self.height) From 665a1a60f15b0509776637715c93d87a273abfe4 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Thu, 29 Feb 2024 14:44:13 +0100 Subject: [PATCH 33/36] Use application default font --- .../Grab.app/Resources/property_selection_area.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Under Construction/Grab.app/Resources/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py index d0f049dc..b825999a 100644 --- a/Under Construction/Grab.app/Resources/property_selection_area.py +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -1,5 +1,6 @@ from PyQt5.QtCore import Qt, QRectF from PyQt5.QtGui import QFont, QFontMetrics, QColor, QPen, QPalette +from PyQt5.QtWidgets import qApp class SelectionArea(object): @@ -20,9 +21,7 @@ def __init__(self): self.coordinate_spacing = 5 - - - self.coordinate_font = QFont('Consolas', 11, QFont.Light) + self.coordinate_font = qApp.font() # font metrics. assume font is monospaced self.coordinate_font.setKerning(False) self.coordinate_font.setFixedPitch(True) @@ -111,7 +110,7 @@ def coordinate_rect_y(self) -> float: @property def coordinate_rect_width(self) -> float: - return self.coordinate_text_width + return self.coordinate_text_width + (self.coordinate_spacing * 2) @property def coordinate_rect_height(self) -> float: From 232bf20c4c50a68e119cb60ba7a40abdefaef830 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Thu, 29 Feb 2024 14:59:14 +0100 Subject: [PATCH 34/36] Fixe grab cursor --- Under Construction/Grab.app/Resources/widget_snipping_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index 19aa9301..9be6e821 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -92,7 +92,7 @@ def fullscreen(self): if img and self.cursor != Qt.BlankCursor: pm = QPixmap(self.width(), self.height()) - cursor_pixmap = QPixmap(os.path.join(os.path.dirname(__file__), f"{self.cursor_name[self.cursor]}.png")) + cursor_pixmap = QPixmap(os.path.join(os.path.dirname(__file__), f"preference_{self.cursor_name[self.cursor]}.png")) painter = QPainter(pm) painter.drawPixmap(0, 0, self.width(), self.height(), img) painter.drawPixmap( From f08c272c328bf762264e4485588ce94eb695ee15 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Thu, 6 Jun 2024 10:30:45 +0200 Subject: [PATCH 35/36] Fixe QRect BreakOut --- .../Grab.app/Resources/dialog_screen_grab.py | 57 ++++++++++--------- Under Construction/Grab.app/Resources/grab.py | 13 ++--- .../Resources/property_selection_area.py | 52 ++++++++++------- .../Resources/widget_snipping_tool.py | 6 +- 4 files changed, 72 insertions(+), 56 deletions(-) diff --git a/Under Construction/Grab.app/Resources/dialog_screen_grab.py b/Under Construction/Grab.app/Resources/dialog_screen_grab.py index 587b19d2..eb7514be 100644 --- a/Under Construction/Grab.app/Resources/dialog_screen_grab.py +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -1,8 +1,8 @@ import os from PyQt5.QtCore import pyqtSignal, Qt, QEvent -from PyQt5.QtGui import QPixmap, QIcon, QFocusEvent -from PyQt5.QtWidgets import QDialog, QDesktopWidget +from PyQt5.QtGui import QPixmap, QIcon, QFocusEvent, QKeySequence +from PyQt5.QtWidgets import QDialog, QDesktopWidget, QShortcut from dialog_screen_grab_ui import Ui_ScreenGrabDialog @@ -37,6 +37,11 @@ def setupCustomUi(self): def connectSignalsSlots(self): self.ui.button_cancel.clicked.connect(self.screen_dialog_quit) + # QShortcut(QKeySequence("Escape"), self) + quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + quitShortcut1.activated.connect(self.screen_dialog_quit) + + # QShortcut(QKeySequence("Ctrl+C"), activated=self.on_Ctrl_C) def initialState(self): self.adjustSize() @@ -50,30 +55,30 @@ def center(self): qr.moveCenter(cp) self.move(qr.topLeft()) - def eventFilter(self, source, event): - if event.type() == QEvent.Close and source is self: - self.close() - - elif event.type() == QEvent.FocusOut: - print('eventFilter: focus out') - if self.hasFocus() or self.ui.button_cancel.hasFocus(): - event.accept() - - else: - super(ScreenGrabDialog, self).close() - event.accept() - self.screen_dialog_signal_start.emit() - - - return super(ScreenGrabDialog, self).eventFilter(source, event) - - # def focusOutEvent(self, event: QFocusEvent) -> None: - # if self.hasFocus() or self.ui.button_cancel.hasFocus(): - # event.accept() - # else: - # super(ScreenGrabDialog, self).close() - # event.accept() - # self.screen_dialog_signal_start.emit() + # def eventFilter(self, source, event): + # if event.type() == QEvent.Close and source is self: + # self.close() + # + # elif event.type() == QEvent.FocusOut: + # print('eventFilter: focus out') + # if self.hasFocus() or self.ui.button_cancel.hasFocus(): + # event.accept() + # + # else: + # super(ScreenGrabDialog, self).close() + # event.accept() + # self.screen_dialog_signal_start.emit() + # + # + # return super(ScreenGrabDialog, self).eventFilter(source, event) + + def focusOutEvent(self, event: QFocusEvent) -> None: + if self.hasFocus() or self.ui.button_cancel.hasFocus(): + event.accept() + else: + super(ScreenGrabDialog, self).close() + event.accept() + self.screen_dialog_signal_start.emit() def screen_dialog_quit(self): self.screen_dialog_signal_quit.emit() diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index c45fc7ed..fb923328 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -181,7 +181,7 @@ def snipArea(self): def snipFull(self): self.setWindowState(Qt.WindowMinimized) - self.snippingWidget.fullscreen() + self.snippingWidget.full_screen() def write_settings(self): self.settings.setValue("geometry", self.saveGeometry()) @@ -382,8 +382,7 @@ def _showScreenGrabDialog(self): self.ScreenGrabDialog.screen_dialog_signal_start.connect(self._ScreenGrabStart) self.ScreenGrabDialog.installEventFilter(self) - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self.ScreenGrabDialog.screen_dialog_cancel) + self.ScreenGrabDialog.show() @@ -398,8 +397,8 @@ def _showSelectionGrabDialog(self): self.SelectionGrabDialog.selection_dialog_signal_quit.connect(self._CloseAllDialogs) self.SelectionGrabDialog.selection_dialog_signal_start.connect(self._SelectionGrabStart) - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self._CloseAllDialogs) + # quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + # quitShortcut1.activated.connect(self._CloseAllDialogs) self.SelectionGrabDialog.exec_() self.show() @@ -431,8 +430,8 @@ def _showTimedScreenGrabDialog(self): self.TimedScreenGrabDialog.timer_dialog_signal_start.connect(self._TimedScreenGrabStart) self.TimedScreenGrabDialog.timer_dialog_signal_quit.connect(self._CloseAllDialogs) - quitShortcut1 = QShortcut(QKeySequence("Escape"), self) - quitShortcut1.activated.connect(self._CloseAllDialogs) + # quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + # quitShortcut1.activated.connect(self._CloseAllDialogs) self.TimedScreenGrabDialog.exec_() self.show() diff --git a/Under Construction/Grab.app/Resources/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py index b825999a..0b42258f 100644 --- a/Under Construction/Grab.app/Resources/property_selection_area.py +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -48,8 +48,11 @@ def is_snipping(self, value: bool): # Position of the selection @property - def x(self) -> float: - return self.__x + def x(self) -> int or None: + try: + return int(self.__x) + except TypeError: + return None @x.setter def x(self, x: float): @@ -57,8 +60,11 @@ def x(self, x: float): self.__x = x @property - def y(self) -> float: - return self.__y + def y(self) -> int or None: + try: + return int(self.__y) + except TypeError: + return None @y.setter def y(self, y: float): @@ -66,8 +72,11 @@ def y(self, y: float): self.__y = y @property - def width(self) -> float: - return self.__width + def width(self) -> int or None: + try: + return int(self.__width) + except TypeError: + return None @width.setter def width(self, width: float): @@ -75,8 +84,11 @@ def width(self, width: float): self.__width = width @property - def height(self) -> float: - return self.__height + def height(self) -> int or None: + try: + return int(self.__height) + except TypeError: + return None @height.setter def height(self, height: float): @@ -93,28 +105,28 @@ def coordinate_text_width(self) -> int: return self.coordinate_font_metrics.width(self.coordinate_text) @property - def coordinate_text_x(self) -> float: - return self.x + self.width - self.coordinate_text_width - self.coordinate_spacing + def coordinate_text_x(self) -> int: + return int(self.x + self.width - self.coordinate_text_width - self.coordinate_spacing) @property - def coordinate_text_y(self) -> float: - return self.y + self.height + self.coordinate_font_metrics.height() + def coordinate_text_y(self) -> int: + return int(self.y + self.height + self.coordinate_font_metrics.height()) @property - def coordinate_rect_x(self) -> float: - return self.x + self.width - self.coordinate_text_width - (self.coordinate_spacing * 2) + def coordinate_rect_x(self) -> int: + return int(self.x + self.width - self.coordinate_text_width - (self.coordinate_spacing * 2)) @property - def coordinate_rect_y(self) -> float: - return self.y + self.height + (self.coordinate_spacing / 2) + def coordinate_rect_y(self) -> int: + return int(self.y + self.height + (self.coordinate_spacing / 2)) @property - def coordinate_rect_width(self) -> float: - return self.coordinate_text_width + (self.coordinate_spacing * 2) + def coordinate_rect_width(self) -> int: + return int(self.coordinate_text_width + (self.coordinate_spacing * 2)) @property - def coordinate_rect_height(self) -> float: - return self.coordinate_font_metrics.height() + (self.coordinate_spacing / 2) + def coordinate_rect_height(self) -> int: + return int(self.coordinate_font_metrics.height() + (self.coordinate_spacing / 2)) @property def SelectionColorBackground(self) -> QColor: diff --git a/Under Construction/Grab.app/Resources/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py index 9be6e821..2663d6d2 100644 --- a/Under Construction/Grab.app/Resources/widget_snipping_tool.py +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -79,7 +79,7 @@ def UpdateScreen(self): if self.windowHandle(): self.screen = self.windowHandle().screen() - def fullscreen(self): + def full_screen(self): self.UpdateScreen() self.show() @@ -96,8 +96,8 @@ def fullscreen(self): painter = QPainter(pm) painter.drawPixmap(0, 0, self.width(), self.height(), img) painter.drawPixmap( - QCursor().pos().x() - (cursor_pixmap.width() / 2), - QCursor().pos().y() - (cursor_pixmap.height() / 2), + QCursor().pos().x() - int(cursor_pixmap.width() / 2), + QCursor().pos().y() - int(cursor_pixmap.height() / 2), cursor_pixmap.width(), cursor_pixmap.height(), cursor_pixmap, From 69ab0c0f1a4ec767426980ef11ceaf23d1dab7d9 Mon Sep 17 00:00:00 2001 From: Tuuux Date: Thu, 6 Jun 2024 11:43:45 +0200 Subject: [PATCH 36/36] Force a bit the ImageViewer to get back the focus --- Under Construction/Grab.app/Resources/grab.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index fb923328..ca0e3b25 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -156,7 +156,6 @@ def connectSignalsSlots(self): self.snippingWidget.snipping_completed.connect(self.onSnippingCompleted) def onSnippingCompleted(self, img): - self.setWindowState(Qt.WindowActive) if img is None: return @@ -174,6 +173,9 @@ def onSnippingCompleted(self, img): self.update_actions() self.show() + self.setWindowState(Qt.WindowActive) + self.img_preview.setFocus() + self.img_preview.update() def snipArea(self): self.setWindowState(Qt.WindowMinimized) @@ -420,7 +422,7 @@ def _SelectionGrabStart(self): def _ScreenGrabStart(self): self._CloseAllDialogs() self.setWindowState(Qt.WindowMinimized) - QTimer.singleShot(1000, self.snipFull) + QTimer.singleShot(100, self.snipFull) def _showTimedScreenGrabDialog(self): if self.ActionMenuCaptureTimedScreen.isEnabled():