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 |
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()) |
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 |