View Full Version : QGraphicsView: restrict bbox of Item to bbox of scene?

2nd April 2009, 14:38

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 (http://www.qtcentre.org/forum/f-qt-programming-2/t-moving-of-qgraphicsitem-6841.html) 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:

from PyQt4 import QtGui, QtCore

from Ui_editor import Ui_Dialog

import resources_rc

class EditorPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, pixmap, editor):
super(EditorPixmapItem, self).__init__(pixmap)
self.editor = editor
self.crop_rect = None

def mousePressEvent(self, event):
if self.crop_rect:
self.crop_rect = CropRectItem(self)
self.start_pos = event.pos()

def mouseMoveEvent(self, event):
p = event.pos()
x1, y1, x2, y2 = self.start_pos.x(), self.start_pos.y(), p.x(), p.y()
dims = self.scene().sceneRect()
min_x, max_x, min_y, max_y = dims.x(), dims.width(), dims.y(), dims.height()
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1

x1 = max(x1, min_x)
x2 = min(x2, max_x)
y1 = max(y1, min_y)
y2 = min(y2, max_y)
self.crop_rect.setRect(x1, y1, x2 - x1, y2 - y1)

def mouseReleaseEvent(self, event):
self.crop_rect.final_pos = event.pos()

class CropRectItem(QtGui.QGraphicsRectItem):
def __init__(self, parent):
super(CropRectItem, self).__init__(0,0, 0,0)
self.setBrush(QtGui.QColor(150, 150, 150, 150))

def itemChange(self, change, value):
if change == self.ItemPositionChange:
new_pos = value.toPointF()
print '%f, %f' % (new_pos.x(), new_pos.y())
return QtGui.QGraphicsRectItem.itemChange(self, change, value)

class ImageEditor(QtGui.QDialog):
def __init__(self, parent, image):
super(ImageEditor, self).__init__(parent)
self.ui = Ui_Dialog()
self.image = image
self.pixmap_item = EditorPixmapItem(image.pixmap, self)
self.scene = QtGui.QGraphicsScene()
self.scene.setSceneRect(0, 0, self.pixmap_item.pixmap().width(), self.pixmap_item.pixmap().height())
self.ui.cover_view.setHorizontalScrollBarPolicy(Qt Core.Qt.ScrollBarAsNeeded)
self.ui.cover_view.setVerticalScrollBarPolicy(QtCo re.Qt.ScrollBarAsNeeded)

self.scale_percent = 100

bg_brush = QtGui.QBrush(QtGui.QPixmap(':/img/checkerboard.png'))

QtCore.QObject.connect(self.ui.scale_slider, QtCore.SIGNAL('valueChanged(int)'), self.scale)

def scale(self, value):
self.pixmap_item.scale(value/100.0, value/100.0)
self.ui.cover_view.setSceneRect(0, 0, int(self.original_width*(value/100.0)), int(self.original_height*(value/100.0)))
self.scale_percent = value