--- a/src/eric7/IconEditor/IconEditorGrid.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/IconEditor/IconEditorGrid.py Wed Jul 13 14:55:47 2022 +0200 @@ -12,8 +12,17 @@ from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QRect, QSize from PyQt6.QtGui import ( - QImage, QColor, QPixmap, qRgba, QPainter, QCursor, QBrush, qGray, qAlpha, - QUndoCommand, QUndoStack + QImage, + QColor, + QPixmap, + qRgba, + QPainter, + QCursor, + QBrush, + qGray, + qAlpha, + QUndoCommand, + QUndoStack, ) from PyQt6.QtWidgets import QWidget, QSizePolicy, QApplication, QDialog @@ -25,10 +34,11 @@ """ Class implementing an undo command for the icon editor. """ + def __init__(self, grid, text, oldImage, parent=None): """ Constructor - + @param grid reference to the icon editor grid (IconEditorGrid) @param text text for the undo command (string) @param oldImage copy of the icon before the changes were applied @@ -36,37 +46,38 @@ @param parent reference to the parent command (QUndoCommand) """ super().__init__(text, parent) - + self.__grid = grid self.__imageBefore = QImage(oldImage) self.__imageAfter = None - + def setAfterImage(self, image): """ Public method to set the image after the changes were applied. - + @param image copy of the icon after the changes were applied (QImage) """ self.__imageAfter = QImage(image) - + def undo(self): """ Public method to perform the undo. """ self.__grid.setIconImage(self.__imageBefore, undoRedo=True) - + def redo(self): """ Public method to perform the redo. """ if self.__imageAfter: self.__grid.setIconImage(self.__imageAfter, undoRedo=True) - + class IconEditorTool(enum.IntEnum): """ Class defining the edit tools. """ + PENCIL = 1 RUBBER = 2 LINE = 3 @@ -78,7 +89,7 @@ FILLED_ELLIPSE = 9 FILL = 10 COLOR_PICKER = 11 - + SELECT_RECTANGLE = 100 SELECT_CIRCLE = 101 @@ -86,7 +97,7 @@ class IconEditorGrid(QWidget): """ Class implementing the icon editor grid. - + @signal canRedoChanged(bool) emitted after the redo status has changed @signal canUndoChanged(bool) emitted after the undo status has changed @signal clipboardImageAvailable(bool) emitted to signal the availability @@ -101,6 +112,7 @@ @signal sizeChanged(int, int) emitted after the size has been changed @signal zoomChanged(int) emitted to signal a change of the zoom value """ + canRedoChanged = pyqtSignal(bool) canUndoChanged = pyqtSignal(bool) clipboardImageAvailable = pyqtSignal(bool) @@ -111,28 +123,27 @@ selectionAvailable = pyqtSignal(bool) sizeChanged = pyqtSignal(int, int) zoomChanged = pyqtSignal(int) - + MarkColor = QColor(255, 255, 255, 255) NoMarkColor = QColor(0, 0, 0, 0) - + ZoomMinimum = 100 ZoomMaximum = 10000 ZoomStep = 100 ZoomDefault = 1200 ZoomPercent = True - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget (QWidget) """ super().__init__(parent) - + self.setAttribute(Qt.WidgetAttribute.WA_StaticContents) - self.setSizePolicy(QSizePolicy.Policy.Minimum, - QSizePolicy.Policy.Minimum) - + self.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) + self.__curColor = Qt.GlobalColor.black self.__zoom = 12 self.__curTool = IconEditorTool.PENCIL @@ -144,70 +155,68 @@ self.__isPasting = False self.__clipboardSize = QSize() self.__pasteRect = QRect() - + self.__undoStack = QUndoStack(self) self.__currentUndoCmd = None - + self.__image = QImage(32, 32, QImage.Format.Format_ARGB32) self.__image.fill(Qt.GlobalColor.transparent) self.__markImage = QImage(self.__image) self.__markImage.fill(self.NoMarkColor.rgba()) - - self.__compositingMode = ( - QPainter.CompositionMode.CompositionMode_SourceOver - ) + + self.__compositingMode = QPainter.CompositionMode.CompositionMode_SourceOver self.__lastPos = (-1, -1) - + self.__gridEnabled = True self.__selectionAvailable = False - + self.__initCursors() self.__initUndoTexts() - + self.setMouseTracking(True) - + self.__undoStack.canRedoChanged.connect(self.canRedoChanged) self.__undoStack.canUndoChanged.connect(self.canUndoChanged) self.__undoStack.cleanChanged.connect(self.__cleanChanged) - + self.imageChanged.connect(self.__updatePreviewPixmap) QApplication.clipboard().dataChanged.connect(self.__checkClipboard) - + self.__checkClipboard() - + def __initCursors(self): """ Private method to initialize the various cursors. """ cursorsPath = os.path.join(os.path.dirname(__file__), "cursors") - + self.__normalCursor = QCursor(Qt.CursorShape.ArrowCursor) - + pix = QPixmap(os.path.join(cursorsPath, "colorpicker-cursor.xpm")) mask = pix.createHeuristicMask() pix.setMask(mask) self.__colorPickerCursor = QCursor(pix, 1, 21) - + pix = QPixmap(os.path.join(cursorsPath, "paintbrush-cursor.xpm")) mask = pix.createHeuristicMask() pix.setMask(mask) self.__paintCursor = QCursor(pix, 0, 19) - + pix = QPixmap(os.path.join(cursorsPath, "fill-cursor.xpm")) mask = pix.createHeuristicMask() pix.setMask(mask) self.__fillCursor = QCursor(pix, 3, 20) - + pix = QPixmap(os.path.join(cursorsPath, "aim-cursor.xpm")) mask = pix.createHeuristicMask() pix.setMask(mask) self.__aimCursor = QCursor(pix, 10, 10) - + pix = QPixmap(os.path.join(cursorsPath, "eraser-cursor.xpm")) mask = pix.createHeuristicMask() pix.setMask(mask) self.__rubberCursor = QCursor(pix, 1, 16) - + def __initUndoTexts(self): """ Private method to initialize texts to be associated with undo commands @@ -225,96 +234,101 @@ IconEditorTool.FILLED_ELLIPSE: self.tr("Draw Filled Ellipse"), IconEditorTool.FILL: self.tr("Fill Region"), } - + def isDirty(self): """ Public method to check the dirty status. - + @return flag indicating a modified status (boolean) """ return self.__dirty - + def setDirty(self, dirty, setCleanState=False): """ Public slot to set the dirty flag. - + @param dirty flag indicating the new modification status (boolean) @param setCleanState flag indicating to set the undo stack to clean (boolean) """ self.__dirty = dirty self.imageChanged.emit(dirty) - + if not dirty and setCleanState: self.__undoStack.setClean() - + def sizeHint(self): """ Public method to report the size hint. - + @return size hint (QSize) """ size = self.__zoom * self.__image.size() if self.__zoom >= 3 and self.__gridEnabled: size += QSize(1, 1) return size - + def setPenColor(self, newColor): """ Public method to set the drawing color. - + @param newColor reference to the new color (QColor) """ self.__curColor = QColor(newColor) self.colorChanged.emit(QColor(newColor)) - + def penColor(self): """ Public method to get the current drawing color. - + @return current drawing color (QColor) """ return QColor(self.__curColor) - + def setCompositingMode(self, mode): """ Public method to set the compositing mode. - + @param mode compositing mode to set (QPainter.CompositionMode) """ self.__compositingMode = mode - + def compositingMode(self): """ Public method to get the compositing mode. - + @return compositing mode (QPainter.CompositionMode) """ return self.__compositingMode - + def setTool(self, tool): """ Public method to set the current drawing tool. - + @param tool drawing tool to be used @type IconEditorTool """ self.__curTool = tool self.__lastPos = (-1, -1) - + if self.__curTool in [ - IconEditorTool.SELECT_RECTANGLE, IconEditorTool.SELECT_CIRCLE + IconEditorTool.SELECT_RECTANGLE, + IconEditorTool.SELECT_CIRCLE, ]: self.__selecting = True else: self.__selecting = False - + if self.__curTool in [ - IconEditorTool.SELECT_RECTANGLE, IconEditorTool.SELECT_CIRCLE, + IconEditorTool.SELECT_RECTANGLE, + IconEditorTool.SELECT_CIRCLE, IconEditorTool.LINE, - IconEditorTool.RECTANGLE, IconEditorTool.FILLED_RECTANGLE, - IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, - IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE + IconEditorTool.RECTANGLE, + IconEditorTool.FILLED_RECTANGLE, + IconEditorTool.CIRCLE, + IconEditorTool.FILLED_CIRCLE, + IconEditorTool.ELLIPSE, + IconEditorTool.FILLED_ELLIPSE, ]: self.setCursor(self.__aimCursor) elif self.__curTool == IconEditorTool.FILL: @@ -327,108 +341,107 @@ self.setCursor(self.__rubberCursor) else: self.setCursor(self.__normalCursor) - + def tool(self): """ Public method to get the current drawing tool. - + @return current drawing tool @rtype IconEditorTool """ return self.__curTool - + def setIconImage(self, newImage, undoRedo=False, clearUndo=False): """ Public method to set a new icon image. - + @param newImage reference to the new image (QImage) @param undoRedo flag indicating an undo or redo operation (boolean) @param clearUndo flag indicating to clear the undo stack (boolean) """ if newImage != self.__image: - self.__image = newImage.convertToFormat( - QImage.Format.Format_ARGB32) + self.__image = newImage.convertToFormat(QImage.Format.Format_ARGB32) self.update() self.updateGeometry() self.resize(self.sizeHint()) - + self.__markImage = QImage(self.__image) self.__markImage.fill(self.NoMarkColor.rgba()) - + if undoRedo: self.setDirty(not self.__undoStack.isClean()) else: self.setDirty(False) - + if clearUndo: self.__undoStack.clear() - + self.sizeChanged.emit(*self.iconSize()) - + def iconImage(self): """ Public method to get a copy of the icon image. - + @return copy of the icon image (QImage) """ return QImage(self.__image) - + def iconSize(self): """ Public method to get the size of the icon. - + @return width and height of the image as a tuple (integer, integer) """ return self.__image.width(), self.__image.height() - + def setZoomFactor(self, newZoom): """ Public method to set the zoom factor in percent. - + @param newZoom zoom factor (integer >= 100) """ - newZoom = max(100, newZoom) # must not be less than 100 + newZoom = max(100, newZoom) # must not be less than 100 if newZoom != self.__zoom: self.__zoom = newZoom // 100 self.update() self.updateGeometry() self.resize(self.sizeHint()) self.zoomChanged.emit(int(self.__zoom * 100)) - + def zoomFactor(self): """ Public method to get the current zoom factor in percent. - + @return zoom factor (integer) """ return self.__zoom * 100 - + def setGridEnabled(self, enable): """ Public method to enable the display of grid lines. - + @param enable enabled status of the grid lines (boolean) """ if enable != self.__gridEnabled: self.__gridEnabled = enable self.update() - + def isGridEnabled(self): """ Public method to get the grid lines status. - + @return enabled status of the grid lines (boolean) """ return self.__gridEnabled - + def paintEvent(self, evt): """ Protected method called to repaint some of the widget. - + @param evt reference to the paint event object (QPaintEvent) """ painter = QPainter(self) - + if self.__zoom >= 3 and self.__gridEnabled: if ericApp().usesDarkPalette(): painter.setPen(self.palette().window().color()) @@ -437,16 +450,22 @@ i = 0 while i <= self.__image.width(): painter.drawLine( - self.__zoom * i, 0, - self.__zoom * i, self.__zoom * self.__image.height()) + self.__zoom * i, + 0, + self.__zoom * i, + self.__zoom * self.__image.height(), + ) i += 1 j = 0 while j <= self.__image.height(): painter.drawLine( - 0, self.__zoom * j, - self.__zoom * self.__image.width(), self.__zoom * j) + 0, + self.__zoom * j, + self.__zoom * self.__image.width(), + self.__zoom * j, + ) j += 1 - + col = QColor("#aaa") painter.setPen(Qt.PenStyle.DashLine) for i in range(0, self.__image.width()): @@ -456,34 +475,36 @@ color = QColor.fromRgba(self.__image.pixel(i, j)) painter.fillRect(rect, QBrush(Qt.GlobalColor.white)) painter.fillRect(QRect(rect.topLeft(), rect.center()), col) - painter.fillRect(QRect(rect.center(), rect.bottomRight()), - col) + painter.fillRect(QRect(rect.center(), rect.bottomRight()), col) painter.fillRect(rect, QBrush(color)) - + if self.__isMarked(i, j): painter.drawRect(rect.adjusted(0, 0, -1, -1)) - + painter.end() - + def __pixelRect(self, i, j): """ Private method to determine the rectangle for a given pixel coordinate. - + @param i x-coordinate of the pixel in the image (integer) @param j y-coordinate of the pixel in the image (integer) @return rectangle for the given pixel coordinates (QRect) """ if self.__zoom >= 3 and self.__gridEnabled: - return QRect(self.__zoom * i + 1, self.__zoom * j + 1, - self.__zoom - 1, self.__zoom - 1) + return QRect( + self.__zoom * i + 1, + self.__zoom * j + 1, + self.__zoom - 1, + self.__zoom - 1, + ) else: - return QRect(self.__zoom * i, self.__zoom * j, - self.__zoom, self.__zoom) - + return QRect(self.__zoom * i, self.__zoom * j, self.__zoom, self.__zoom) + def mousePressEvent(self, evt): """ Protected method to handle mouse button press events. - + @param evt reference to the mouse event object (QMouseEvent) """ if evt.button() == Qt.MouseButton.LeftButton: @@ -494,17 +515,19 @@ self.update(self.__pasteRect) self.__pasteRect = QRect() return - + if self.__curTool == IconEditorTool.PENCIL: - cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], - self.__image) + cmd = IconEditCommand( + self, self.__undoTexts[self.__curTool], self.__image + ) self.__setImagePixel(evt.position().toPoint(), True) self.setDirty(True) self.__undoStack.push(cmd) self.__currentUndoCmd = cmd elif self.__curTool == IconEditorTool.RUBBER: - cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], - self.__image) + cmd = IconEditCommand( + self, self.__undoTexts[self.__curTool], self.__image + ) self.__setImagePixel(evt.position().toPoint(), False) self.setDirty(True) self.__undoStack.push(cmd) @@ -513,8 +536,9 @@ i, j = self.__imageCoordinates(evt.position().toPoint()) col = QColor() col.setRgba(self.__image.pixel(i, j)) - cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], - self.__image) + cmd = IconEditCommand( + self, self.__undoTexts[self.__curTool], self.__image + ) self.__drawFlood(i, j, col) self.setDirty(True) self.__undoStack.push(cmd) @@ -528,23 +552,19 @@ self.__unMark() self.__startPos = evt.position().toPoint() self.__endPos = evt.position().toPoint() - + def mouseMoveEvent(self, evt): """ Protected method to handle mouse move events. - + @param evt reference to the mouse event object (QMouseEvent) """ - self.positionChanged.emit( - *self.__imageCoordinates(evt.position().toPoint())) - - if ( - self.__isPasting and - not (evt.buttons() & Qt.MouseButton.LeftButton) - ): + self.positionChanged.emit(*self.__imageCoordinates(evt.position().toPoint())) + + if self.__isPasting and not (evt.buttons() & Qt.MouseButton.LeftButton): self.__drawPasteRect(evt.position().toPoint()) return - + if evt.buttons() & Qt.MouseButton.LeftButton: if self.__curTool == IconEditorTool.PENCIL: self.__setImagePixel(evt.position().toPoint(), True) @@ -552,48 +572,50 @@ elif self.__curTool == IconEditorTool.RUBBER: self.__setImagePixel(evt.position().toPoint(), False) self.setDirty(True) - elif self.__curTool in [IconEditorTool.FILL, - IconEditorTool.COLOR_PICKER]: - pass # do nothing + elif self.__curTool in [IconEditorTool.FILL, IconEditorTool.COLOR_PICKER]: + pass # do nothing else: self.__drawTool(evt.position().toPoint(), True) - + def mouseReleaseEvent(self, evt): """ Protected method to handle mouse button release events. - + @param evt reference to the mouse event object (QMouseEvent) """ if evt.button() == Qt.MouseButton.LeftButton: if ( - self.__curTool in [IconEditorTool.PENCIL, - IconEditorTool.RUBBER] and - self.__currentUndoCmd + self.__curTool in [IconEditorTool.PENCIL, IconEditorTool.RUBBER] + and self.__currentUndoCmd ): self.__currentUndoCmd.setAfterImage(self.__image) self.__currentUndoCmd = None - + if self.__curTool not in [ - IconEditorTool.PENCIL, IconEditorTool.RUBBER, - IconEditorTool.FILL, IconEditorTool.COLOR_PICKER, - IconEditorTool.SELECT_RECTANGLE, IconEditorTool.SELECT_CIRCLE + IconEditorTool.PENCIL, + IconEditorTool.RUBBER, + IconEditorTool.FILL, + IconEditorTool.COLOR_PICKER, + IconEditorTool.SELECT_RECTANGLE, + IconEditorTool.SELECT_CIRCLE, ]: - cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], - self.__image) + cmd = IconEditCommand( + self, self.__undoTexts[self.__curTool], self.__image + ) if self.__drawTool(evt.position().toPoint(), False): self.__undoStack.push(cmd) cmd.setAfterImage(self.__image) self.setDirty(True) - + def __setImagePixel(self, pos, opaque): """ Private slot to set or erase a pixel. - + @param pos position of the pixel in the widget (QPoint) @param opaque flag indicating a set operation (boolean) """ i, j = self.__imageCoordinates(pos) - + if self.__image.rect().contains(i, j) and (i, j) != self.__lastPos: if opaque: painter = QPainter(self.__image) @@ -603,66 +625,68 @@ else: self.__image.setPixel(i, j, qRgba(0, 0, 0, 0)) self.__lastPos = (i, j) - + self.update(self.__pixelRect(i, j)) - + def __imageCoordinates(self, pos): """ Private method to convert from widget to image coordinates. - + @param pos widget coordinate (QPoint) @return tuple with the image coordinates (tuple of two integers) """ i = pos.x() // self.__zoom j = pos.y() // self.__zoom return i, j - + def __drawPasteRect(self, pos): """ Private slot to draw a rectangle for signaling a paste operation. - + @param pos widget position of the paste rectangle (QPoint) """ self.__markImage.fill(self.NoMarkColor.rgba()) if self.__pasteRect.isValid(): self.__updateImageRect( self.__pasteRect.topLeft(), - self.__pasteRect.bottomRight() + QPoint(1, 1)) - + self.__pasteRect.bottomRight() + QPoint(1, 1), + ) + x, y = self.__imageCoordinates(pos) isize = self.__image.size() sx = ( self.__clipboardSize.width() - if x + self.__clipboardSize.width() <= isize.width() else - isize.width() - x + if x + self.__clipboardSize.width() <= isize.width() + else isize.width() - x ) sy = ( self.__clipboardSize.height() - if y + self.__clipboardSize.height() <= isize.height() else - isize.height() - y + if y + self.__clipboardSize.height() <= isize.height() + else isize.height() - y ) - + self.__pasteRect = QRect(QPoint(x, y), QSize(sx - 1, sy - 1)) - + painter = QPainter(self.__markImage) painter.setPen(self.MarkColor) painter.drawRect(self.__pasteRect) painter.end() - - self.__updateImageRect(self.__pasteRect.topLeft(), - self.__pasteRect.bottomRight() + QPoint(1, 1)) - + + self.__updateImageRect( + self.__pasteRect.topLeft(), self.__pasteRect.bottomRight() + QPoint(1, 1) + ) + def __drawTool(self, pos, mark): """ Private method to perform a draw operation depending of the current tool. - + @param pos widget coordinate to perform the draw operation at (QPoint) @param mark flag indicating a mark operation (boolean) @return flag indicating a successful draw (boolean) """ self.__unMark() - + if mark: self.__endPos = QPoint(pos) drawColor = self.MarkColor @@ -670,20 +694,21 @@ else: drawColor = self.penColor() img = self.__image - + start = QPoint(*self.__imageCoordinates(self.__startPos)) end = QPoint(*self.__imageCoordinates(pos)) - + painter = QPainter(img) painter.setPen(drawColor) painter.setCompositionMode(self.__compositingMode) - + if self.__curTool == IconEditorTool.LINE: painter.drawLine(start, end) - + elif self.__curTool in [ - IconEditorTool.RECTANGLE, IconEditorTool.FILLED_RECTANGLE, - IconEditorTool.SELECT_RECTANGLE + IconEditorTool.RECTANGLE, + IconEditorTool.FILLED_RECTANGLE, + IconEditorTool.SELECT_RECTANGLE, ]: left = min(start.x(), end.x()) top = min(start.y(), end.y()) @@ -697,32 +722,32 @@ else: painter.drawRect(left, top, right - left, bottom - top) if self.__selecting: - self.__selRect = QRect( - left, top, right - left + 1, bottom - top + 1) + self.__selRect = QRect(left, top, right - left + 1, bottom - top + 1) self.__selectionAvailable = True self.selectionAvailable.emit(True) - + elif self.__curTool in [ - IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, - IconEditorTool.SELECT_CIRCLE + IconEditorTool.CIRCLE, + IconEditorTool.FILLED_CIRCLE, + IconEditorTool.SELECT_CIRCLE, ]: deltaX = abs(start.x() - end.x()) deltaY = abs(start.y() - end.y()) r = max(deltaX, deltaY) if self.__curTool in [ - IconEditorTool.FILLED_CIRCLE, IconEditorTool.SELECT_CIRCLE + IconEditorTool.FILLED_CIRCLE, + IconEditorTool.SELECT_CIRCLE, ]: painter.setBrush(QBrush(drawColor)) painter.drawEllipse(start, r, r) if self.__selecting: - self.__selRect = QRect(start.x() - r, start.y() - r, - 2 * r + 1, 2 * r + 1) + self.__selRect = QRect( + start.x() - r, start.y() - r, 2 * r + 1, 2 * r + 1 + ) self.__selectionAvailable = True self.selectionAvailable.emit(True) - - elif self.__curTool in [ - IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE - ]: + + elif self.__curTool in [IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE]: r1 = abs(start.x() - end.x()) r2 = abs(start.y() - end.y()) if r1 == 0 or r2 == 0: @@ -730,23 +755,25 @@ if self.__curTool == IconEditorTool.FILLED_ELLIPSE: painter.setBrush(QBrush(drawColor)) painter.drawEllipse(start, r1, r2) - + painter.end() - + if self.__curTool in [ - IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, - IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE + IconEditorTool.CIRCLE, + IconEditorTool.FILLED_CIRCLE, + IconEditorTool.ELLIPSE, + IconEditorTool.FILLED_ELLIPSE, ]: self.update() else: self.__updateRect(self.__startPos, pos) - + return True - + def __drawFlood(self, i, j, oldColor, doUpdate=True): """ Private method to perform a flood fill operation. - + @param i x-value in image coordinates (integer) @param j y-value in image coordinates (integer) @param oldColor reference to the color at position i, j (QColor) @@ -754,38 +781,40 @@ (used for speed optimizations) """ if ( - not self.__image.rect().contains(i, j) or - self.__image.pixel(i, j) != oldColor.rgba() or - self.__image.pixel(i, j) == self.penColor().rgba() + not self.__image.rect().contains(i, j) + or self.__image.pixel(i, j) != oldColor.rgba() + or self.__image.pixel(i, j) == self.penColor().rgba() ): return - + self.__image.setPixel(i, j, self.penColor().rgba()) - + self.__drawFlood(i, j - 1, oldColor, False) self.__drawFlood(i, j + 1, oldColor, False) self.__drawFlood(i - 1, j, oldColor, False) self.__drawFlood(i + 1, j, oldColor, False) - + if doUpdate: self.update() - + def __updateRect(self, pos1, pos2): """ Private slot to update parts of the widget. - + @param pos1 top, left position for the update in widget coordinates (QPoint) @param pos2 bottom, right position for the update in widget coordinates (QPoint) """ - self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)), - QPoint(*self.__imageCoordinates(pos2))) - + self.__updateImageRect( + QPoint(*self.__imageCoordinates(pos1)), + QPoint(*self.__imageCoordinates(pos2)), + ) + def __updateImageRect(self, ipos1, ipos2): """ Private slot to update parts of the widget. - + @param ipos1 top, left position for the update in image coordinates (QPoint) @param ipos2 bottom, right position for the update in image @@ -793,58 +822,60 @@ """ r1 = self.__pixelRect(ipos1.x(), ipos1.y()) r2 = self.__pixelRect(ipos2.x(), ipos2.y()) - + left = min(r1.x(), r2.x()) top = min(r1.y(), r2.y()) right = max(r1.x() + r1.width(), r2.x() + r2.width()) bottom = max(r1.y() + r1.height(), r2.y() + r2.height()) self.update(left, top, right - left + 1, bottom - top + 1) - + def __unMark(self): """ Private slot to remove the mark indicator. """ self.__markImage.fill(self.NoMarkColor.rgba()) if self.__curTool in [ - IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, - IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE, - IconEditorTool.SELECT_CIRCLE + IconEditorTool.CIRCLE, + IconEditorTool.FILLED_CIRCLE, + IconEditorTool.ELLIPSE, + IconEditorTool.FILLED_ELLIPSE, + IconEditorTool.SELECT_CIRCLE, ]: self.update() else: self.__updateRect(self.__startPos, self.__endPos) - + if self.__selecting: self.__selRect = QRect() self.__selectionAvailable = False self.selectionAvailable.emit(False) - + def __isMarked(self, i, j): """ Private method to check, if a pixel is marked. - + @param i x-value in image coordinates (integer) @param j y-value in image coordinates (integer) @return flag indicating a marked pixel (boolean) """ return self.__markImage.pixel(i, j) == self.MarkColor.rgba() - + def __updatePreviewPixmap(self): """ Private slot to generate and signal an updated preview pixmap. """ p = QPixmap.fromImage(self.__image) self.previewChanged.emit(p) - + def previewPixmap(self): """ Public method to generate a preview pixmap. - + @return preview pixmap (QPixmap) """ p = QPixmap.fromImage(self.__image) return p - + def __checkClipboard(self): """ Private slot to check, if the clipboard contains a valid image, and @@ -853,19 +884,19 @@ ok = self.__clipboardImage()[1] self.__clipboardImageAvailable = ok self.clipboardImageAvailable.emit(ok) - + def canPaste(self): """ Public slot to check the availability of the paste operation. - + @return flag indicating availability of paste (boolean) """ return self.__clipboardImageAvailable - + def __clipboardImage(self): """ Private method to get an image from the clipboard. - + @return tuple with the image (QImage) and a flag indicating a valid image (boolean) """ @@ -873,48 +904,51 @@ ok = not img.isNull() if ok: img = img.convertToFormat(QImage.Format.Format_ARGB32) - + return img, ok - + def __getSelectionImage(self, cut): """ Private method to get an image from the selection. - + @param cut flag indicating to cut the selection (boolean) @return image of the selection (QImage) """ if cut: - cmd = IconEditCommand(self, self.tr("Cut Selection"), - self.__image) - + cmd = IconEditCommand(self, self.tr("Cut Selection"), self.__image) + img = QImage(self.__selRect.size(), QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.transparent) for i in range(0, self.__selRect.width()): for j in range(0, self.__selRect.height()): - if ( - self.__image.rect().contains( - self.__selRect.x() + i, self.__selRect.y() + j) and - self.__isMarked(self.__selRect.x() + i, - self.__selRect.y() + j) - ): - img.setPixel(i, j, self.__image.pixel( - self.__selRect.x() + i, self.__selRect.y() + j)) + if self.__image.rect().contains( + self.__selRect.x() + i, self.__selRect.y() + j + ) and self.__isMarked(self.__selRect.x() + i, self.__selRect.y() + j): + img.setPixel( + i, + j, + self.__image.pixel( + self.__selRect.x() + i, self.__selRect.y() + j + ), + ) if cut: - self.__image.setPixel(self.__selRect.x() + i, - self.__selRect.y() + j, - Qt.GlobalColor.transparent) - + self.__image.setPixel( + self.__selRect.x() + i, + self.__selRect.y() + j, + Qt.GlobalColor.transparent, + ) + if cut: self.__undoStack.push(cmd) cmd.setAfterImage(self.__image) - + self.__unMark() - + if cut: self.update(self.__selRect) - + return img - + def editCopy(self): """ Public slot to copy the selection. @@ -922,7 +956,7 @@ if self.__selRect.isValid(): img = self.__getSelectionImage(False) QApplication.clipboard().setImage(img) - + def editCut(self): """ Public slot to cut the selection. @@ -930,27 +964,29 @@ if self.__selRect.isValid(): img = self.__getSelectionImage(True) QApplication.clipboard().setImage(img) - + @pyqtSlot() def editPaste(self, pasting=False): """ Public slot to paste an image from the clipboard. - + @param pasting flag indicating part two of the paste operation (boolean) """ img, ok = self.__clipboardImage() if ok: if ( - img.width() > self.__image.width() or - img.height() > self.__image.height() + img.width() > self.__image.width() + or img.height() > self.__image.height() ): res = EricMessageBox.yesNo( self, self.tr("Paste"), self.tr( """<p>The clipboard image is larger than the""" - """ current image.<br/>Paste as new image?</p>""")) + """ current image.<br/>Paste as new image?</p>""" + ), + ) if res: self.editPasteAsNew() return @@ -958,29 +994,35 @@ self.__isPasting = True self.__clipboardSize = img.size() else: - cmd = IconEditCommand(self, self.tr("Paste Clipboard"), - self.__image) + cmd = IconEditCommand(self, self.tr("Paste Clipboard"), self.__image) self.__markImage.fill(self.NoMarkColor.rgba()) painter = QPainter(self.__image) painter.setPen(self.penColor()) painter.setCompositionMode(self.__compositingMode) painter.drawImage( - self.__pasteRect.x(), self.__pasteRect.y(), img, 0, 0, + self.__pasteRect.x(), + self.__pasteRect.y(), + img, + 0, + 0, self.__pasteRect.width() + 1, - self.__pasteRect.height() + 1) - + self.__pasteRect.height() + 1, + ) + self.__undoStack.push(cmd) cmd.setAfterImage(self.__image) - + self.__updateImageRect( self.__pasteRect.topLeft(), - self.__pasteRect.bottomRight() + QPoint(1, 1)) + self.__pasteRect.bottomRight() + QPoint(1, 1), + ) else: EricMessageBox.warning( self, self.tr("Pasting Image"), - self.tr("""Invalid image data in clipboard.""")) - + self.tr("""Invalid image data in clipboard."""), + ) + def editPasteAsNew(self): """ Public slot to paste the clipboard as a new image. @@ -988,69 +1030,70 @@ img, ok = self.__clipboardImage() if ok: cmd = IconEditCommand( - self, self.tr("Paste Clipboard as New Image"), - self.__image) + self, self.tr("Paste Clipboard as New Image"), self.__image + ) self.setIconImage(img) self.setDirty(True) self.__undoStack.push(cmd) cmd.setAfterImage(self.__image) - + def editSelectAll(self): """ Public slot to select the complete image. """ self.__unMark() - + self.__startPos = QPoint(0, 0) self.__endPos = QPoint(self.rect().bottomRight()) self.__markImage.fill(self.MarkColor.rgba()) self.__selRect = self.__image.rect() self.__selectionAvailable = True self.selectionAvailable.emit(True) - + self.update() - + def editClear(self): """ Public slot to clear the image. """ self.__unMark() - + cmd = IconEditCommand(self, self.tr("Clear Image"), self.__image) self.__image.fill(Qt.GlobalColor.transparent) self.update() self.setDirty(True) self.__undoStack.push(cmd) cmd.setAfterImage(self.__image) - + def editResize(self): """ Public slot to resize the image. """ from .IconSizeDialog import IconSizeDialog + dlg = IconSizeDialog(self.__image.width(), self.__image.height()) res = dlg.exec() if res == QDialog.DialogCode.Accepted: newWidth, newHeight = dlg.getData() - if ( - newWidth != self.__image.width() or - newHeight != self.__image.height() - ): - cmd = IconEditCommand(self, self.tr("Resize Image"), - self.__image) + if newWidth != self.__image.width() or newHeight != self.__image.height(): + cmd = IconEditCommand(self, self.tr("Resize Image"), self.__image) img = self.__image.scaled( - newWidth, newHeight, Qt.AspectRatioMode.IgnoreAspectRatio, - Qt.TransformationMode.SmoothTransformation) + newWidth, + newHeight, + Qt.AspectRatioMode.IgnoreAspectRatio, + Qt.TransformationMode.SmoothTransformation, + ) self.setIconImage(img) self.setDirty(True) self.__undoStack.push(cmd) cmd.setAfterImage(self.__image) - + def editNew(self): """ Public slot to generate a new, empty image. """ from .IconSizeDialog import IconSizeDialog + dlg = IconSizeDialog(self.__image.width(), self.__image.height()) res = dlg.exec() if res == QDialog.DialogCode.Accepted: @@ -1058,20 +1101,18 @@ img = QImage(width, height, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.transparent) self.setIconImage(img) - + def grayScale(self): """ Public slot to convert the image to gray preserving transparency. """ - cmd = IconEditCommand(self, self.tr("Convert to Grayscale"), - self.__image) + cmd = IconEditCommand(self, self.tr("Convert to Grayscale"), self.__image) for x in range(self.__image.width()): for y in range(self.__image.height()): col = self.__image.pixel(x, y) if col != qRgba(0, 0, 0, 0): gray = qGray(col) - self.__image.setPixel( - x, y, qRgba(gray, gray, gray, qAlpha(col))) + self.__image.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(col))) self.update() self.setDirty(True) self.__undoStack.push(cmd) @@ -1083,38 +1124,38 @@ """ if self.__undoStack.canUndo(): self.__undoStack.undo() - + def editRedo(self): """ Public slot to perform a redo operation. """ if self.__undoStack.canRedo(): self.__undoStack.redo() - + def canUndo(self): """ Public method to return the undo status. - + @return flag indicating the availability of undo (boolean) """ return self.__undoStack.canUndo() - + def canRedo(self): """ Public method to return the redo status. - + @return flag indicating the availability of redo (boolean) """ return self.__undoStack.canRedo() - + def __cleanChanged(self, clean): """ Private slot to handle the undo stack clean state change. - + @param clean flag indicating the clean state (boolean) """ self.setDirty(not clean) - + def shutdown(self): """ Public slot to perform some shutdown actions. @@ -1122,11 +1163,11 @@ self.__undoStack.canRedoChanged.disconnect(self.canRedoChanged) self.__undoStack.canUndoChanged.disconnect(self.canUndoChanged) self.__undoStack.cleanChanged.disconnect(self.__cleanChanged) - + def isSelectionAvailable(self): """ Public method to check the availability of a selection. - + @return flag indicating the availability of a selection (boolean) """ return self.__selectionAvailable