Hi,

I'm in the process of writing my first Qt application (I've done Swing and SWT applications before). As Python is my favored language, I use PyQt4. My application needs a simple image editor, only cropping and scaling. Scaling is done with a slider (0-100%), which transforms the image and also adjusts the sceneRect to be exactly as big as the image (to get rid of unnecessary scrollbars). That all works fine.

My problem comes with cropping. The user should be able to draw a rectangular cropping area on the image. He should not be able to extend the cropping area outside of the image. And he should be allowed to move the cropping area around after he has drawn it.

Part 1 (restrict to image while extending the cropper) works more or less, if not 100% accurate, with a overridden mouseMoveEvent method on the image.

Part 2 (restrict movement of the cropper to the scene) doesn't work. I read in this thread that overriding itemChange is the right way to go, but I don't really understand the value I get in that method. The docs say it's the new position, but according to my tests, it's the delta between the original position of the rect (before the first move operation) and the new position, which irritates the heck out of me. This probably has a perfectly reasonable explanation that involves local, parent, and scene coordinates, but my head is hurting enough with just one coordinate system

How could I implement this movement restriction? Here's the code I have so far:

Qt Code:
  1. from PyQt4 import QtGui, QtCore
  2.  
  3. from Ui_editor import Ui_Dialog
  4.  
  5. import resources_rc
  6.  
  7. class EditorPixmapItem(QtGui.QGraphicsPixmapItem):
  8. def __init__(self, pixmap, editor):
  9. super(EditorPixmapItem, self).__init__(pixmap)
  10. self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
  11. self.editor = editor
  12. self.crop_rect = None
  13.  
  14. def mousePressEvent(self, event):
  15. if self.crop_rect:
  16. self.editor.scene.removeItem(self.crop_rect)
  17. self.crop_rect = CropRectItem(self)
  18. self.start_pos = event.pos()
  19.  
  20. def mouseMoveEvent(self, event):
  21. p = event.pos()
  22. x1, y1, x2, y2 = self.start_pos.x(), self.start_pos.y(), p.x(), p.y()
  23. dims = self.scene().sceneRect()
  24. min_x, max_x, min_y, max_y = dims.x(), dims.width(), dims.y(), dims.height()
  25. if x1 > x2:
  26. x1, x2 = x2, x1
  27. if y1 > y2:
  28. y1, y2 = y2, y1
  29.  
  30. x1 = max(x1, min_x)
  31. x2 = min(x2, max_x)
  32. y1 = max(y1, min_y)
  33. y2 = min(y2, max_y)
  34. self.crop_rect.setRect(x1, y1, x2 - x1, y2 - y1)
  35.  
  36. def mouseReleaseEvent(self, event):
  37. self.crop_rect.final_pos = event.pos()
  38.  
  39. class CropRectItem(QtGui.QGraphicsRectItem):
  40. def __init__(self, parent):
  41. super(CropRectItem, self).__init__(0,0, 0,0)
  42. self.setParentItem(parent)
  43. self.setBrush(QtGui.QColor(150, 150, 150, 150))
  44. self.setFlag(self.ItemIsMovable)
  45.  
  46. def itemChange(self, change, value):
  47. if change == self.ItemPositionChange:
  48. new_pos = value.toPointF()
  49. print '%f, %f' % (new_pos.x(), new_pos.y())
  50. return QtGui.QGraphicsRectItem.itemChange(self, change, value)
  51.  
  52. class ImageEditor(QtGui.QDialog):
  53. def __init__(self, parent, image):
  54. super(ImageEditor, self).__init__(parent)
  55. self.ui = Ui_Dialog()
  56. self.ui.setupUi(self)
  57. self.image = image
  58. self.pixmap_item = EditorPixmapItem(image.pixmap, self)
  59. self.scene = QtGui.QGraphicsScene()
  60. self.scene.setSceneRect(0, 0, self.pixmap_item.pixmap().width(), self.pixmap_item.pixmap().height())
  61. self.scene.addItem(self.pixmap_item)
  62. self.ui.cover_view.setScene(self.scene)
  63. self.ui.cover_view.centerOn(self.pixmap_item)
  64. self.ui.cover_view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
  65. self.ui.cover_view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
  66.  
  67. self.scale_percent = 100
  68.  
  69. bg_brush = QtGui.QBrush(QtGui.QPixmap(':/img/checkerboard.png'))
  70. self.ui.cover_view.setBackgroundBrush(bg_brush)
  71.  
  72. QtCore.QObject.connect(self.ui.scale_slider, QtCore.SIGNAL('valueChanged(int)'), self.scale)
  73.  
  74. def scale(self, value):
  75. self.pixmap_item.resetTransform()
  76. self.pixmap_item.scale(value/100.0, value/100.0)
  77. self.ui.cover_view.setSceneRect(0, 0, int(self.original_width*(value/100.0)), int(self.original_height*(value/100.0)))
  78. self.ui.cover_view.centerOn(self.pixmap_item)
  79. self.scale_percent = value
To copy to clipboard, switch view to plain text mode