Graeme Rock Graeme Rock - 1 month ago 18
C++ Question

Constraining child QGraphicsItem to scene?

Does anyone have a better way to constrain a child of a

QGraphicsItem
to a scene?

I have successfully properly constrained a parent
QGraphicsItem
to its scene by overriding
itemChange
, but now I need to do the same for the child
QGraphicsItem
.

Example Use-case:

An Example Use Case with two handles (children) and a bar (parent)

This code works... for the most part. The only problem is the
QGraphicsItem
's velocity when hitting either side will affect its endstop position
:

QVariant SizeGripItem::HandleItem::itemChange(GraphicsItemChange change,
const QVariant &value)
{
QPointF newPos = value.toPointF();
if (change == ItemPositionChange)
{
if(scene())
{
newPos.setY(pos().y()); // Y-coordinate is constant.

if(scenePos().x() < 0 ) //If child item is off the left side of the scene,
{
if (newPos.x() < pos().x()) // and is trying to move left,
{
newPos.setX(pos().x()); // then hold its position
}
}
else if( scenePos().x() > scene()->sceneRect().right()) //If child item is off the right side of the scene,
{
if (newPos.x() > pos().x()) //and is trying to move right,
{
newPos.setX(pos().x()); // then hold its position
}
}
}
}
return newPos;
}


For the parent item, I used:
newPos.setX(qMin(scRect.right(), qMax(newPos.x(), scRect.left())));

which worked perfectly, but I'm stumped as to how or if I could use that here.

Answer

First, to be specific, scenes effectively have no boundaries. What you're trying to do is constrain the item to the scene rectangle that you've set elsewhere.

The problem I see is in your use of scenePos. This is an ItemPositionChange; the item's scenePos hasn't been updated with the new position yet, so when you check for scenePos being out of the scene rect, you're really checking the result of the last position change, not the current one. Because of that, your item ends up just off the edge of the scene rectangle and then sticks there. How far off the edge depends on how fast you were moving the mouse, which dictates how much distance there is between ItemPositionChange notifications.

Instead, you need to compare the new position to the scene rectangle and then restrict the value that gets returned to be within the scene rectangle. You need the new position in scene coordinates to do the comparison, so you need something like:

QPoint new_scene_pos = mapToScene (new_pos);

if (new_scene_pos.x() < scene()->sceneRect().left())
    {
    new_scene_pos.setX (scene()->sceneRect().left());
    new_pos = mapFromScene (new_scene_pos);
    }

This isn't complete code, obviously, but these are the conversions and checks you need to do to keep it in on the left side. The right side is very similar, so just use the new_scene_pos for the comparison there.

Note that I didn't assume that the left edge of sceneRecT is at 0. I'm sure that's what you coded where you set the sceneRect, but using the actual left value rather than assuming it's 0 eliminates any problems if you end up later changing the range of scene coordinates you're planning to work with.

I used "left" instead of "x" on the sceneRect call just because it parallels using "right" for the other side. They're exactly the same, but I think it reads slightly better in this case.