diff --git a/Under Construction/Grab.app/Resources/LICENSE b/Under Construction/Grab.app/Resources/LICENSE index 0e77b64a4..47bff5f11 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/QtImageViewer.py b/Under Construction/Grab.app/Resources/QtImageViewer.py new file mode 100644 index 000000000..a4ca8e555 --- /dev/null +++ b/Under Construction/Grab.app/Resources/QtImageViewer.py @@ -0,0 +1,619 @@ +""" 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, 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, 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: + 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) + + self.setBackgroundBrush(Qt.darkGray) + + + 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/README.md b/Under Construction/Grab.app/Resources/README.md new file mode 100644 index 000000000..ca7284629 --- /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 ``(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 ``(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`` 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 ``(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. 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 000000000..30249afac --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_help.py @@ -0,0 +1,31 @@ +import os + +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QDialog +from PyQt5.QtCore import pyqtSignal +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 000000000..74182eb02 --- /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 000000000..2b29138b6 --- /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 new file mode 100644 index 000000000..eb7514bed --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.py @@ -0,0 +1,88 @@ +import os + +from PyQt5.QtCore import pyqtSignal, Qt, QEvent +from PyQt5.QtGui import QPixmap, QIcon, QFocusEvent, QKeySequence +from PyQt5.QtWidgets import QDialog, QDesktopWidget, QShortcut + +from dialog_screen_grab_ui import Ui_ScreenGrabDialog + + +class ScreenGrabDialog(QDialog): + screen_dialog_signal_quit = pyqtSignal() + screen_dialog_signal_start = pyqtSignal() + + def __init__(self, parent=None): + super(ScreenGrabDialog, self).__init__(parent) + + self.ui = Ui_ScreenGrabDialog() + self.ui.setupUi(self) + + self.setupCustomUi() + self.connectSignalsSlots() + self.initialState() + + 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 + ) + ) + + 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) + # 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() + 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 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_screen_grab.ui b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui new file mode 100644 index 000000000..8b092e962 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab.ui @@ -0,0 +1,221 @@ + + + ScreenGrabDialog + + + Qt::WindowModal + + + + 0 + 0 + 486 + 203 + + + + + 0 + 0 + + + + Qt::NoFocus + + + Screen Grab + + + true + + + + + + 12 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 12 + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../../../../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 12 + 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 + + + + 0 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 20 + + + + + + + + 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 new file mode 100644 index 000000000..2eb901154 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_screen_grab_ui.py @@ -0,0 +1,92 @@ +# -*- 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_ScreenGrabDialog(object): + def setupUi(self, ScreenGrabDialog): + ScreenGrabDialog.setObjectName("ScreenGrabDialog") + ScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) + 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) + self.verticalLayout.setObjectName("verticalLayout") + self.MainVbox = QtWidgets.QVBoxLayout() + self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + 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) + self.icon = QtWidgets.QLabel(ScreenGrabDialog) + 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(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, 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(ScreenGrabDialog) + 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(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) + 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(0, 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) + self.verticalLayout.addLayout(self.MainVbox) + + self.retranslateUi(ScreenGrabDialog) + QtCore.QMetaObject.connectSlotsByName(ScreenGrabDialog) + + def retranslateUi(self, ScreenGrabDialog): + _translate = QtCore.QCoreApplication.translate + 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 new file mode 100644 index 000000000..21333f242 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab.ui @@ -0,0 +1,232 @@ + + + SelectionGrabDialog + + + Qt::WindowModal + + + + 0 + 0 + 486 + 178 + + + + + 0 + 0 + + + + Qt::NoFocus + + + Selection Grab + + + true + + + + + + 12 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 12 + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../../../../../../../../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 12 + 20 + + + + + + + + 0 + + + + + + Nimbus Sans + + + + 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 + + + true + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + 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 new file mode 100644 index 000000000..48475e694 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_selection_grab_ui.py @@ -0,0 +1,101 @@ +# -*- 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.WindowModal) + SelectionGrabDialog.resize(486, 178) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + 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) + self.verticalLayout.setObjectName("verticalLayout") + self.MainVbox = QtWidgets.QVBoxLayout() + self.MainVbox.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + 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) + 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(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, 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(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) + 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(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) + 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.setDefault(True) + self.button_cancel.setFlat(False) + 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", "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 new file mode 100644 index 000000000..677c94373 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_selection_screen_grab.py @@ -0,0 +1,67 @@ +import os + +from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtGui import QPixmap, QIcon, QFocusEvent +from PyQt5.QtWidgets import QDialog, QDesktopWidget + +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.ui = Ui_SelectionGrabDialog() + self.ui.setupUi(self) + + self.setupCustomUi() + self.connectSignalsSlots() + 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 + ) + ) + + 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) + + 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() + + 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() + + 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 new file mode 100644 index 000000000..da258fde6 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.py @@ -0,0 +1,63 @@ +import os + +from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import QDialog, QDesktopWidget + +from dialog_timed_screen_grab_ui import Ui_TimedScreenGrabDialog + + +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.sec = int(timer / 1000) + self.ui = Ui_TimedScreenGrabDialog() + self.ui.setupUi(self) + + 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.setupCustomUi() + self.connectSignalsSlots() + 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 + ) + ) + + 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) + + 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() + + 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 new file mode 100644 index 000000000..aed17d907 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab.ui @@ -0,0 +1,241 @@ + + + TimedScreenGrabDialog + + + Qt::WindowModal + + + + 0 + 0 + 490 + 163 + + + + Qt::NoFocus + + + Timed Screen Grab + + + true + + + + + + 12 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 12 + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../../../../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 12 + 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 + + + + 0 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 0 + 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 000000000..c67673449 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_timed_screen_grab_ui.py @@ -0,0 +1,107 @@ +# -*- 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_TimedScreenGrabDialog(object): + def setupUi(self, TimedScreenGrabDialog): + TimedScreenGrabDialog.setObjectName("TimedScreenGrabDialog") + TimedScreenGrabDialog.setWindowModality(QtCore.Qt.WindowModal) + TimedScreenGrabDialog.resize(490, 163) + 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) + self.MainVbox.setContentsMargins(0, 0, 0, 0) + 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) + self.icon = QtWidgets.QLabel(TimedScreenGrabDialog) + 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(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, 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(TimedScreenGrabDialog) + 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(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) + 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(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) + 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(TimedScreenGrabDialog) + 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(TimedScreenGrabDialog) + QtCore.QMetaObject.connectSlotsByName(TimedScreenGrabDialog) + + def retranslateUi(self, TimedScreenGrabDialog): + _translate = QtCore.QCoreApplication.translate + 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 new file mode 100644 index 000000000..8f7dba662 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_window_grab.ui @@ -0,0 +1,230 @@ + + + WindowGrabDialog + + + Qt::WindowModal + + + + 0 + 0 + 493 + 188 + + + + Qt::NoFocus + + + Window Grab + + + true + + + + + + 12 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 12 + + + QLayout::SetDefaultConstraint + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + ../../../../../../../../../../Processes.app/Resources/Processes.png + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 12 + 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 + + + + 0 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 20 + + + + + + + + 22 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 0 + 0 + + + + + Nimbus Sans + + + + Cancel + + + + + + + Choose Window + + + true + + + + + + + + + + + + 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 000000000..8fb4d2852 --- /dev/null +++ b/Under Construction/Grab.app/Resources/dialog_window_grab_ui.py @@ -0,0 +1,99 @@ +# -*- 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.WindowModal) + WindowGrabDialog.resize(493, 188) + 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(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) + 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(12, 20, QtWidgets.QSizePolicy.MinimumExpanding, 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(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) + 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(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) + 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.setDefault(True) + 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-x11.py b/Under Construction/Grab.app/Resources/grab-x11.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/Under Construction/Grab.app/Resources/grab.py b/Under Construction/Grab.app/Resources/grab.py index edb111535..ca0e3b257 100755 --- a/Under Construction/Grab.app/Resources/grab.py +++ b/Under Construction/Grab.app/Resources/grab.py @@ -1,57 +1,217 @@ #!/usr/bin/env python3 -from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QGridLayout, QFileDialog, QMessageBox -from PyQt5.QtGui import QPixmap, QIcon, QPainter -from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtWidgets import ( + QActionGroup, + qApp, + QApplication, + QFileDialog, + QMainWindow, + QMessageBox, + 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 import sys import os # The Main Window 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 dialog_selection_screen_grab import SelectionGrabDialog +from widget_snipping_tool import SnippingWidget +from preference_window import PreferenceWindow + +QLoggingCategory.setFilterRules("*.debug=false\nqt.qpa.*=false") class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(Window, self).__init__() + self.parent = parent + self.screen = qApp + self.initialized = False + + self.settings = None self.fileName = None self.printerObj = None self.timer_count = None + self.scale_factor = None - self.setupUi(self) + self.snippingWidget = None + self.sound = None + + self.TimedScreenGrabDialog = None + self.ScreenGrabDialog = None + self.SelectionGrabDialog = None + self.PreferenceWindow = None + self.preference_pointer = None + self.preference_enable_sound = None + self.setupUi(self) self.setupCustomUi() + self.setupCustomUiGroups() self.connectSignalsSlots() + self.initialized = False + 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("Untitled[*]") + self.resize(370, 270) - self.take_screenshot() + self.settings = QSettings("helloSystem", "Grab.app") + self.read_settings() + + self.snipFull() + self.initialized = True 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"))) + self.snippingWidget = SnippingWidget() + + 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) + 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) + self.ActionMenuEditPreference.triggered.connect(self._showPreferenceWindow) + + # 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._showScreenGrabDialog) + self.ActionMenuCaptureSelection.triggered.connect(self._showSelectionGrabDialog) + 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) + self.ActionMenuHelpDocumentation.triggered.connect(self._showHelpDialog) + + # Snipping widget + self.snippingWidget.snipping_completed.connect(self.onSnippingCompleted) + + def onSnippingCompleted(self, img): + + if img is None: + return + + if self.preference_enable_sound and self.initialized: + self.sound.play() + + self.img_preview.setImage(img) + self.img_preview.clearZoom() + + self.fileName = None + self.setWindowTitle("Untitled[*]") + + self.setWindowModified(True) + + self.update_actions() + self.show() + self.setWindowState(Qt.WindowActive) + self.img_preview.setFocus() + self.img_preview.update() + + def snipArea(self): + self.setWindowState(Qt.WindowMinimized) + self.snippingWidget.start() + + def snipFull(self): + self.setWindowState(Qt.WindowMinimized) + self.snippingWidget.full_screen() + + 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) - def closeEvent(self, evnt): - super(Window, self).closeEvent(evnt) + 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() + 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(): @@ -63,12 +223,10 @@ 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): - # if not self.isWindowModified(): - # return options = QFileDialog.Options() # options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName( @@ -80,7 +238,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): @@ -89,25 +247,46 @@ def copy_to_clipboard(self): qi = self.img_preview.pixmap().toImage() QApplication.clipboard().setImage(qi) - def new_screenshot(self): - self.hide() - QTimer.singleShot(1000, self.take_screenshot) + def normal_size(self): + self.img_preview.clearZoom() - def new_timed_screenshot(self): - self.hide() - QTimer.singleShot(self.timer_count, self.take_screenshot) + 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() - def take_screenshot(self): - self.img_preview.setPixmap(QApplication.primaryScreen().grabWindow(0)) + # 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.show() - if self.fileName: - self.setWindowTitle("Grab - %s[*]" % (os.path.basename(self.fileName))) + self.ActionMenuViewActualSize.setEnabled(False) + self.ActionMenuViewZoomToFit.setEnabled(False) + self.ActionMenuViewZoomIn.setEnabled(False) + self.ActionMenuViewZoomOut.setEnabled(False) + self.ActionMenuViewZoomToSelection.setEnabled(False) else: - self.setWindowTitle("Grab - new document[*]") - self.setWindowModified(True) + 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) - # defining the method to print the image def print_image(self): # creating an object of the QPrintDialog class print_dialog = QPrintDialog(self.printerObj, self) @@ -153,6 +332,25 @@ def drawImage(printer): dlg.paintRequested.connect(drawImage) dlg.exec_() + 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 + 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 + ) + 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() @@ -162,22 +360,157 @@ 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() + def _showScreenGrabDialog(self): + if self.ActionMenuCaptureScreen.isEnabled(): + self.hide() -if __name__ == "__main__": + 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.ScreenGrabDialog.show() + + while not self.windowHandle(): + QApplication.processEvents() + + def _showSelectionGrabDialog(self): + if self.ActionMenuCaptureSelection.isEnabled(): + self.hide() + + 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.show() + + 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 _SelectionGrabStart(self): + self._CloseAllDialogs() + self.snipArea() + + def _ScreenGrabStart(self): + self._CloseAllDialogs() + self.setWindowState(Qt.WindowMinimized) + QTimer.singleShot(100, self.snipFull) + + def _showTimedScreenGrabDialog(self): + if self.ActionMenuCaptureTimedScreen.isEnabled(): + self.hide() + 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) + + # quitShortcut1 = QShortcut(QKeySequence("Escape"), self) + # quitShortcut1.activated.connect(self._CloseAllDialogs) + + self.TimedScreenGrabDialog.exec_() + self.show() + + def _TimedScreenGrabStart(self): + self._CloseAllDialogs() + self.setWindowState(Qt.WindowMinimized) + QTimer.singleShot(self.timer_count, self.snipFull) + + def _CloseAllDialogs(self): + if self.TimedScreenGrabDialog and isinstance(self.TimedScreenGrabDialog, TimedScreenGrabDialog): + self.TimedScreenGrabDialog.close() + self.TimedScreenGrabDialog = None + if self.ScreenGrabDialog and isinstance(self.ScreenGrabDialog, ScreenGrabDialog): + self.ScreenGrabDialog.close() + self.ScreenGrabDialog = None + 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() + + while not self.windowHandle(): + QApplication.processEvents() + + QApplication.flush() + + def _timer_change_for(self, value): + self.timer_count = value + + 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 + + def _showHelpDialog(self): + if self.ActionMenuHelpAbout.isEnabled(): + self.HelpDialog = HelpDialog() + self.HelpDialog.show() + + +def main(): app = QApplication(sys.argv) - win = Window() - win.show() - sys.exit(app.exec()) + app.setApplicationName("Grab") + app.setApplicationDisplayName("Grab") + app.setApplicationVersion("0.1") + 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 d602a2cd3..b2137a10b 100644 --- a/Under Construction/Grab.app/Resources/main_window.ui +++ b/Under Construction/Grab.app/Resources/main_window.ui @@ -6,18 +6,30 @@ 0 0 - 353 + 601 294 + + true + Grab + + border: 0px; + + + + 0 + + QLayout::SetDefaultConstraint + 0 @@ -31,12 +43,9 @@ 0 - - - - 0 - 0 - + + + background-color: rgba(255,255,255, 3%) @@ -47,7 +56,7 @@ 0 0 - 353 + 601 28 @@ -67,25 +76,57 @@ Edit + + Capture + + + Timer + + + + + + + + + + + + + + Help + + + + Window + + + + + + + + + @@ -102,6 +143,9 @@ + + false + Save @@ -110,6 +154,9 @@ + + false + Save As @@ -118,6 +165,13 @@ + + false + + + + ../../../../../../.designer/backup../../../../../../.designer/backup + Print @@ -126,6 +180,9 @@ + + false + Print Setup @@ -143,7 +200,7 @@ - false + true Selection @@ -182,12 +239,200 @@ 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 + + + + + Documentation + + + + + Preferences + + - 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 1f6207ce6..d8c05a357 100644 --- a/Under Construction/Grab.app/Resources/main_window_ui.py +++ b/Under Construction/Grab.app/Resources/main_window_ui.py @@ -14,24 +14,24 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(353, 294) + MainWindow.resize(601, 294) + MainWindow.setAcceptDrops(True) + MainWindow.setStyleSheet("border: 0px;") + MainWindow.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)) self.centralwidget = QtWidgets.QWidget(MainWindow) 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") - 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,25 +39,35 @@ 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") 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) @@ -67,6 +77,73 @@ 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.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) @@ -74,14 +151,36 @@ def setupUi(self, MainWindow): self.menuFile.addAction(self.ActionMenuFilePrint) self.menuFile.addAction(self.ActionMenuFilePrintSetup) self.menuEdit.addAction(self.ActionMenuEditCopy) + self.menuEdit.addSeparator() + self.menuEdit.addAction(self.ActionMenuEditPreference) + 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.ActionMenuHelpDocumentation) 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.menuCapture.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(MainWindow) @@ -93,7 +192,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", "Window")) self.ActionMenuEditCopy.setText(_translate("MainWindow", "Copy")) self.ActionMenuEditCopy.setShortcut(_translate("MainWindow", "Ctrl+C")) self.ActionMenuHelpAbout.setText(_translate("MainWindow", "About")) @@ -115,4 +216,29 @@ 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")) + 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_ArrowCursor.png b/Under Construction/Grab.app/Resources/preference_ArrowCursor.png new file mode 100644 index 000000000..a69ef4eb6 Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_ArrowCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_BlankCursor.png b/Under Construction/Grab.app/Resources/preference_BlankCursor.png new file mode 100644 index 000000000..0a8f9a883 Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_BlankCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_ForbiddenCursor.png b/Under Construction/Grab.app/Resources/preference_ForbiddenCursor.png new file mode 100644 index 000000000..2b08c4e2a Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_ForbiddenCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_IBeamCursor.png b/Under Construction/Grab.app/Resources/preference_IBeamCursor.png new file mode 100644 index 000000000..097fc5fa7 Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_IBeamCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_OpenHandCursor.png b/Under Construction/Grab.app/Resources/preference_OpenHandCursor.png new file mode 100644 index 000000000..9181c859e Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_OpenHandCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_PointingHandCursor.png b/Under Construction/Grab.app/Resources/preference_PointingHandCursor.png new file mode 100644 index 000000000..d2004aefa Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_PointingHandCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_UpArrowCursor.png b/Under Construction/Grab.app/Resources/preference_UpArrowCursor.png new file mode 100644 index 000000000..d3e70ef4c Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_UpArrowCursor.png differ diff --git a/Under Construction/Grab.app/Resources/preference_WhatsThisCursor.png b/Under Construction/Grab.app/Resources/preference_WhatsThisCursor.png new file mode 100644 index 000000000..b47601c37 Binary files /dev/null and b/Under Construction/Grab.app/Resources/preference_WhatsThisCursor.png differ 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 000000000..5d3ae4253 --- /dev/null +++ b/Under Construction/Grab.app/Resources/preference_window.py @@ -0,0 +1,91 @@ +import os +import sys + +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"preference_{button.objectName()}.png")): + button.setIcon(QIcon(os.path.join(os.path.dirname(__file__), f"preference_{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()) + 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 000000000..4eeb434e5 --- /dev/null +++ b/Under Construction/Grab.app/Resources/preference_window.ui @@ -0,0 +1,208 @@ + + + PreferenceWindow + + + + 0 + 0 + 220 + 187 + + + + Preferences + + + + ../../../../../../.designer/backup/Grab.png../../../../../../.designer/backup/Grab.png + + + + + + Pointer Type + + + + + + No Cursor + + + ... + + + + 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 000000000..3caecb4fc --- /dev/null +++ b/Under Construction/Grab.app/Resources/preference_window_ui.py @@ -0,0 +1,101 @@ +# -*- 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("./../../../../../../.designer/backup/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.setToolTip(_translate("PreferenceWindow", "No Cursor")) + 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/property_selection_area.py b/Under Construction/Grab.app/Resources/property_selection_area.py new file mode 100644 index 000000000..0b42258f9 --- /dev/null +++ b/Under Construction/Grab.app/Resources/property_selection_area.py @@ -0,0 +1,168 @@ +from PyQt5.QtCore import Qt, QRectF +from PyQt5.QtGui import QFont, QFontMetrics, QColor, QPen, QPalette +from PyQt5.QtWidgets import qApp + + +class SelectionArea(object): + + def __init__(self): + self.__x = None + 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 = qApp.font() + # 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, + Qt.SolidLine, + Qt.RoundCap, + Qt.RoundJoin + ) + self.selection_pen_width = 2 + + # Status + @property + def is_snipping(self) -> bool: + return self.__is_snipping + + @is_snipping.setter + def is_snipping(self, value: bool): + if value != self.is_snipping: + self.__is_snipping = value + + # Position of the selection + @property + def x(self) -> int or None: + try: + return int(self.__x) + except TypeError: + return None + + @x.setter + def x(self, x: float): + if x != self.x: + self.__x = x + + @property + def y(self) -> int or None: + try: + return int(self.__y) + except TypeError: + return None + + @y.setter + def y(self, y: float): + if y != self.y: + self.__y = y + + @property + def width(self) -> int or None: + try: + return int(self.__width) + except TypeError: + return None + + @width.setter + def width(self, width: float): + if width != self.width: + self.__width = width + + @property + def height(self) -> int or None: + try: + return int(self.__height) + except TypeError: + return None + + @height.setter + def height(self, height: float): + if height != self.height: + self.__height = height + + # Coordinate + @property + def coordinate_text(self) -> str: + return f"{int(abs(self.width))}, {int(abs(self.height))}" + + @property + def coordinate_text_width(self) -> int: + return self.coordinate_font_metrics.width(self.coordinate_text) + + @property + 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) -> int: + return int(self.y + self.height + self.coordinate_font_metrics.height()) + + @property + 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) -> int: + return int(self.y + self.height + (self.coordinate_spacing / 2)) + + @property + def coordinate_rect_width(self) -> int: + return int(self.coordinate_text_width + (self.coordinate_spacing * 2)) + + @property + def coordinate_rect_height(self) -> int: + return int(self.coordinate_font_metrics.height() + (self.coordinate_spacing / 2)) + + @property + def SelectionColorBackground(self) -> QColor: + if self.is_snipping: + color = QPalette().color(QPalette.Highlight) + color.setAlpha(5) + return color + else: + return QColor(0, 0, 0, 0) + + @property + def SelectionPen(self) -> QPen: + 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) -> None: + self.x = req.x() + 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) diff --git a/Under Construction/Grab.app/Resources/stylesheet.qss b/Under Construction/Grab.app/Resources/stylesheet.qss new file mode 100644 index 000000000..e1362583f --- /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); +} 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 000000000..000ea19c0 Binary files /dev/null and b/Under Construction/Grab.app/Resources/trigger_of_camera.wav differ 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 3cb6b2e7c..000000000 --- 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/widget_snipping_tool.py b/Under Construction/Grab.app/Resources/widget_snipping_tool.py new file mode 100644 index 000000000..2663d6d27 --- /dev/null +++ b/Under Construction/Grab.app/Resources/widget_snipping_tool.py @@ -0,0 +1,187 @@ +import os + +from PyQt5.QtCore import Qt, QPoint, QRectF, pyqtSignal +from PyQt5.QtGui import QPainter, QCursor, QKeySequence, QPixmap +from PyQt5.QtWidgets import QApplication, QWidget, qApp, QShortcut + +from property_selection_area import SelectionArea + + +# Refer to https://github.com/harupy/snipping-tool +class SnippingWidget(QWidget): + snipping_completed = pyqtSignal(object) + + def __init__(self, parent=None, cursor=None): + super(SnippingWidget, self).__init__() + self.parent = parent + + self.begin = None + self.end = None + + self.qp = None + self.selection = None + self.selection_rect = None + self.win_id = None + self.screen = None + self.selection_pen_width = None + self.enable_sound = None + 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.setupCustomUi() + self.connectSignalsSlots() + self.initialState() + + 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() + self.selection = SelectionArea() + + self.selection.is_snipping = False + self.UpdateScreen() + + def cancel_widget_snapping(self): + self.selection.is_snipping = False + self.begin = QPoint() + self.end = QPoint() + self.snipping_completed.emit(None) + QApplication.restoreOverrideCursor() + self.close() + + def UpdateScreen(self): + # Be sure the screen is the Parent of the Desktop + self.screen = qApp.primaryScreen() + self.win_id = QApplication.desktop().winId() + + # 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 full_screen(self): + self.UpdateScreen() + self.show() + + if not self.screen: + return + try: + img = self.screen.grabWindow(self.win_id) + except (Exception, BaseException): + img = None + + if img and self.cursor != Qt.BlankCursor: + pm = QPixmap(self.width(), self.height()) + 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( + QCursor().pos().x() - int(cursor_pixmap.width() / 2), + QCursor().pos().y() - int(cursor_pixmap.height() / 2), + cursor_pixmap.width(), + cursor_pixmap.height(), + cursor_pixmap, + ) + painter.end() + img = pm + + self.snipping_completed.emit(img) + + def start(self): + self.UpdateScreen() + self.selection.is_snipping = True + # self.setWindowOpacity(0.3) + QApplication.setOverrideCursor(QCursor(Qt.CrossCursor)) + self.show() + + def paintEvent(self, event): + if not self.selection.is_snipping: + self.begin = QPoint() + self.end = QPoint() + # self.setWindowOpacity(0.0) + self.qp.begin(self) + + # self.setWindowOpacity(opacity) + selection_rect = QRectF(self.begin, self.end) + if not selection_rect.isNull(): + # 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 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, + self.selection.coordinate_rect_y, + self.selection.coordinate_rect_width, + self.selection.coordinate_rect_height, + ) + ) + + self.qp.end() + + def mousePressEvent(self, event): + self.begin = event.pos() + self.end = self.begin + self.update() + + def mouseMoveEvent(self, event): + self.end = event.pos() + self.update() + + def mouseReleaseEvent(self, event): + self.UpdateScreen() + if not self.screen: + return + + self.selection.is_snipping = False + QApplication.restoreOverrideCursor() + self.repaint() + self.setWindowOpacity(0.0) + QApplication.processEvents() + + 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) + + self.snipping_completed.emit(img) + QApplication.restoreOverrideCursor() + self.close() diff --git a/Under Construction/Update.app/Resources/update.py b/Under Construction/Update.app/Resources/update.py index 673ea7c65..5d21d2bb8 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)