IconEditor/IconEditorGrid.py

changeset 2992
dbdf27746da5
parent 2957
c7f8c7561542
child 3020
542e97d4ecb3
child 3057
10516539f238
equal deleted inserted replaced
2991:226481ff40d1 2992:dbdf27746da5
6 """ 6 """
7 Module implementing the icon editor grid. 7 Module implementing the icon editor grid.
8 """ 8 """
9 9
10 from PyQt4.QtCore import pyqtSignal, Qt, QPoint, QRect, QSize 10 from PyQt4.QtCore import pyqtSignal, Qt, QPoint, QRect, QSize
11 from PyQt4.QtGui import QUndoCommand, QImage, QWidget, QColor, QPixmap, QSizePolicy, \ 11 from PyQt4.QtGui import QUndoCommand, QImage, QWidget, QColor, QPixmap, \
12 QUndoStack, qRgba, QPainter, QApplication, QCursor, QBrush, QDialog, qGray, qAlpha 12 QSizePolicy, QUndoStack, qRgba, QPainter, QApplication, QCursor, \
13 QBrush, QDialog, qGray, qAlpha
13 14
14 from E5Gui import E5MessageBox 15 from E5Gui import E5MessageBox
15 16
16 from .cursors import cursors_rc # __IGNORE_WARNING__ 17 from .cursors import cursors_rc # __IGNORE_WARNING__
17 18
24 """ 25 """
25 Constructor 26 Constructor
26 27
27 @param grid reference to the icon editor grid (IconEditorGrid) 28 @param grid reference to the icon editor grid (IconEditorGrid)
28 @param text text for the undo command (string) 29 @param text text for the undo command (string)
29 @param oldImage copy of the icon before the changes were applied (QImage) 30 @param oldImage copy of the icon before the changes were applied
31 (QImage)
30 @param parent reference to the parent command (QUndoCommand) 32 @param parent reference to the parent command (QUndoCommand)
31 """ 33 """
32 super().__init__(text, parent) 34 super().__init__(text, parent)
33 35
34 self.__grid = grid 36 self.__grid = grid
61 """ 63 """
62 Class implementing the icon editor grid. 64 Class implementing the icon editor grid.
63 65
64 @signal canRedoChanged(bool) emitted after the redo status has changed 66 @signal canRedoChanged(bool) emitted after the redo status has changed
65 @signal canUndoChanged(bool) emitted after the undo status has changed 67 @signal canUndoChanged(bool) emitted after the undo status has changed
66 @signal clipboardImageAvailable(bool) emitted to signal the availability of an 68 @signal clipboardImageAvailable(bool) emitted to signal the availability
67 image to be pasted 69 of an image to be pasted
68 @signal colorChanged(QColor) emitted after the drawing color was changed 70 @signal colorChanged(QColor) emitted after the drawing color was changed
69 @signal imageChanged(bool) emitted after the image was modified 71 @signal imageChanged(bool) emitted after the image was modified
70 @signal positionChanged(int, int) emitted after the cursor poition was changed 72 @signal positionChanged(int, int) emitted after the cursor poition was
73 changed
71 @signal previewChanged(QPixmap) emitted to signal a new preview pixmap 74 @signal previewChanged(QPixmap) emitted to signal a new preview pixmap
72 @signal selectionAvailable(bool) emitted to signal a change of the selection 75 @signal selectionAvailable(bool) emitted to signal a change of the
76 selection
73 @signal sizeChanged(int, int) emitted after the size has been changed 77 @signal sizeChanged(int, int) emitted after the size has been changed
74 @signal zoomChanged(int) emitted to signal a change of the zoom value 78 @signal zoomChanged(int) emitted to signal a change of the zoom value
75 """ 79 """
76 canRedoChanged = pyqtSignal(bool) 80 canRedoChanged = pyqtSignal(bool)
77 canUndoChanged = pyqtSignal(bool) 81 canUndoChanged = pyqtSignal(bool)
190 pix.setMask(mask) 194 pix.setMask(mask)
191 self.__rubberCursor = QCursor(pix, 1, 16) 195 self.__rubberCursor = QCursor(pix, 1, 16)
192 196
193 def __initUndoTexts(self): 197 def __initUndoTexts(self):
194 """ 198 """
195 Private method to initialize texts to be associated with undo commands for 199 Private method to initialize texts to be associated with undo commands
196 the various drawing tools. 200 for the various drawing tools.
197 """ 201 """
198 self.__undoTexts = { 202 self.__undoTexts = {
199 self.Pencil: self.trUtf8("Set Pixel"), 203 self.Pencil: self.trUtf8("Set Pixel"),
200 self.Rubber: self.trUtf8("Erase Pixel"), 204 self.Rubber: self.trUtf8("Erase Pixel"),
201 self.Line: self.trUtf8("Draw Line"), 205 self.Line: self.trUtf8("Draw Line"),
219 def setDirty(self, dirty, setCleanState=False): 223 def setDirty(self, dirty, setCleanState=False):
220 """ 224 """
221 Public slot to set the dirty flag. 225 Public slot to set the dirty flag.
222 226
223 @param dirty flag indicating the new modification status (boolean) 227 @param dirty flag indicating the new modification status (boolean)
224 @param setCleanState flag indicating to set the undo stack to clean (boolean) 228 @param setCleanState flag indicating to set the undo stack to clean
229 (boolean)
225 """ 230 """
226 self.__dirty = dirty 231 self.__dirty = dirty
227 self.imageChanged.emit(dirty) 232 self.imageChanged.emit(dirty)
228 233
229 if not dirty and setCleanState: 234 if not dirty and setCleanState:
286 if self.__curTool in [self.RectangleSelection, self.CircleSelection]: 291 if self.__curTool in [self.RectangleSelection, self.CircleSelection]:
287 self.__selecting = True 292 self.__selecting = True
288 else: 293 else:
289 self.__selecting = False 294 self.__selecting = False
290 295
291 if self.__curTool in [self.RectangleSelection, self.CircleSelection, self.Line, 296 if self.__curTool in [self.RectangleSelection, self.CircleSelection,
292 self.Rectangle, self.FilledRectangle, 297 self.Line, self.Rectangle, self.FilledRectangle,
293 self.Circle, self.FilledCircle, 298 self.Circle, self.FilledCircle,
294 self.Ellipse, self.FilledEllipse]: 299 self.Ellipse, self.FilledEllipse]:
295 self.setCursor(self.__aimCursor) 300 self.setCursor(self.__aimCursor)
296 elif self.__curTool == self.Fill: 301 elif self.__curTool == self.Fill:
297 self.setCursor(self.__fillCursor) 302 self.setCursor(self.__fillCursor)
406 411
407 if self.__zoom >= 3 and self.__gridEnabled: 412 if self.__zoom >= 3 and self.__gridEnabled:
408 painter.setPen(self.palette().windowText().color()) 413 painter.setPen(self.palette().windowText().color())
409 i = 0 414 i = 0
410 while i <= self.__image.width(): 415 while i <= self.__image.width():
411 painter.drawLine(self.__zoom * i, 0, 416 painter.drawLine(
412 self.__zoom * i, self.__zoom * self.__image.height()) 417 self.__zoom * i, 0,
418 self.__zoom * i, self.__zoom * self.__image.height())
413 i += 1 419 i += 1
414 j = 0 420 j = 0
415 while j <= self.__image.height(): 421 while j <= self.__image.height():
416 painter.drawLine(0, self.__zoom * j, 422 painter.drawLine(
417 self.__zoom * self.__image.width(), self.__zoom * j) 423 0, self.__zoom * j,
424 self.__zoom * self.__image.width(), self.__zoom * j)
418 j += 1 425 j += 1
419 426
420 col = QColor("#aaa") 427 col = QColor("#aaa")
421 painter.setPen(Qt.DashLine) 428 painter.setPen(Qt.DashLine)
422 for i in range(0, self.__image.width()): 429 for i in range(0, self.__image.width()):
424 rect = self.__pixelRect(i, j) 431 rect = self.__pixelRect(i, j)
425 if evt.region().intersects(rect): 432 if evt.region().intersects(rect):
426 color = QColor.fromRgba(self.__image.pixel(i, j)) 433 color = QColor.fromRgba(self.__image.pixel(i, j))
427 painter.fillRect(rect, QBrush(Qt.white)) 434 painter.fillRect(rect, QBrush(Qt.white))
428 painter.fillRect(QRect(rect.topLeft(), rect.center()), col) 435 painter.fillRect(QRect(rect.topLeft(), rect.center()), col)
429 painter.fillRect(QRect(rect.center(), rect.bottomRight()), col) 436 painter.fillRect(QRect(rect.center(), rect.bottomRight()),
437 col)
430 painter.fillRect(rect, QBrush(color)) 438 painter.fillRect(rect, QBrush(color))
431 439
432 if self.__isMarked(i, j): 440 if self.__isMarked(i, j):
433 painter.drawRect(rect.adjusted(0, 0, -1, -1)) 441 painter.drawRect(rect.adjusted(0, 0, -1, -1))
434 442
444 """ 452 """
445 if self.__zoom >= 3 and self.__gridEnabled: 453 if self.__zoom >= 3 and self.__gridEnabled:
446 return QRect(self.__zoom * i + 1, self.__zoom * j + 1, 454 return QRect(self.__zoom * i + 1, self.__zoom * j + 1,
447 self.__zoom - 1, self.__zoom - 1) 455 self.__zoom - 1, self.__zoom - 1)
448 else: 456 else:
449 return QRect(self.__zoom * i, self.__zoom * j, self.__zoom, self.__zoom) 457 return QRect(self.__zoom * i, self.__zoom * j,
458 self.__zoom, self.__zoom)
450 459
451 def mousePressEvent(self, evt): 460 def mousePressEvent(self, evt):
452 """ 461 """
453 Protected method to handle mouse button press events. 462 Protected method to handle mouse button press events.
454 463
533 self.__currentUndoCmd.setAfterImage(self.__image) 542 self.__currentUndoCmd.setAfterImage(self.__image)
534 self.__currentUndoCmd = None 543 self.__currentUndoCmd = None
535 544
536 if self.__curTool not in [self.Pencil, self.Rubber, 545 if self.__curTool not in [self.Pencil, self.Rubber,
537 self.Fill, self.ColorPicker, 546 self.Fill, self.ColorPicker,
538 self.RectangleSelection, self.CircleSelection]: 547 self.RectangleSelection,
548 self.CircleSelection]:
539 cmd = IconEditCommand(self, self.__undoTexts[self.__curTool], 549 cmd = IconEditCommand(self, self.__undoTexts[self.__curTool],
540 self.__image) 550 self.__image)
541 if self.__drawTool(evt.pos(), False): 551 if self.__drawTool(evt.pos(), False):
542 self.__undoStack.push(cmd) 552 self.__undoStack.push(cmd)
543 cmd.setAfterImage(self.__image) 553 cmd.setAfterImage(self.__image)
581 591
582 @param pos widget position of the paste rectangle (QPoint) 592 @param pos widget position of the paste rectangle (QPoint)
583 """ 593 """
584 self.__markImage.fill(self.NoMarkColor.rgba()) 594 self.__markImage.fill(self.NoMarkColor.rgba())
585 if self.__pasteRect.isValid(): 595 if self.__pasteRect.isValid():
586 self.__updateImageRect(self.__pasteRect.topLeft(), 596 self.__updateImageRect(
587 self.__pasteRect.bottomRight() + QPoint(1, 1)) 597 self.__pasteRect.topLeft(),
598 self.__pasteRect.bottomRight() + QPoint(1, 1))
588 599
589 x, y = self.__imageCoordinates(pos) 600 x, y = self.__imageCoordinates(pos)
590 isize = self.__image.size() 601 isize = self.__image.size()
591 if x + self.__clipboardSize.width() <= isize.width(): 602 if x + self.__clipboardSize.width() <= isize.width():
592 sx = self.__clipboardSize.width() 603 sx = self.__clipboardSize.width()
659 r = max(abs(start.x() - end.x()), abs(start.y() - end.y())) 670 r = max(abs(start.x() - end.x()), abs(start.y() - end.y()))
660 if self.__curTool in [self.FilledCircle, self.CircleSelection]: 671 if self.__curTool in [self.FilledCircle, self.CircleSelection]:
661 painter.setBrush(QBrush(drawColor)) 672 painter.setBrush(QBrush(drawColor))
662 painter.drawEllipse(start, r, r) 673 painter.drawEllipse(start, r, r)
663 if self.__selecting: 674 if self.__selecting:
664 self.__selRect = QRect(start.x() - r, start.y() - r, 2 * r + 1, 2 * r + 1) 675 self.__selRect = QRect(start.x() - r, start.y() - r,
676 2 * r + 1, 2 * r + 1)
665 self.__selectionAvailable = True 677 self.__selectionAvailable = True
666 self.selectionAvailable.emit(True) 678 self.selectionAvailable.emit(True)
667 679
668 elif self.__curTool in [self.Ellipse, self.FilledEllipse]: 680 elif self.__curTool in [self.Ellipse, self.FilledEllipse]:
669 r1 = abs(start.x() - end.x()) 681 r1 = abs(start.x() - end.x())
711 723
712 def __updateRect(self, pos1, pos2): 724 def __updateRect(self, pos1, pos2):
713 """ 725 """
714 Private slot to update parts of the widget. 726 Private slot to update parts of the widget.
715 727
716 @param pos1 top, left position for the update in widget coordinates (QPoint) 728 @param pos1 top, left position for the update in widget coordinates
717 @param pos2 bottom, right position for the update in widget coordinates (QPoint) 729 (QPoint)
730 @param pos2 bottom, right position for the update in widget
731 coordinates (QPoint)
718 """ 732 """
719 self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)), 733 self.__updateImageRect(QPoint(*self.__imageCoordinates(pos1)),
720 QPoint(*self.__imageCoordinates(pos2))) 734 QPoint(*self.__imageCoordinates(pos2)))
721 735
722 def __updateImageRect(self, ipos1, ipos2): 736 def __updateImageRect(self, ipos1, ipos2):
723 """ 737 """
724 Private slot to update parts of the widget. 738 Private slot to update parts of the widget.
725 739
726 @param ipos1 top, left position for the update in image coordinates (QPoint) 740 @param ipos1 top, left position for the update in image coordinates
727 @param ipos2 bottom, right position for the update in image coordinates (QPoint) 741 (QPoint)
742 @param ipos2 bottom, right position for the update in image
743 coordinates (QPoint)
728 """ 744 """
729 r1 = self.__pixelRect(ipos1.x(), ipos1.y()) 745 r1 = self.__pixelRect(ipos1.x(), ipos1.y())
730 r2 = self.__pixelRect(ipos2.x(), ipos2.y()) 746 r2 = self.__pixelRect(ipos2.x(), ipos2.y())
731 747
732 left = min(r1.x(), r2.x()) 748 left = min(r1.x(), r2.x())
778 p = QPixmap.fromImage(self.__image) 794 p = QPixmap.fromImage(self.__image)
779 return p 795 return p
780 796
781 def __checkClipboard(self): 797 def __checkClipboard(self):
782 """ 798 """
783 Private slot to check, if the clipboard contains a valid image, and signal 799 Private slot to check, if the clipboard contains a valid image, and
784 the result. 800 signal the result.
785 """ 801 """
786 ok = self.__clipboardImage()[1] 802 ok = self.__clipboardImage()[1]
787 self.__clipboardImageAvailable = ok 803 self.__clipboardImageAvailable = ok
788 self.clipboardImageAvailable.emit(ok) 804 self.clipboardImageAvailable.emit(ok)
789 805
815 831
816 @param cut flag indicating to cut the selection (boolean) 832 @param cut flag indicating to cut the selection (boolean)
817 @return image of the selection (QImage) 833 @return image of the selection (QImage)
818 """ 834 """
819 if cut: 835 if cut:
820 cmd = IconEditCommand(self, self.trUtf8("Cut Selection"), self.__image) 836 cmd = IconEditCommand(self, self.trUtf8("Cut Selection"),
837 self.__image)
821 838
822 img = QImage(self.__selRect.size(), QImage.Format_ARGB32) 839 img = QImage(self.__selRect.size(), QImage.Format_ARGB32)
823 img.fill(qRgba(0, 0, 0, 0)) 840 img.fill(qRgba(0, 0, 0, 0))
824 for i in range(0, self.__selRect.width()): 841 for i in range(0, self.__selRect.width()):
825 for j in range(0, self.__selRect.height()): 842 for j in range(0, self.__selRect.height()):
826 if self.__image.rect().contains(self.__selRect.x() + i, 843 if self.__image.rect().contains(self.__selRect.x() + i,
827 self.__selRect.y() + j): 844 self.__selRect.y() + j):
828 if self.__isMarked(self.__selRect.x() + i, self.__selRect.y() + j): 845 if self.__isMarked(
829 img.setPixel(i, j, self.__image.pixel(self.__selRect.x() + i, 846 self.__selRect.x() + i, self.__selRect.y() + j):
830 self.__selRect.y() + j)) 847 img.setPixel(i, j, self.__image.pixel(
848 self.__selRect.x() + i, self.__selRect.y() + j))
831 if cut: 849 if cut:
832 self.__image.setPixel(self.__selRect.x() + i, 850 self.__image.setPixel(self.__selRect.x() + i,
833 self.__selRect.y() + j, 851 self.__selRect.y() + j,
834 qRgba(0, 0, 0, 0)) 852 qRgba(0, 0, 0, 0))
835 853
862 880
863 def editPaste(self, pasting=False): 881 def editPaste(self, pasting=False):
864 """ 882 """
865 Public slot to paste an image from the clipboard. 883 Public slot to paste an image from the clipboard.
866 884
867 @param pasting flag indicating part two of the paste operation (boolean) 885 @param pasting flag indicating part two of the paste operation
886 (boolean)
868 """ 887 """
869 img, ok = self.__clipboardImage() 888 img, ok = self.__clipboardImage()
870 if ok: 889 if ok:
871 if img.width() > self.__image.width() or img.height() > self.__image.height(): 890 if img.width() > self.__image.width() or \
891 img.height() > self.__image.height():
872 res = E5MessageBox.yesNo(self, 892 res = E5MessageBox.yesNo(self,
873 self.trUtf8("Paste"), 893 self.trUtf8("Paste"),
874 self.trUtf8("""<p>The clipboard image is larger than the current """ 894 self.trUtf8(
875 """image.<br/>Paste as new image?</p>""")) 895 """<p>The clipboard image is larger than the"""
896 """ current image.<br/>Paste as new image?</p>"""))
876 if res: 897 if res:
877 self.editPasteAsNew() 898 self.editPasteAsNew()
878 return 899 return
879 elif not pasting: 900 elif not pasting:
880 self.__isPasting = True 901 self.__isPasting = True
881 self.__clipboardSize = img.size() 902 self.__clipboardSize = img.size()
882 else: 903 else:
883 cmd = IconEditCommand(self, self.trUtf8("Paste Clipboard"), self.__image) 904 cmd = IconEditCommand(self, self.trUtf8("Paste Clipboard"),
905 self.__image)
884 self.__markImage.fill(self.NoMarkColor.rgba()) 906 self.__markImage.fill(self.NoMarkColor.rgba())
885 painter = QPainter(self.__image) 907 painter = QPainter(self.__image)
886 painter.setPen(self.penColor()) 908 painter.setPen(self.penColor())
887 painter.setCompositionMode(self.__compositingMode) 909 painter.setCompositionMode(self.__compositingMode)
888 painter.drawImage(self.__pasteRect.x(), self.__pasteRect.y(), img, 0, 0, 910 painter.drawImage(
889 self.__pasteRect.width() + 1, self.__pasteRect.height() + 1) 911 self.__pasteRect.x(), self.__pasteRect.y(), img, 0, 0,
912 self.__pasteRect.width() + 1,
913 self.__pasteRect.height() + 1)
890 914
891 self.__undoStack.push(cmd) 915 self.__undoStack.push(cmd)
892 cmd.setAfterImage(self.__image) 916 cmd.setAfterImage(self.__image)
893 917
894 self.__updateImageRect(self.__pasteRect.topLeft(), 918 self.__updateImageRect(
895 self.__pasteRect.bottomRight() + QPoint(1, 1)) 919 self.__pasteRect.topLeft(),
920 self.__pasteRect.bottomRight() + QPoint(1, 1))
896 else: 921 else:
897 E5MessageBox.warning(self, 922 E5MessageBox.warning(self,
898 self.trUtf8("Pasting Image"), 923 self.trUtf8("Pasting Image"),
899 self.trUtf8("""Invalid image data in clipboard.""")) 924 self.trUtf8("""Invalid image data in clipboard."""))
900 925
902 """ 927 """
903 Public slot to paste the clipboard as a new image. 928 Public slot to paste the clipboard as a new image.
904 """ 929 """
905 img, ok = self.__clipboardImage() 930 img, ok = self.__clipboardImage()
906 if ok: 931 if ok:
907 cmd = IconEditCommand(self, self.trUtf8("Paste Clipboard as New Image"), 932 cmd = IconEditCommand(
908 self.__image) 933 self, self.trUtf8("Paste Clipboard as New Image"),
934 self.__image)
909 self.setIconImage(img) 935 self.setIconImage(img)
910 self.setDirty(True) 936 self.setDirty(True)
911 self.__undoStack.push(cmd) 937 self.__undoStack.push(cmd)
912 cmd.setAfterImage(self.__image) 938 cmd.setAfterImage(self.__image)
913 939
946 from .IconSizeDialog import IconSizeDialog 972 from .IconSizeDialog import IconSizeDialog
947 dlg = IconSizeDialog(self.__image.width(), self.__image.height()) 973 dlg = IconSizeDialog(self.__image.width(), self.__image.height())
948 res = dlg.exec_() 974 res = dlg.exec_()
949 if res == QDialog.Accepted: 975 if res == QDialog.Accepted:
950 newWidth, newHeight = dlg.getData() 976 newWidth, newHeight = dlg.getData()
951 if newWidth != self.__image.width() or newHeight != self.__image.height(): 977 if newWidth != self.__image.width() or \
952 cmd = IconEditCommand(self, self.trUtf8("Resize Image"), self.__image) 978 newHeight != self.__image.height():
953 img = self.__image.scaled(newWidth, newHeight, Qt.IgnoreAspectRatio, 979 cmd = IconEditCommand(self, self.trUtf8("Resize Image"),
954 Qt.SmoothTransformation) 980 self.__image)
981 img = self.__image.scaled(
982 newWidth, newHeight, Qt.IgnoreAspectRatio,
983 Qt.SmoothTransformation)
955 self.setIconImage(img) 984 self.setIconImage(img)
956 self.setDirty(True) 985 self.setDirty(True)
957 self.__undoStack.push(cmd) 986 self.__undoStack.push(cmd)
958 cmd.setAfterImage(self.__image) 987 cmd.setAfterImage(self.__image)
959 988
972 1001
973 def grayScale(self): 1002 def grayScale(self):
974 """ 1003 """
975 Public slot to convert the image to gray preserving transparency. 1004 Public slot to convert the image to gray preserving transparency.
976 """ 1005 """
977 cmd = IconEditCommand(self, self.trUtf8("Convert to Grayscale"), self.__image) 1006 cmd = IconEditCommand(self, self.trUtf8("Convert to Grayscale"),
1007 self.__image)
978 for x in range(self.__image.width()): 1008 for x in range(self.__image.width()):
979 for y in range(self.__image.height()): 1009 for y in range(self.__image.height()):
980 col = self.__image.pixel(x, y) 1010 col = self.__image.pixel(x, y)
981 if col != qRgba(0, 0, 0, 0): 1011 if col != qRgba(0, 0, 0, 0):
982 gray = qGray(col) 1012 gray = qGray(col)
983 self.__image.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(col))) 1013 self.__image.setPixel(
1014 x, y, qRgba(gray, gray, gray, qAlpha(col)))
984 self.update() 1015 self.update()
985 self.setDirty(True) 1016 self.setDirty(True)
986 self.__undoStack.push(cmd) 1017 self.__undoStack.push(cmd)
987 cmd.setAfterImage(self.__image) 1018 cmd.setAfterImage(self.__image)
988 1019

eric ide

mercurial