142 self.__selecting = False |
153 self.__selecting = False |
143 self.__selRect = QRect() |
154 self.__selRect = QRect() |
144 self.__isPasting = False |
155 self.__isPasting = False |
145 self.__clipboardSize = QSize() |
156 self.__clipboardSize = QSize() |
146 self.__pasteRect = QRect() |
157 self.__pasteRect = QRect() |
147 |
158 |
148 self.__undoStack = QUndoStack(self) |
159 self.__undoStack = QUndoStack(self) |
149 self.__currentUndoCmd = None |
160 self.__currentUndoCmd = None |
150 |
161 |
151 self.__image = QImage(32, 32, QImage.Format.Format_ARGB32) |
162 self.__image = QImage(32, 32, QImage.Format.Format_ARGB32) |
152 self.__image.fill(Qt.GlobalColor.transparent) |
163 self.__image.fill(Qt.GlobalColor.transparent) |
153 self.__markImage = QImage(self.__image) |
164 self.__markImage = QImage(self.__image) |
154 self.__markImage.fill(self.NoMarkColor.rgba()) |
165 self.__markImage.fill(self.NoMarkColor.rgba()) |
155 |
166 |
156 self.__compositingMode = ( |
167 self.__compositingMode = QPainter.CompositionMode.CompositionMode_SourceOver |
157 QPainter.CompositionMode.CompositionMode_SourceOver |
|
158 ) |
|
159 self.__lastPos = (-1, -1) |
168 self.__lastPos = (-1, -1) |
160 |
169 |
161 self.__gridEnabled = True |
170 self.__gridEnabled = True |
162 self.__selectionAvailable = False |
171 self.__selectionAvailable = False |
163 |
172 |
164 self.__initCursors() |
173 self.__initCursors() |
165 self.__initUndoTexts() |
174 self.__initUndoTexts() |
166 |
175 |
167 self.setMouseTracking(True) |
176 self.setMouseTracking(True) |
168 |
177 |
169 self.__undoStack.canRedoChanged.connect(self.canRedoChanged) |
178 self.__undoStack.canRedoChanged.connect(self.canRedoChanged) |
170 self.__undoStack.canUndoChanged.connect(self.canUndoChanged) |
179 self.__undoStack.canUndoChanged.connect(self.canUndoChanged) |
171 self.__undoStack.cleanChanged.connect(self.__cleanChanged) |
180 self.__undoStack.cleanChanged.connect(self.__cleanChanged) |
172 |
181 |
173 self.imageChanged.connect(self.__updatePreviewPixmap) |
182 self.imageChanged.connect(self.__updatePreviewPixmap) |
174 QApplication.clipboard().dataChanged.connect(self.__checkClipboard) |
183 QApplication.clipboard().dataChanged.connect(self.__checkClipboard) |
175 |
184 |
176 self.__checkClipboard() |
185 self.__checkClipboard() |
177 |
186 |
178 def __initCursors(self): |
187 def __initCursors(self): |
179 """ |
188 """ |
180 Private method to initialize the various cursors. |
189 Private method to initialize the various cursors. |
181 """ |
190 """ |
182 cursorsPath = os.path.join(os.path.dirname(__file__), "cursors") |
191 cursorsPath = os.path.join(os.path.dirname(__file__), "cursors") |
183 |
192 |
184 self.__normalCursor = QCursor(Qt.CursorShape.ArrowCursor) |
193 self.__normalCursor = QCursor(Qt.CursorShape.ArrowCursor) |
185 |
194 |
186 pix = QPixmap(os.path.join(cursorsPath, "colorpicker-cursor.xpm")) |
195 pix = QPixmap(os.path.join(cursorsPath, "colorpicker-cursor.xpm")) |
187 mask = pix.createHeuristicMask() |
196 mask = pix.createHeuristicMask() |
188 pix.setMask(mask) |
197 pix.setMask(mask) |
189 self.__colorPickerCursor = QCursor(pix, 1, 21) |
198 self.__colorPickerCursor = QCursor(pix, 1, 21) |
190 |
199 |
191 pix = QPixmap(os.path.join(cursorsPath, "paintbrush-cursor.xpm")) |
200 pix = QPixmap(os.path.join(cursorsPath, "paintbrush-cursor.xpm")) |
192 mask = pix.createHeuristicMask() |
201 mask = pix.createHeuristicMask() |
193 pix.setMask(mask) |
202 pix.setMask(mask) |
194 self.__paintCursor = QCursor(pix, 0, 19) |
203 self.__paintCursor = QCursor(pix, 0, 19) |
195 |
204 |
196 pix = QPixmap(os.path.join(cursorsPath, "fill-cursor.xpm")) |
205 pix = QPixmap(os.path.join(cursorsPath, "fill-cursor.xpm")) |
197 mask = pix.createHeuristicMask() |
206 mask = pix.createHeuristicMask() |
198 pix.setMask(mask) |
207 pix.setMask(mask) |
199 self.__fillCursor = QCursor(pix, 3, 20) |
208 self.__fillCursor = QCursor(pix, 3, 20) |
200 |
209 |
201 pix = QPixmap(os.path.join(cursorsPath, "aim-cursor.xpm")) |
210 pix = QPixmap(os.path.join(cursorsPath, "aim-cursor.xpm")) |
202 mask = pix.createHeuristicMask() |
211 mask = pix.createHeuristicMask() |
203 pix.setMask(mask) |
212 pix.setMask(mask) |
204 self.__aimCursor = QCursor(pix, 10, 10) |
213 self.__aimCursor = QCursor(pix, 10, 10) |
205 |
214 |
206 pix = QPixmap(os.path.join(cursorsPath, "eraser-cursor.xpm")) |
215 pix = QPixmap(os.path.join(cursorsPath, "eraser-cursor.xpm")) |
207 mask = pix.createHeuristicMask() |
216 mask = pix.createHeuristicMask() |
208 pix.setMask(mask) |
217 pix.setMask(mask) |
209 self.__rubberCursor = QCursor(pix, 1, 16) |
218 self.__rubberCursor = QCursor(pix, 1, 16) |
210 |
219 |
211 def __initUndoTexts(self): |
220 def __initUndoTexts(self): |
212 """ |
221 """ |
213 Private method to initialize texts to be associated with undo commands |
222 Private method to initialize texts to be associated with undo commands |
214 for the various drawing tools. |
223 for the various drawing tools. |
215 """ |
224 """ |
223 IconEditorTool.FILLED_CIRCLE: self.tr("Draw Filled Circle"), |
232 IconEditorTool.FILLED_CIRCLE: self.tr("Draw Filled Circle"), |
224 IconEditorTool.ELLIPSE: self.tr("Draw Ellipse"), |
233 IconEditorTool.ELLIPSE: self.tr("Draw Ellipse"), |
225 IconEditorTool.FILLED_ELLIPSE: self.tr("Draw Filled Ellipse"), |
234 IconEditorTool.FILLED_ELLIPSE: self.tr("Draw Filled Ellipse"), |
226 IconEditorTool.FILL: self.tr("Fill Region"), |
235 IconEditorTool.FILL: self.tr("Fill Region"), |
227 } |
236 } |
228 |
237 |
229 def isDirty(self): |
238 def isDirty(self): |
230 """ |
239 """ |
231 Public method to check the dirty status. |
240 Public method to check the dirty status. |
232 |
241 |
233 @return flag indicating a modified status (boolean) |
242 @return flag indicating a modified status (boolean) |
234 """ |
243 """ |
235 return self.__dirty |
244 return self.__dirty |
236 |
245 |
237 def setDirty(self, dirty, setCleanState=False): |
246 def setDirty(self, dirty, setCleanState=False): |
238 """ |
247 """ |
239 Public slot to set the dirty flag. |
248 Public slot to set the dirty flag. |
240 |
249 |
241 @param dirty flag indicating the new modification status (boolean) |
250 @param dirty flag indicating the new modification status (boolean) |
242 @param setCleanState flag indicating to set the undo stack to clean |
251 @param setCleanState flag indicating to set the undo stack to clean |
243 (boolean) |
252 (boolean) |
244 """ |
253 """ |
245 self.__dirty = dirty |
254 self.__dirty = dirty |
246 self.imageChanged.emit(dirty) |
255 self.imageChanged.emit(dirty) |
247 |
256 |
248 if not dirty and setCleanState: |
257 if not dirty and setCleanState: |
249 self.__undoStack.setClean() |
258 self.__undoStack.setClean() |
250 |
259 |
251 def sizeHint(self): |
260 def sizeHint(self): |
252 """ |
261 """ |
253 Public method to report the size hint. |
262 Public method to report the size hint. |
254 |
263 |
255 @return size hint (QSize) |
264 @return size hint (QSize) |
256 """ |
265 """ |
257 size = self.__zoom * self.__image.size() |
266 size = self.__zoom * self.__image.size() |
258 if self.__zoom >= 3 and self.__gridEnabled: |
267 if self.__zoom >= 3 and self.__gridEnabled: |
259 size += QSize(1, 1) |
268 size += QSize(1, 1) |
260 return size |
269 return size |
261 |
270 |
262 def setPenColor(self, newColor): |
271 def setPenColor(self, newColor): |
263 """ |
272 """ |
264 Public method to set the drawing color. |
273 Public method to set the drawing color. |
265 |
274 |
266 @param newColor reference to the new color (QColor) |
275 @param newColor reference to the new color (QColor) |
267 """ |
276 """ |
268 self.__curColor = QColor(newColor) |
277 self.__curColor = QColor(newColor) |
269 self.colorChanged.emit(QColor(newColor)) |
278 self.colorChanged.emit(QColor(newColor)) |
270 |
279 |
271 def penColor(self): |
280 def penColor(self): |
272 """ |
281 """ |
273 Public method to get the current drawing color. |
282 Public method to get the current drawing color. |
274 |
283 |
275 @return current drawing color (QColor) |
284 @return current drawing color (QColor) |
276 """ |
285 """ |
277 return QColor(self.__curColor) |
286 return QColor(self.__curColor) |
278 |
287 |
279 def setCompositingMode(self, mode): |
288 def setCompositingMode(self, mode): |
280 """ |
289 """ |
281 Public method to set the compositing mode. |
290 Public method to set the compositing mode. |
282 |
291 |
283 @param mode compositing mode to set (QPainter.CompositionMode) |
292 @param mode compositing mode to set (QPainter.CompositionMode) |
284 """ |
293 """ |
285 self.__compositingMode = mode |
294 self.__compositingMode = mode |
286 |
295 |
287 def compositingMode(self): |
296 def compositingMode(self): |
288 """ |
297 """ |
289 Public method to get the compositing mode. |
298 Public method to get the compositing mode. |
290 |
299 |
291 @return compositing mode (QPainter.CompositionMode) |
300 @return compositing mode (QPainter.CompositionMode) |
292 """ |
301 """ |
293 return self.__compositingMode |
302 return self.__compositingMode |
294 |
303 |
295 def setTool(self, tool): |
304 def setTool(self, tool): |
296 """ |
305 """ |
297 Public method to set the current drawing tool. |
306 Public method to set the current drawing tool. |
298 |
307 |
299 @param tool drawing tool to be used |
308 @param tool drawing tool to be used |
300 @type IconEditorTool |
309 @type IconEditorTool |
301 """ |
310 """ |
302 self.__curTool = tool |
311 self.__curTool = tool |
303 self.__lastPos = (-1, -1) |
312 self.__lastPos = (-1, -1) |
304 |
313 |
305 if self.__curTool in [ |
314 if self.__curTool in [ |
306 IconEditorTool.SELECT_RECTANGLE, IconEditorTool.SELECT_CIRCLE |
315 IconEditorTool.SELECT_RECTANGLE, |
|
316 IconEditorTool.SELECT_CIRCLE, |
307 ]: |
317 ]: |
308 self.__selecting = True |
318 self.__selecting = True |
309 else: |
319 else: |
310 self.__selecting = False |
320 self.__selecting = False |
311 |
321 |
312 if self.__curTool in [ |
322 if self.__curTool in [ |
313 IconEditorTool.SELECT_RECTANGLE, IconEditorTool.SELECT_CIRCLE, |
323 IconEditorTool.SELECT_RECTANGLE, |
|
324 IconEditorTool.SELECT_CIRCLE, |
314 IconEditorTool.LINE, |
325 IconEditorTool.LINE, |
315 IconEditorTool.RECTANGLE, IconEditorTool.FILLED_RECTANGLE, |
326 IconEditorTool.RECTANGLE, |
316 IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, |
327 IconEditorTool.FILLED_RECTANGLE, |
317 IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE |
328 IconEditorTool.CIRCLE, |
|
329 IconEditorTool.FILLED_CIRCLE, |
|
330 IconEditorTool.ELLIPSE, |
|
331 IconEditorTool.FILLED_ELLIPSE, |
318 ]: |
332 ]: |
319 self.setCursor(self.__aimCursor) |
333 self.setCursor(self.__aimCursor) |
320 elif self.__curTool == IconEditorTool.FILL: |
334 elif self.__curTool == IconEditorTool.FILL: |
321 self.setCursor(self.__fillCursor) |
335 self.setCursor(self.__fillCursor) |
322 elif self.__curTool == IconEditorTool.COLOR_PICKER: |
336 elif self.__curTool == IconEditorTool.COLOR_PICKER: |
325 self.setCursor(self.__paintCursor) |
339 self.setCursor(self.__paintCursor) |
326 elif self.__curTool == IconEditorTool.RUBBER: |
340 elif self.__curTool == IconEditorTool.RUBBER: |
327 self.setCursor(self.__rubberCursor) |
341 self.setCursor(self.__rubberCursor) |
328 else: |
342 else: |
329 self.setCursor(self.__normalCursor) |
343 self.setCursor(self.__normalCursor) |
330 |
344 |
331 def tool(self): |
345 def tool(self): |
332 """ |
346 """ |
333 Public method to get the current drawing tool. |
347 Public method to get the current drawing tool. |
334 |
348 |
335 @return current drawing tool |
349 @return current drawing tool |
336 @rtype IconEditorTool |
350 @rtype IconEditorTool |
337 """ |
351 """ |
338 return self.__curTool |
352 return self.__curTool |
339 |
353 |
340 def setIconImage(self, newImage, undoRedo=False, clearUndo=False): |
354 def setIconImage(self, newImage, undoRedo=False, clearUndo=False): |
341 """ |
355 """ |
342 Public method to set a new icon image. |
356 Public method to set a new icon image. |
343 |
357 |
344 @param newImage reference to the new image (QImage) |
358 @param newImage reference to the new image (QImage) |
345 @param undoRedo flag indicating an undo or redo operation (boolean) |
359 @param undoRedo flag indicating an undo or redo operation (boolean) |
346 @param clearUndo flag indicating to clear the undo stack (boolean) |
360 @param clearUndo flag indicating to clear the undo stack (boolean) |
347 """ |
361 """ |
348 if newImage != self.__image: |
362 if newImage != self.__image: |
349 self.__image = newImage.convertToFormat( |
363 self.__image = newImage.convertToFormat(QImage.Format.Format_ARGB32) |
350 QImage.Format.Format_ARGB32) |
|
351 self.update() |
364 self.update() |
352 self.updateGeometry() |
365 self.updateGeometry() |
353 self.resize(self.sizeHint()) |
366 self.resize(self.sizeHint()) |
354 |
367 |
355 self.__markImage = QImage(self.__image) |
368 self.__markImage = QImage(self.__image) |
356 self.__markImage.fill(self.NoMarkColor.rgba()) |
369 self.__markImage.fill(self.NoMarkColor.rgba()) |
357 |
370 |
358 if undoRedo: |
371 if undoRedo: |
359 self.setDirty(not self.__undoStack.isClean()) |
372 self.setDirty(not self.__undoStack.isClean()) |
360 else: |
373 else: |
361 self.setDirty(False) |
374 self.setDirty(False) |
362 |
375 |
363 if clearUndo: |
376 if clearUndo: |
364 self.__undoStack.clear() |
377 self.__undoStack.clear() |
365 |
378 |
366 self.sizeChanged.emit(*self.iconSize()) |
379 self.sizeChanged.emit(*self.iconSize()) |
367 |
380 |
368 def iconImage(self): |
381 def iconImage(self): |
369 """ |
382 """ |
370 Public method to get a copy of the icon image. |
383 Public method to get a copy of the icon image. |
371 |
384 |
372 @return copy of the icon image (QImage) |
385 @return copy of the icon image (QImage) |
373 """ |
386 """ |
374 return QImage(self.__image) |
387 return QImage(self.__image) |
375 |
388 |
376 def iconSize(self): |
389 def iconSize(self): |
377 """ |
390 """ |
378 Public method to get the size of the icon. |
391 Public method to get the size of the icon. |
379 |
392 |
380 @return width and height of the image as a tuple (integer, integer) |
393 @return width and height of the image as a tuple (integer, integer) |
381 """ |
394 """ |
382 return self.__image.width(), self.__image.height() |
395 return self.__image.width(), self.__image.height() |
383 |
396 |
384 def setZoomFactor(self, newZoom): |
397 def setZoomFactor(self, newZoom): |
385 """ |
398 """ |
386 Public method to set the zoom factor in percent. |
399 Public method to set the zoom factor in percent. |
387 |
400 |
388 @param newZoom zoom factor (integer >= 100) |
401 @param newZoom zoom factor (integer >= 100) |
389 """ |
402 """ |
390 newZoom = max(100, newZoom) # must not be less than 100 |
403 newZoom = max(100, newZoom) # must not be less than 100 |
391 if newZoom != self.__zoom: |
404 if newZoom != self.__zoom: |
392 self.__zoom = newZoom // 100 |
405 self.__zoom = newZoom // 100 |
393 self.update() |
406 self.update() |
394 self.updateGeometry() |
407 self.updateGeometry() |
395 self.resize(self.sizeHint()) |
408 self.resize(self.sizeHint()) |
396 self.zoomChanged.emit(int(self.__zoom * 100)) |
409 self.zoomChanged.emit(int(self.__zoom * 100)) |
397 |
410 |
398 def zoomFactor(self): |
411 def zoomFactor(self): |
399 """ |
412 """ |
400 Public method to get the current zoom factor in percent. |
413 Public method to get the current zoom factor in percent. |
401 |
414 |
402 @return zoom factor (integer) |
415 @return zoom factor (integer) |
403 """ |
416 """ |
404 return self.__zoom * 100 |
417 return self.__zoom * 100 |
405 |
418 |
406 def setGridEnabled(self, enable): |
419 def setGridEnabled(self, enable): |
407 """ |
420 """ |
408 Public method to enable the display of grid lines. |
421 Public method to enable the display of grid lines. |
409 |
422 |
410 @param enable enabled status of the grid lines (boolean) |
423 @param enable enabled status of the grid lines (boolean) |
411 """ |
424 """ |
412 if enable != self.__gridEnabled: |
425 if enable != self.__gridEnabled: |
413 self.__gridEnabled = enable |
426 self.__gridEnabled = enable |
414 self.update() |
427 self.update() |
415 |
428 |
416 def isGridEnabled(self): |
429 def isGridEnabled(self): |
417 """ |
430 """ |
418 Public method to get the grid lines status. |
431 Public method to get the grid lines status. |
419 |
432 |
420 @return enabled status of the grid lines (boolean) |
433 @return enabled status of the grid lines (boolean) |
421 """ |
434 """ |
422 return self.__gridEnabled |
435 return self.__gridEnabled |
423 |
436 |
424 def paintEvent(self, evt): |
437 def paintEvent(self, evt): |
425 """ |
438 """ |
426 Protected method called to repaint some of the widget. |
439 Protected method called to repaint some of the widget. |
427 |
440 |
428 @param evt reference to the paint event object (QPaintEvent) |
441 @param evt reference to the paint event object (QPaintEvent) |
429 """ |
442 """ |
430 painter = QPainter(self) |
443 painter = QPainter(self) |
431 |
444 |
432 if self.__zoom >= 3 and self.__gridEnabled: |
445 if self.__zoom >= 3 and self.__gridEnabled: |
433 if ericApp().usesDarkPalette(): |
446 if ericApp().usesDarkPalette(): |
434 painter.setPen(self.palette().window().color()) |
447 painter.setPen(self.palette().window().color()) |
435 else: |
448 else: |
436 painter.setPen(self.palette().windowText().color()) |
449 painter.setPen(self.palette().windowText().color()) |
437 i = 0 |
450 i = 0 |
438 while i <= self.__image.width(): |
451 while i <= self.__image.width(): |
439 painter.drawLine( |
452 painter.drawLine( |
440 self.__zoom * i, 0, |
453 self.__zoom * i, |
441 self.__zoom * i, self.__zoom * self.__image.height()) |
454 0, |
|
455 self.__zoom * i, |
|
456 self.__zoom * self.__image.height(), |
|
457 ) |
442 i += 1 |
458 i += 1 |
443 j = 0 |
459 j = 0 |
444 while j <= self.__image.height(): |
460 while j <= self.__image.height(): |
445 painter.drawLine( |
461 painter.drawLine( |
446 0, self.__zoom * j, |
462 0, |
447 self.__zoom * self.__image.width(), self.__zoom * j) |
463 self.__zoom * j, |
|
464 self.__zoom * self.__image.width(), |
|
465 self.__zoom * j, |
|
466 ) |
448 j += 1 |
467 j += 1 |
449 |
468 |
450 col = QColor("#aaa") |
469 col = QColor("#aaa") |
451 painter.setPen(Qt.PenStyle.DashLine) |
470 painter.setPen(Qt.PenStyle.DashLine) |
452 for i in range(0, self.__image.width()): |
471 for i in range(0, self.__image.width()): |
453 for j in range(0, self.__image.height()): |
472 for j in range(0, self.__image.height()): |
454 rect = self.__pixelRect(i, j) |
473 rect = self.__pixelRect(i, j) |
455 if evt.region().intersects(rect): |
474 if evt.region().intersects(rect): |
456 color = QColor.fromRgba(self.__image.pixel(i, j)) |
475 color = QColor.fromRgba(self.__image.pixel(i, j)) |
457 painter.fillRect(rect, QBrush(Qt.GlobalColor.white)) |
476 painter.fillRect(rect, QBrush(Qt.GlobalColor.white)) |
458 painter.fillRect(QRect(rect.topLeft(), rect.center()), col) |
477 painter.fillRect(QRect(rect.topLeft(), rect.center()), col) |
459 painter.fillRect(QRect(rect.center(), rect.bottomRight()), |
478 painter.fillRect(QRect(rect.center(), rect.bottomRight()), col) |
460 col) |
|
461 painter.fillRect(rect, QBrush(color)) |
479 painter.fillRect(rect, QBrush(color)) |
462 |
480 |
463 if self.__isMarked(i, j): |
481 if self.__isMarked(i, j): |
464 painter.drawRect(rect.adjusted(0, 0, -1, -1)) |
482 painter.drawRect(rect.adjusted(0, 0, -1, -1)) |
465 |
483 |
466 painter.end() |
484 painter.end() |
467 |
485 |
468 def __pixelRect(self, i, j): |
486 def __pixelRect(self, i, j): |
469 """ |
487 """ |
470 Private method to determine the rectangle for a given pixel coordinate. |
488 Private method to determine the rectangle for a given pixel coordinate. |
471 |
489 |
472 @param i x-coordinate of the pixel in the image (integer) |
490 @param i x-coordinate of the pixel in the image (integer) |
473 @param j y-coordinate of the pixel in the image (integer) |
491 @param j y-coordinate of the pixel in the image (integer) |
474 @return rectangle for the given pixel coordinates (QRect) |
492 @return rectangle for the given pixel coordinates (QRect) |
475 """ |
493 """ |
476 if self.__zoom >= 3 and self.__gridEnabled: |
494 if self.__zoom >= 3 and self.__gridEnabled: |
477 return QRect(self.__zoom * i + 1, self.__zoom * j + 1, |
495 return QRect( |
478 self.__zoom - 1, self.__zoom - 1) |
496 self.__zoom * i + 1, |
|
497 self.__zoom * j + 1, |
|
498 self.__zoom - 1, |
|
499 self.__zoom - 1, |
|
500 ) |
479 else: |
501 else: |
480 return QRect(self.__zoom * i, self.__zoom * j, |
502 return QRect(self.__zoom * i, self.__zoom * j, self.__zoom, self.__zoom) |
481 self.__zoom, self.__zoom) |
503 |
482 |
|
483 def mousePressEvent(self, evt): |
504 def mousePressEvent(self, evt): |
484 """ |
505 """ |
485 Protected method to handle mouse button press events. |
506 Protected method to handle mouse button press events. |
486 |
507 |
487 @param evt reference to the mouse event object (QMouseEvent) |
508 @param evt reference to the mouse event object (QMouseEvent) |
488 """ |
509 """ |
489 if evt.button() == Qt.MouseButton.LeftButton: |
510 if evt.button() == Qt.MouseButton.LeftButton: |
490 if self.__isPasting: |
511 if self.__isPasting: |
491 self.__isPasting = False |
512 self.__isPasting = False |
492 self.editPaste(True) |
513 self.editPaste(True) |
493 self.__markImage.fill(self.NoMarkColor.rgba()) |
514 self.__markImage.fill(self.NoMarkColor.rgba()) |
494 self.update(self.__pasteRect) |
515 self.update(self.__pasteRect) |
495 self.__pasteRect = QRect() |
516 self.__pasteRect = QRect() |
496 return |
517 return |
497 |
518 |
498 if self.__curTool == IconEditorTool.PENCIL: |
519 if self.__curTool == IconEditorTool.PENCIL: |
499 cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], |
520 cmd = IconEditCommand( |
500 self.__image) |
521 self, self.__undoTexts[self.__curTool], self.__image |
|
522 ) |
501 self.__setImagePixel(evt.position().toPoint(), True) |
523 self.__setImagePixel(evt.position().toPoint(), True) |
502 self.setDirty(True) |
524 self.setDirty(True) |
503 self.__undoStack.push(cmd) |
525 self.__undoStack.push(cmd) |
504 self.__currentUndoCmd = cmd |
526 self.__currentUndoCmd = cmd |
505 elif self.__curTool == IconEditorTool.RUBBER: |
527 elif self.__curTool == IconEditorTool.RUBBER: |
506 cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], |
528 cmd = IconEditCommand( |
507 self.__image) |
529 self, self.__undoTexts[self.__curTool], self.__image |
|
530 ) |
508 self.__setImagePixel(evt.position().toPoint(), False) |
531 self.__setImagePixel(evt.position().toPoint(), False) |
509 self.setDirty(True) |
532 self.setDirty(True) |
510 self.__undoStack.push(cmd) |
533 self.__undoStack.push(cmd) |
511 self.__currentUndoCmd = cmd |
534 self.__currentUndoCmd = cmd |
512 elif self.__curTool == IconEditorTool.FILL: |
535 elif self.__curTool == IconEditorTool.FILL: |
513 i, j = self.__imageCoordinates(evt.position().toPoint()) |
536 i, j = self.__imageCoordinates(evt.position().toPoint()) |
514 col = QColor() |
537 col = QColor() |
515 col.setRgba(self.__image.pixel(i, j)) |
538 col.setRgba(self.__image.pixel(i, j)) |
516 cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], |
539 cmd = IconEditCommand( |
517 self.__image) |
540 self, self.__undoTexts[self.__curTool], self.__image |
|
541 ) |
518 self.__drawFlood(i, j, col) |
542 self.__drawFlood(i, j, col) |
519 self.setDirty(True) |
543 self.setDirty(True) |
520 self.__undoStack.push(cmd) |
544 self.__undoStack.push(cmd) |
521 cmd.setAfterImage(self.__image) |
545 cmd.setAfterImage(self.__image) |
522 elif self.__curTool == IconEditorTool.COLOR_PICKER: |
546 elif self.__curTool == IconEditorTool.COLOR_PICKER: |
526 self.setPenColor(col) |
550 self.setPenColor(col) |
527 else: |
551 else: |
528 self.__unMark() |
552 self.__unMark() |
529 self.__startPos = evt.position().toPoint() |
553 self.__startPos = evt.position().toPoint() |
530 self.__endPos = evt.position().toPoint() |
554 self.__endPos = evt.position().toPoint() |
531 |
555 |
532 def mouseMoveEvent(self, evt): |
556 def mouseMoveEvent(self, evt): |
533 """ |
557 """ |
534 Protected method to handle mouse move events. |
558 Protected method to handle mouse move events. |
535 |
559 |
536 @param evt reference to the mouse event object (QMouseEvent) |
560 @param evt reference to the mouse event object (QMouseEvent) |
537 """ |
561 """ |
538 self.positionChanged.emit( |
562 self.positionChanged.emit(*self.__imageCoordinates(evt.position().toPoint())) |
539 *self.__imageCoordinates(evt.position().toPoint())) |
563 |
540 |
564 if self.__isPasting and not (evt.buttons() & Qt.MouseButton.LeftButton): |
541 if ( |
|
542 self.__isPasting and |
|
543 not (evt.buttons() & Qt.MouseButton.LeftButton) |
|
544 ): |
|
545 self.__drawPasteRect(evt.position().toPoint()) |
565 self.__drawPasteRect(evt.position().toPoint()) |
546 return |
566 return |
547 |
567 |
548 if evt.buttons() & Qt.MouseButton.LeftButton: |
568 if evt.buttons() & Qt.MouseButton.LeftButton: |
549 if self.__curTool == IconEditorTool.PENCIL: |
569 if self.__curTool == IconEditorTool.PENCIL: |
550 self.__setImagePixel(evt.position().toPoint(), True) |
570 self.__setImagePixel(evt.position().toPoint(), True) |
551 self.setDirty(True) |
571 self.setDirty(True) |
552 elif self.__curTool == IconEditorTool.RUBBER: |
572 elif self.__curTool == IconEditorTool.RUBBER: |
553 self.__setImagePixel(evt.position().toPoint(), False) |
573 self.__setImagePixel(evt.position().toPoint(), False) |
554 self.setDirty(True) |
574 self.setDirty(True) |
555 elif self.__curTool in [IconEditorTool.FILL, |
575 elif self.__curTool in [IconEditorTool.FILL, IconEditorTool.COLOR_PICKER]: |
556 IconEditorTool.COLOR_PICKER]: |
576 pass # do nothing |
557 pass # do nothing |
|
558 else: |
577 else: |
559 self.__drawTool(evt.position().toPoint(), True) |
578 self.__drawTool(evt.position().toPoint(), True) |
560 |
579 |
561 def mouseReleaseEvent(self, evt): |
580 def mouseReleaseEvent(self, evt): |
562 """ |
581 """ |
563 Protected method to handle mouse button release events. |
582 Protected method to handle mouse button release events. |
564 |
583 |
565 @param evt reference to the mouse event object (QMouseEvent) |
584 @param evt reference to the mouse event object (QMouseEvent) |
566 """ |
585 """ |
567 if evt.button() == Qt.MouseButton.LeftButton: |
586 if evt.button() == Qt.MouseButton.LeftButton: |
568 if ( |
587 if ( |
569 self.__curTool in [IconEditorTool.PENCIL, |
588 self.__curTool in [IconEditorTool.PENCIL, IconEditorTool.RUBBER] |
570 IconEditorTool.RUBBER] and |
589 and self.__currentUndoCmd |
571 self.__currentUndoCmd |
|
572 ): |
590 ): |
573 self.__currentUndoCmd.setAfterImage(self.__image) |
591 self.__currentUndoCmd.setAfterImage(self.__image) |
574 self.__currentUndoCmd = None |
592 self.__currentUndoCmd = None |
575 |
593 |
576 if self.__curTool not in [ |
594 if self.__curTool not in [ |
577 IconEditorTool.PENCIL, IconEditorTool.RUBBER, |
595 IconEditorTool.PENCIL, |
578 IconEditorTool.FILL, IconEditorTool.COLOR_PICKER, |
596 IconEditorTool.RUBBER, |
579 IconEditorTool.SELECT_RECTANGLE, IconEditorTool.SELECT_CIRCLE |
597 IconEditorTool.FILL, |
|
598 IconEditorTool.COLOR_PICKER, |
|
599 IconEditorTool.SELECT_RECTANGLE, |
|
600 IconEditorTool.SELECT_CIRCLE, |
580 ]: |
601 ]: |
581 cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], |
602 cmd = IconEditCommand( |
582 self.__image) |
603 self, self.__undoTexts[self.__curTool], self.__image |
|
604 ) |
583 if self.__drawTool(evt.position().toPoint(), False): |
605 if self.__drawTool(evt.position().toPoint(), False): |
584 self.__undoStack.push(cmd) |
606 self.__undoStack.push(cmd) |
585 cmd.setAfterImage(self.__image) |
607 cmd.setAfterImage(self.__image) |
586 self.setDirty(True) |
608 self.setDirty(True) |
587 |
609 |
588 def __setImagePixel(self, pos, opaque): |
610 def __setImagePixel(self, pos, opaque): |
589 """ |
611 """ |
590 Private slot to set or erase a pixel. |
612 Private slot to set or erase a pixel. |
591 |
613 |
592 @param pos position of the pixel in the widget (QPoint) |
614 @param pos position of the pixel in the widget (QPoint) |
593 @param opaque flag indicating a set operation (boolean) |
615 @param opaque flag indicating a set operation (boolean) |
594 """ |
616 """ |
595 i, j = self.__imageCoordinates(pos) |
617 i, j = self.__imageCoordinates(pos) |
596 |
618 |
597 if self.__image.rect().contains(i, j) and (i, j) != self.__lastPos: |
619 if self.__image.rect().contains(i, j) and (i, j) != self.__lastPos: |
598 if opaque: |
620 if opaque: |
599 painter = QPainter(self.__image) |
621 painter = QPainter(self.__image) |
600 painter.setPen(self.penColor()) |
622 painter.setPen(self.penColor()) |
601 painter.setCompositionMode(self.__compositingMode) |
623 painter.setCompositionMode(self.__compositingMode) |
602 painter.drawPoint(i, j) |
624 painter.drawPoint(i, j) |
603 else: |
625 else: |
604 self.__image.setPixel(i, j, qRgba(0, 0, 0, 0)) |
626 self.__image.setPixel(i, j, qRgba(0, 0, 0, 0)) |
605 self.__lastPos = (i, j) |
627 self.__lastPos = (i, j) |
606 |
628 |
607 self.update(self.__pixelRect(i, j)) |
629 self.update(self.__pixelRect(i, j)) |
608 |
630 |
609 def __imageCoordinates(self, pos): |
631 def __imageCoordinates(self, pos): |
610 """ |
632 """ |
611 Private method to convert from widget to image coordinates. |
633 Private method to convert from widget to image coordinates. |
612 |
634 |
613 @param pos widget coordinate (QPoint) |
635 @param pos widget coordinate (QPoint) |
614 @return tuple with the image coordinates (tuple of two integers) |
636 @return tuple with the image coordinates (tuple of two integers) |
615 """ |
637 """ |
616 i = pos.x() // self.__zoom |
638 i = pos.x() // self.__zoom |
617 j = pos.y() // self.__zoom |
639 j = pos.y() // self.__zoom |
618 return i, j |
640 return i, j |
619 |
641 |
620 def __drawPasteRect(self, pos): |
642 def __drawPasteRect(self, pos): |
621 """ |
643 """ |
622 Private slot to draw a rectangle for signaling a paste operation. |
644 Private slot to draw a rectangle for signaling a paste operation. |
623 |
645 |
624 @param pos widget position of the paste rectangle (QPoint) |
646 @param pos widget position of the paste rectangle (QPoint) |
625 """ |
647 """ |
626 self.__markImage.fill(self.NoMarkColor.rgba()) |
648 self.__markImage.fill(self.NoMarkColor.rgba()) |
627 if self.__pasteRect.isValid(): |
649 if self.__pasteRect.isValid(): |
628 self.__updateImageRect( |
650 self.__updateImageRect( |
629 self.__pasteRect.topLeft(), |
651 self.__pasteRect.topLeft(), |
630 self.__pasteRect.bottomRight() + QPoint(1, 1)) |
652 self.__pasteRect.bottomRight() + QPoint(1, 1), |
631 |
653 ) |
|
654 |
632 x, y = self.__imageCoordinates(pos) |
655 x, y = self.__imageCoordinates(pos) |
633 isize = self.__image.size() |
656 isize = self.__image.size() |
634 sx = ( |
657 sx = ( |
635 self.__clipboardSize.width() |
658 self.__clipboardSize.width() |
636 if x + self.__clipboardSize.width() <= isize.width() else |
659 if x + self.__clipboardSize.width() <= isize.width() |
637 isize.width() - x |
660 else isize.width() - x |
638 ) |
661 ) |
639 sy = ( |
662 sy = ( |
640 self.__clipboardSize.height() |
663 self.__clipboardSize.height() |
641 if y + self.__clipboardSize.height() <= isize.height() else |
664 if y + self.__clipboardSize.height() <= isize.height() |
642 isize.height() - y |
665 else isize.height() - y |
643 ) |
666 ) |
644 |
667 |
645 self.__pasteRect = QRect(QPoint(x, y), QSize(sx - 1, sy - 1)) |
668 self.__pasteRect = QRect(QPoint(x, y), QSize(sx - 1, sy - 1)) |
646 |
669 |
647 painter = QPainter(self.__markImage) |
670 painter = QPainter(self.__markImage) |
648 painter.setPen(self.MarkColor) |
671 painter.setPen(self.MarkColor) |
649 painter.drawRect(self.__pasteRect) |
672 painter.drawRect(self.__pasteRect) |
650 painter.end() |
673 painter.end() |
651 |
674 |
652 self.__updateImageRect(self.__pasteRect.topLeft(), |
675 self.__updateImageRect( |
653 self.__pasteRect.bottomRight() + QPoint(1, 1)) |
676 self.__pasteRect.topLeft(), self.__pasteRect.bottomRight() + QPoint(1, 1) |
654 |
677 ) |
|
678 |
655 def __drawTool(self, pos, mark): |
679 def __drawTool(self, pos, mark): |
656 """ |
680 """ |
657 Private method to perform a draw operation depending of the current |
681 Private method to perform a draw operation depending of the current |
658 tool. |
682 tool. |
659 |
683 |
660 @param pos widget coordinate to perform the draw operation at (QPoint) |
684 @param pos widget coordinate to perform the draw operation at (QPoint) |
661 @param mark flag indicating a mark operation (boolean) |
685 @param mark flag indicating a mark operation (boolean) |
662 @return flag indicating a successful draw (boolean) |
686 @return flag indicating a successful draw (boolean) |
663 """ |
687 """ |
664 self.__unMark() |
688 self.__unMark() |
665 |
689 |
666 if mark: |
690 if mark: |
667 self.__endPos = QPoint(pos) |
691 self.__endPos = QPoint(pos) |
668 drawColor = self.MarkColor |
692 drawColor = self.MarkColor |
669 img = self.__markImage |
693 img = self.__markImage |
670 else: |
694 else: |
671 drawColor = self.penColor() |
695 drawColor = self.penColor() |
672 img = self.__image |
696 img = self.__image |
673 |
697 |
674 start = QPoint(*self.__imageCoordinates(self.__startPos)) |
698 start = QPoint(*self.__imageCoordinates(self.__startPos)) |
675 end = QPoint(*self.__imageCoordinates(pos)) |
699 end = QPoint(*self.__imageCoordinates(pos)) |
676 |
700 |
677 painter = QPainter(img) |
701 painter = QPainter(img) |
678 painter.setPen(drawColor) |
702 painter.setPen(drawColor) |
679 painter.setCompositionMode(self.__compositingMode) |
703 painter.setCompositionMode(self.__compositingMode) |
680 |
704 |
681 if self.__curTool == IconEditorTool.LINE: |
705 if self.__curTool == IconEditorTool.LINE: |
682 painter.drawLine(start, end) |
706 painter.drawLine(start, end) |
683 |
707 |
684 elif self.__curTool in [ |
708 elif self.__curTool in [ |
685 IconEditorTool.RECTANGLE, IconEditorTool.FILLED_RECTANGLE, |
709 IconEditorTool.RECTANGLE, |
686 IconEditorTool.SELECT_RECTANGLE |
710 IconEditorTool.FILLED_RECTANGLE, |
|
711 IconEditorTool.SELECT_RECTANGLE, |
687 ]: |
712 ]: |
688 left = min(start.x(), end.x()) |
713 left = min(start.x(), end.x()) |
689 top = min(start.y(), end.y()) |
714 top = min(start.y(), end.y()) |
690 right = max(start.x(), end.x()) |
715 right = max(start.x(), end.x()) |
691 bottom = max(start.y(), end.y()) |
716 bottom = max(start.y(), end.y()) |
695 for y in range(top, bottom + 1): |
720 for y in range(top, bottom + 1): |
696 painter.drawLine(left, y, right, y) |
721 painter.drawLine(left, y, right, y) |
697 else: |
722 else: |
698 painter.drawRect(left, top, right - left, bottom - top) |
723 painter.drawRect(left, top, right - left, bottom - top) |
699 if self.__selecting: |
724 if self.__selecting: |
700 self.__selRect = QRect( |
725 self.__selRect = QRect(left, top, right - left + 1, bottom - top + 1) |
701 left, top, right - left + 1, bottom - top + 1) |
|
702 self.__selectionAvailable = True |
726 self.__selectionAvailable = True |
703 self.selectionAvailable.emit(True) |
727 self.selectionAvailable.emit(True) |
704 |
728 |
705 elif self.__curTool in [ |
729 elif self.__curTool in [ |
706 IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, |
730 IconEditorTool.CIRCLE, |
707 IconEditorTool.SELECT_CIRCLE |
731 IconEditorTool.FILLED_CIRCLE, |
|
732 IconEditorTool.SELECT_CIRCLE, |
708 ]: |
733 ]: |
709 deltaX = abs(start.x() - end.x()) |
734 deltaX = abs(start.x() - end.x()) |
710 deltaY = abs(start.y() - end.y()) |
735 deltaY = abs(start.y() - end.y()) |
711 r = max(deltaX, deltaY) |
736 r = max(deltaX, deltaY) |
712 if self.__curTool in [ |
737 if self.__curTool in [ |
713 IconEditorTool.FILLED_CIRCLE, IconEditorTool.SELECT_CIRCLE |
738 IconEditorTool.FILLED_CIRCLE, |
|
739 IconEditorTool.SELECT_CIRCLE, |
714 ]: |
740 ]: |
715 painter.setBrush(QBrush(drawColor)) |
741 painter.setBrush(QBrush(drawColor)) |
716 painter.drawEllipse(start, r, r) |
742 painter.drawEllipse(start, r, r) |
717 if self.__selecting: |
743 if self.__selecting: |
718 self.__selRect = QRect(start.x() - r, start.y() - r, |
744 self.__selRect = QRect( |
719 2 * r + 1, 2 * r + 1) |
745 start.x() - r, start.y() - r, 2 * r + 1, 2 * r + 1 |
|
746 ) |
720 self.__selectionAvailable = True |
747 self.__selectionAvailable = True |
721 self.selectionAvailable.emit(True) |
748 self.selectionAvailable.emit(True) |
722 |
749 |
723 elif self.__curTool in [ |
750 elif self.__curTool in [IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE]: |
724 IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE |
|
725 ]: |
|
726 r1 = abs(start.x() - end.x()) |
751 r1 = abs(start.x() - end.x()) |
727 r2 = abs(start.y() - end.y()) |
752 r2 = abs(start.y() - end.y()) |
728 if r1 == 0 or r2 == 0: |
753 if r1 == 0 or r2 == 0: |
729 return False |
754 return False |
730 if self.__curTool == IconEditorTool.FILLED_ELLIPSE: |
755 if self.__curTool == IconEditorTool.FILLED_ELLIPSE: |
731 painter.setBrush(QBrush(drawColor)) |
756 painter.setBrush(QBrush(drawColor)) |
732 painter.drawEllipse(start, r1, r2) |
757 painter.drawEllipse(start, r1, r2) |
733 |
758 |
734 painter.end() |
759 painter.end() |
735 |
760 |
736 if self.__curTool in [ |
761 if self.__curTool in [ |
737 IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, |
762 IconEditorTool.CIRCLE, |
738 IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE |
763 IconEditorTool.FILLED_CIRCLE, |
|
764 IconEditorTool.ELLIPSE, |
|
765 IconEditorTool.FILLED_ELLIPSE, |
739 ]: |
766 ]: |
740 self.update() |
767 self.update() |
741 else: |
768 else: |
742 self.__updateRect(self.__startPos, pos) |
769 self.__updateRect(self.__startPos, pos) |
743 |
770 |
744 return True |
771 return True |
745 |
772 |
746 def __drawFlood(self, i, j, oldColor, doUpdate=True): |
773 def __drawFlood(self, i, j, oldColor, doUpdate=True): |
747 """ |
774 """ |
748 Private method to perform a flood fill operation. |
775 Private method to perform a flood fill operation. |
749 |
776 |
750 @param i x-value in image coordinates (integer) |
777 @param i x-value in image coordinates (integer) |
751 @param j y-value in image coordinates (integer) |
778 @param j y-value in image coordinates (integer) |
752 @param oldColor reference to the color at position i, j (QColor) |
779 @param oldColor reference to the color at position i, j (QColor) |
753 @param doUpdate flag indicating an update is requested (boolean) |
780 @param doUpdate flag indicating an update is requested (boolean) |
754 (used for speed optimizations) |
781 (used for speed optimizations) |
755 """ |
782 """ |
756 if ( |
783 if ( |
757 not self.__image.rect().contains(i, j) or |
784 not self.__image.rect().contains(i, j) |
758 self.__image.pixel(i, j) != oldColor.rgba() or |
785 or self.__image.pixel(i, j) != oldColor.rgba() |
759 self.__image.pixel(i, j) == self.penColor().rgba() |
786 or self.__image.pixel(i, j) == self.penColor().rgba() |
760 ): |
787 ): |
761 return |
788 return |
762 |
789 |
763 self.__image.setPixel(i, j, self.penColor().rgba()) |
790 self.__image.setPixel(i, j, self.penColor().rgba()) |
764 |
791 |
765 self.__drawFlood(i, j - 1, oldColor, False) |
792 self.__drawFlood(i, j - 1, oldColor, False) |
766 self.__drawFlood(i, j + 1, oldColor, False) |
793 self.__drawFlood(i, j + 1, oldColor, False) |
767 self.__drawFlood(i - 1, j, oldColor, False) |
794 self.__drawFlood(i - 1, j, oldColor, False) |
768 self.__drawFlood(i + 1, j, oldColor, False) |
795 self.__drawFlood(i + 1, j, oldColor, False) |
769 |
796 |
770 if doUpdate: |
797 if doUpdate: |
771 self.update() |
798 self.update() |
772 |
799 |
773 def __updateRect(self, pos1, pos2): |
800 def __updateRect(self, pos1, pos2): |
774 """ |
801 """ |
775 Private slot to update parts of the widget. |
802 Private slot to update parts of the widget. |
776 |
803 |
777 @param pos1 top, left position for the update in widget coordinates |
804 @param pos1 top, left position for the update in widget coordinates |
778 (QPoint) |
805 (QPoint) |
779 @param pos2 bottom, right position for the update in widget |
806 @param pos2 bottom, right position for the update in widget |
780 coordinates (QPoint) |
807 coordinates (QPoint) |
781 """ |
808 """ |
782 self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)), |
809 self.__updateImageRect( |
783 QPoint(*self.__imageCoordinates(pos2))) |
810 QPoint(*self.__imageCoordinates(pos1)), |
784 |
811 QPoint(*self.__imageCoordinates(pos2)), |
|
812 ) |
|
813 |
785 def __updateImageRect(self, ipos1, ipos2): |
814 def __updateImageRect(self, ipos1, ipos2): |
786 """ |
815 """ |
787 Private slot to update parts of the widget. |
816 Private slot to update parts of the widget. |
788 |
817 |
789 @param ipos1 top, left position for the update in image coordinates |
818 @param ipos1 top, left position for the update in image coordinates |
790 (QPoint) |
819 (QPoint) |
791 @param ipos2 bottom, right position for the update in image |
820 @param ipos2 bottom, right position for the update in image |
792 coordinates (QPoint) |
821 coordinates (QPoint) |
793 """ |
822 """ |
794 r1 = self.__pixelRect(ipos1.x(), ipos1.y()) |
823 r1 = self.__pixelRect(ipos1.x(), ipos1.y()) |
795 r2 = self.__pixelRect(ipos2.x(), ipos2.y()) |
824 r2 = self.__pixelRect(ipos2.x(), ipos2.y()) |
796 |
825 |
797 left = min(r1.x(), r2.x()) |
826 left = min(r1.x(), r2.x()) |
798 top = min(r1.y(), r2.y()) |
827 top = min(r1.y(), r2.y()) |
799 right = max(r1.x() + r1.width(), r2.x() + r2.width()) |
828 right = max(r1.x() + r1.width(), r2.x() + r2.width()) |
800 bottom = max(r1.y() + r1.height(), r2.y() + r2.height()) |
829 bottom = max(r1.y() + r1.height(), r2.y() + r2.height()) |
801 self.update(left, top, right - left + 1, bottom - top + 1) |
830 self.update(left, top, right - left + 1, bottom - top + 1) |
802 |
831 |
803 def __unMark(self): |
832 def __unMark(self): |
804 """ |
833 """ |
805 Private slot to remove the mark indicator. |
834 Private slot to remove the mark indicator. |
806 """ |
835 """ |
807 self.__markImage.fill(self.NoMarkColor.rgba()) |
836 self.__markImage.fill(self.NoMarkColor.rgba()) |
808 if self.__curTool in [ |
837 if self.__curTool in [ |
809 IconEditorTool.CIRCLE, IconEditorTool.FILLED_CIRCLE, |
838 IconEditorTool.CIRCLE, |
810 IconEditorTool.ELLIPSE, IconEditorTool.FILLED_ELLIPSE, |
839 IconEditorTool.FILLED_CIRCLE, |
811 IconEditorTool.SELECT_CIRCLE |
840 IconEditorTool.ELLIPSE, |
|
841 IconEditorTool.FILLED_ELLIPSE, |
|
842 IconEditorTool.SELECT_CIRCLE, |
812 ]: |
843 ]: |
813 self.update() |
844 self.update() |
814 else: |
845 else: |
815 self.__updateRect(self.__startPos, self.__endPos) |
846 self.__updateRect(self.__startPos, self.__endPos) |
816 |
847 |
817 if self.__selecting: |
848 if self.__selecting: |
818 self.__selRect = QRect() |
849 self.__selRect = QRect() |
819 self.__selectionAvailable = False |
850 self.__selectionAvailable = False |
820 self.selectionAvailable.emit(False) |
851 self.selectionAvailable.emit(False) |
821 |
852 |
822 def __isMarked(self, i, j): |
853 def __isMarked(self, i, j): |
823 """ |
854 """ |
824 Private method to check, if a pixel is marked. |
855 Private method to check, if a pixel is marked. |
825 |
856 |
826 @param i x-value in image coordinates (integer) |
857 @param i x-value in image coordinates (integer) |
827 @param j y-value in image coordinates (integer) |
858 @param j y-value in image coordinates (integer) |
828 @return flag indicating a marked pixel (boolean) |
859 @return flag indicating a marked pixel (boolean) |
829 """ |
860 """ |
830 return self.__markImage.pixel(i, j) == self.MarkColor.rgba() |
861 return self.__markImage.pixel(i, j) == self.MarkColor.rgba() |
831 |
862 |
832 def __updatePreviewPixmap(self): |
863 def __updatePreviewPixmap(self): |
833 """ |
864 """ |
834 Private slot to generate and signal an updated preview pixmap. |
865 Private slot to generate and signal an updated preview pixmap. |
835 """ |
866 """ |
836 p = QPixmap.fromImage(self.__image) |
867 p = QPixmap.fromImage(self.__image) |
837 self.previewChanged.emit(p) |
868 self.previewChanged.emit(p) |
838 |
869 |
839 def previewPixmap(self): |
870 def previewPixmap(self): |
840 """ |
871 """ |
841 Public method to generate a preview pixmap. |
872 Public method to generate a preview pixmap. |
842 |
873 |
843 @return preview pixmap (QPixmap) |
874 @return preview pixmap (QPixmap) |
844 """ |
875 """ |
845 p = QPixmap.fromImage(self.__image) |
876 p = QPixmap.fromImage(self.__image) |
846 return p |
877 return p |
847 |
878 |
848 def __checkClipboard(self): |
879 def __checkClipboard(self): |
849 """ |
880 """ |
850 Private slot to check, if the clipboard contains a valid image, and |
881 Private slot to check, if the clipboard contains a valid image, and |
851 signal the result. |
882 signal the result. |
852 """ |
883 """ |
853 ok = self.__clipboardImage()[1] |
884 ok = self.__clipboardImage()[1] |
854 self.__clipboardImageAvailable = ok |
885 self.__clipboardImageAvailable = ok |
855 self.clipboardImageAvailable.emit(ok) |
886 self.clipboardImageAvailable.emit(ok) |
856 |
887 |
857 def canPaste(self): |
888 def canPaste(self): |
858 """ |
889 """ |
859 Public slot to check the availability of the paste operation. |
890 Public slot to check the availability of the paste operation. |
860 |
891 |
861 @return flag indicating availability of paste (boolean) |
892 @return flag indicating availability of paste (boolean) |
862 """ |
893 """ |
863 return self.__clipboardImageAvailable |
894 return self.__clipboardImageAvailable |
864 |
895 |
865 def __clipboardImage(self): |
896 def __clipboardImage(self): |
866 """ |
897 """ |
867 Private method to get an image from the clipboard. |
898 Private method to get an image from the clipboard. |
868 |
899 |
869 @return tuple with the image (QImage) and a flag indicating a |
900 @return tuple with the image (QImage) and a flag indicating a |
870 valid image (boolean) |
901 valid image (boolean) |
871 """ |
902 """ |
872 img = QApplication.clipboard().image() |
903 img = QApplication.clipboard().image() |
873 ok = not img.isNull() |
904 ok = not img.isNull() |
874 if ok: |
905 if ok: |
875 img = img.convertToFormat(QImage.Format.Format_ARGB32) |
906 img = img.convertToFormat(QImage.Format.Format_ARGB32) |
876 |
907 |
877 return img, ok |
908 return img, ok |
878 |
909 |
879 def __getSelectionImage(self, cut): |
910 def __getSelectionImage(self, cut): |
880 """ |
911 """ |
881 Private method to get an image from the selection. |
912 Private method to get an image from the selection. |
882 |
913 |
883 @param cut flag indicating to cut the selection (boolean) |
914 @param cut flag indicating to cut the selection (boolean) |
884 @return image of the selection (QImage) |
915 @return image of the selection (QImage) |
885 """ |
916 """ |
886 if cut: |
917 if cut: |
887 cmd = IconEditCommand(self, self.tr("Cut Selection"), |
918 cmd = IconEditCommand(self, self.tr("Cut Selection"), self.__image) |
888 self.__image) |
919 |
889 |
|
890 img = QImage(self.__selRect.size(), QImage.Format.Format_ARGB32) |
920 img = QImage(self.__selRect.size(), QImage.Format.Format_ARGB32) |
891 img.fill(Qt.GlobalColor.transparent) |
921 img.fill(Qt.GlobalColor.transparent) |
892 for i in range(0, self.__selRect.width()): |
922 for i in range(0, self.__selRect.width()): |
893 for j in range(0, self.__selRect.height()): |
923 for j in range(0, self.__selRect.height()): |
894 if ( |
924 if self.__image.rect().contains( |
895 self.__image.rect().contains( |
925 self.__selRect.x() + i, self.__selRect.y() + j |
896 self.__selRect.x() + i, self.__selRect.y() + j) and |
926 ) and self.__isMarked(self.__selRect.x() + i, self.__selRect.y() + j): |
897 self.__isMarked(self.__selRect.x() + i, |
927 img.setPixel( |
898 self.__selRect.y() + j) |
928 i, |
899 ): |
929 j, |
900 img.setPixel(i, j, self.__image.pixel( |
930 self.__image.pixel( |
901 self.__selRect.x() + i, self.__selRect.y() + j)) |
931 self.__selRect.x() + i, self.__selRect.y() + j |
|
932 ), |
|
933 ) |
902 if cut: |
934 if cut: |
903 self.__image.setPixel(self.__selRect.x() + i, |
935 self.__image.setPixel( |
904 self.__selRect.y() + j, |
936 self.__selRect.x() + i, |
905 Qt.GlobalColor.transparent) |
937 self.__selRect.y() + j, |
906 |
938 Qt.GlobalColor.transparent, |
|
939 ) |
|
940 |
907 if cut: |
941 if cut: |
908 self.__undoStack.push(cmd) |
942 self.__undoStack.push(cmd) |
909 cmd.setAfterImage(self.__image) |
943 cmd.setAfterImage(self.__image) |
910 |
944 |
911 self.__unMark() |
945 self.__unMark() |
912 |
946 |
913 if cut: |
947 if cut: |
914 self.update(self.__selRect) |
948 self.update(self.__selRect) |
915 |
949 |
916 return img |
950 return img |
917 |
951 |
918 def editCopy(self): |
952 def editCopy(self): |
919 """ |
953 """ |
920 Public slot to copy the selection. |
954 Public slot to copy the selection. |
921 """ |
955 """ |
922 if self.__selRect.isValid(): |
956 if self.__selRect.isValid(): |
923 img = self.__getSelectionImage(False) |
957 img = self.__getSelectionImage(False) |
924 QApplication.clipboard().setImage(img) |
958 QApplication.clipboard().setImage(img) |
925 |
959 |
926 def editCut(self): |
960 def editCut(self): |
927 """ |
961 """ |
928 Public slot to cut the selection. |
962 Public slot to cut the selection. |
929 """ |
963 """ |
930 if self.__selRect.isValid(): |
964 if self.__selRect.isValid(): |
931 img = self.__getSelectionImage(True) |
965 img = self.__getSelectionImage(True) |
932 QApplication.clipboard().setImage(img) |
966 QApplication.clipboard().setImage(img) |
933 |
967 |
934 @pyqtSlot() |
968 @pyqtSlot() |
935 def editPaste(self, pasting=False): |
969 def editPaste(self, pasting=False): |
936 """ |
970 """ |
937 Public slot to paste an image from the clipboard. |
971 Public slot to paste an image from the clipboard. |
938 |
972 |
939 @param pasting flag indicating part two of the paste operation |
973 @param pasting flag indicating part two of the paste operation |
940 (boolean) |
974 (boolean) |
941 """ |
975 """ |
942 img, ok = self.__clipboardImage() |
976 img, ok = self.__clipboardImage() |
943 if ok: |
977 if ok: |
944 if ( |
978 if ( |
945 img.width() > self.__image.width() or |
979 img.width() > self.__image.width() |
946 img.height() > self.__image.height() |
980 or img.height() > self.__image.height() |
947 ): |
981 ): |
948 res = EricMessageBox.yesNo( |
982 res = EricMessageBox.yesNo( |
949 self, |
983 self, |
950 self.tr("Paste"), |
984 self.tr("Paste"), |
951 self.tr( |
985 self.tr( |
952 """<p>The clipboard image is larger than the""" |
986 """<p>The clipboard image is larger than the""" |
953 """ current image.<br/>Paste as new image?</p>""")) |
987 """ current image.<br/>Paste as new image?</p>""" |
|
988 ), |
|
989 ) |
954 if res: |
990 if res: |
955 self.editPasteAsNew() |
991 self.editPasteAsNew() |
956 return |
992 return |
957 elif not pasting: |
993 elif not pasting: |
958 self.__isPasting = True |
994 self.__isPasting = True |
959 self.__clipboardSize = img.size() |
995 self.__clipboardSize = img.size() |
960 else: |
996 else: |
961 cmd = IconEditCommand(self, self.tr("Paste Clipboard"), |
997 cmd = IconEditCommand(self, self.tr("Paste Clipboard"), self.__image) |
962 self.__image) |
|
963 self.__markImage.fill(self.NoMarkColor.rgba()) |
998 self.__markImage.fill(self.NoMarkColor.rgba()) |
964 painter = QPainter(self.__image) |
999 painter = QPainter(self.__image) |
965 painter.setPen(self.penColor()) |
1000 painter.setPen(self.penColor()) |
966 painter.setCompositionMode(self.__compositingMode) |
1001 painter.setCompositionMode(self.__compositingMode) |
967 painter.drawImage( |
1002 painter.drawImage( |
968 self.__pasteRect.x(), self.__pasteRect.y(), img, 0, 0, |
1003 self.__pasteRect.x(), |
|
1004 self.__pasteRect.y(), |
|
1005 img, |
|
1006 0, |
|
1007 0, |
969 self.__pasteRect.width() + 1, |
1008 self.__pasteRect.width() + 1, |
970 self.__pasteRect.height() + 1) |
1009 self.__pasteRect.height() + 1, |
971 |
1010 ) |
|
1011 |
972 self.__undoStack.push(cmd) |
1012 self.__undoStack.push(cmd) |
973 cmd.setAfterImage(self.__image) |
1013 cmd.setAfterImage(self.__image) |
974 |
1014 |
975 self.__updateImageRect( |
1015 self.__updateImageRect( |
976 self.__pasteRect.topLeft(), |
1016 self.__pasteRect.topLeft(), |
977 self.__pasteRect.bottomRight() + QPoint(1, 1)) |
1017 self.__pasteRect.bottomRight() + QPoint(1, 1), |
|
1018 ) |
978 else: |
1019 else: |
979 EricMessageBox.warning( |
1020 EricMessageBox.warning( |
980 self, |
1021 self, |
981 self.tr("Pasting Image"), |
1022 self.tr("Pasting Image"), |
982 self.tr("""Invalid image data in clipboard.""")) |
1023 self.tr("""Invalid image data in clipboard."""), |
983 |
1024 ) |
|
1025 |
984 def editPasteAsNew(self): |
1026 def editPasteAsNew(self): |
985 """ |
1027 """ |
986 Public slot to paste the clipboard as a new image. |
1028 Public slot to paste the clipboard as a new image. |
987 """ |
1029 """ |
988 img, ok = self.__clipboardImage() |
1030 img, ok = self.__clipboardImage() |
989 if ok: |
1031 if ok: |
990 cmd = IconEditCommand( |
1032 cmd = IconEditCommand( |
991 self, self.tr("Paste Clipboard as New Image"), |
1033 self, self.tr("Paste Clipboard as New Image"), self.__image |
992 self.__image) |
1034 ) |
993 self.setIconImage(img) |
1035 self.setIconImage(img) |
994 self.setDirty(True) |
1036 self.setDirty(True) |
995 self.__undoStack.push(cmd) |
1037 self.__undoStack.push(cmd) |
996 cmd.setAfterImage(self.__image) |
1038 cmd.setAfterImage(self.__image) |
997 |
1039 |
998 def editSelectAll(self): |
1040 def editSelectAll(self): |
999 """ |
1041 """ |
1000 Public slot to select the complete image. |
1042 Public slot to select the complete image. |
1001 """ |
1043 """ |
1002 self.__unMark() |
1044 self.__unMark() |
1003 |
1045 |
1004 self.__startPos = QPoint(0, 0) |
1046 self.__startPos = QPoint(0, 0) |
1005 self.__endPos = QPoint(self.rect().bottomRight()) |
1047 self.__endPos = QPoint(self.rect().bottomRight()) |
1006 self.__markImage.fill(self.MarkColor.rgba()) |
1048 self.__markImage.fill(self.MarkColor.rgba()) |
1007 self.__selRect = self.__image.rect() |
1049 self.__selRect = self.__image.rect() |
1008 self.__selectionAvailable = True |
1050 self.__selectionAvailable = True |
1009 self.selectionAvailable.emit(True) |
1051 self.selectionAvailable.emit(True) |
1010 |
1052 |
1011 self.update() |
1053 self.update() |
1012 |
1054 |
1013 def editClear(self): |
1055 def editClear(self): |
1014 """ |
1056 """ |
1015 Public slot to clear the image. |
1057 Public slot to clear the image. |
1016 """ |
1058 """ |
1017 self.__unMark() |
1059 self.__unMark() |
1018 |
1060 |
1019 cmd = IconEditCommand(self, self.tr("Clear Image"), self.__image) |
1061 cmd = IconEditCommand(self, self.tr("Clear Image"), self.__image) |
1020 self.__image.fill(Qt.GlobalColor.transparent) |
1062 self.__image.fill(Qt.GlobalColor.transparent) |
1021 self.update() |
1063 self.update() |
1022 self.setDirty(True) |
1064 self.setDirty(True) |
1023 self.__undoStack.push(cmd) |
1065 self.__undoStack.push(cmd) |
1024 cmd.setAfterImage(self.__image) |
1066 cmd.setAfterImage(self.__image) |
1025 |
1067 |
1026 def editResize(self): |
1068 def editResize(self): |
1027 """ |
1069 """ |
1028 Public slot to resize the image. |
1070 Public slot to resize the image. |
1029 """ |
1071 """ |
1030 from .IconSizeDialog import IconSizeDialog |
1072 from .IconSizeDialog import IconSizeDialog |
|
1073 |
1031 dlg = IconSizeDialog(self.__image.width(), self.__image.height()) |
1074 dlg = IconSizeDialog(self.__image.width(), self.__image.height()) |
1032 res = dlg.exec() |
1075 res = dlg.exec() |
1033 if res == QDialog.DialogCode.Accepted: |
1076 if res == QDialog.DialogCode.Accepted: |
1034 newWidth, newHeight = dlg.getData() |
1077 newWidth, newHeight = dlg.getData() |
1035 if ( |
1078 if newWidth != self.__image.width() or newHeight != self.__image.height(): |
1036 newWidth != self.__image.width() or |
1079 cmd = IconEditCommand(self, self.tr("Resize Image"), self.__image) |
1037 newHeight != self.__image.height() |
|
1038 ): |
|
1039 cmd = IconEditCommand(self, self.tr("Resize Image"), |
|
1040 self.__image) |
|
1041 img = self.__image.scaled( |
1080 img = self.__image.scaled( |
1042 newWidth, newHeight, Qt.AspectRatioMode.IgnoreAspectRatio, |
1081 newWidth, |
1043 Qt.TransformationMode.SmoothTransformation) |
1082 newHeight, |
|
1083 Qt.AspectRatioMode.IgnoreAspectRatio, |
|
1084 Qt.TransformationMode.SmoothTransformation, |
|
1085 ) |
1044 self.setIconImage(img) |
1086 self.setIconImage(img) |
1045 self.setDirty(True) |
1087 self.setDirty(True) |
1046 self.__undoStack.push(cmd) |
1088 self.__undoStack.push(cmd) |
1047 cmd.setAfterImage(self.__image) |
1089 cmd.setAfterImage(self.__image) |
1048 |
1090 |
1049 def editNew(self): |
1091 def editNew(self): |
1050 """ |
1092 """ |
1051 Public slot to generate a new, empty image. |
1093 Public slot to generate a new, empty image. |
1052 """ |
1094 """ |
1053 from .IconSizeDialog import IconSizeDialog |
1095 from .IconSizeDialog import IconSizeDialog |
|
1096 |
1054 dlg = IconSizeDialog(self.__image.width(), self.__image.height()) |
1097 dlg = IconSizeDialog(self.__image.width(), self.__image.height()) |
1055 res = dlg.exec() |
1098 res = dlg.exec() |
1056 if res == QDialog.DialogCode.Accepted: |
1099 if res == QDialog.DialogCode.Accepted: |
1057 width, height = dlg.getData() |
1100 width, height = dlg.getData() |
1058 img = QImage(width, height, QImage.Format.Format_ARGB32) |
1101 img = QImage(width, height, QImage.Format.Format_ARGB32) |
1059 img.fill(Qt.GlobalColor.transparent) |
1102 img.fill(Qt.GlobalColor.transparent) |
1060 self.setIconImage(img) |
1103 self.setIconImage(img) |
1061 |
1104 |
1062 def grayScale(self): |
1105 def grayScale(self): |
1063 """ |
1106 """ |
1064 Public slot to convert the image to gray preserving transparency. |
1107 Public slot to convert the image to gray preserving transparency. |
1065 """ |
1108 """ |
1066 cmd = IconEditCommand(self, self.tr("Convert to Grayscale"), |
1109 cmd = IconEditCommand(self, self.tr("Convert to Grayscale"), self.__image) |
1067 self.__image) |
|
1068 for x in range(self.__image.width()): |
1110 for x in range(self.__image.width()): |
1069 for y in range(self.__image.height()): |
1111 for y in range(self.__image.height()): |
1070 col = self.__image.pixel(x, y) |
1112 col = self.__image.pixel(x, y) |
1071 if col != qRgba(0, 0, 0, 0): |
1113 if col != qRgba(0, 0, 0, 0): |
1072 gray = qGray(col) |
1114 gray = qGray(col) |
1073 self.__image.setPixel( |
1115 self.__image.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(col))) |
1074 x, y, qRgba(gray, gray, gray, qAlpha(col))) |
|
1075 self.update() |
1116 self.update() |
1076 self.setDirty(True) |
1117 self.setDirty(True) |
1077 self.__undoStack.push(cmd) |
1118 self.__undoStack.push(cmd) |
1078 cmd.setAfterImage(self.__image) |
1119 cmd.setAfterImage(self.__image) |
1079 |
1120 |