Jumping QGraphicsObject
-
I create QGraphicsObjects that are drawn in a QGraphicsScene. These QGraphicsObjects are moveable and their painting includes drawing a Name text, see an example below. The size of the QGraphicsObject depends on the space that is needed to draw the Name text. The name can be changed and as a result the QGraphicsObject is redrawn taking the sizing rules for it into account, and it does so without changing its center position. I use QGraphicsObject::prepareGeometryChange() before recomputing the new dimensions and actually changing the Name. I got all this to work well.
The problem that I experience is that in case changing the Name implies that the dimensions of the QGraphicsObject are changed and I subsequently want to move the QGraphicsObject by using the left mouse button, its center position first jumps to some other position before it starts moving. This is only the first time I move the QGraphicsObject after changing the Name. Any idea what this behavior might cause?
The same problem also occurs in case any of the other texts in the picture are changed such that the dimensions of the QGraphicsObjects have to change.
-
Well, I now experience the same problem in another situation... Whether or not I use this prepareGeometryChange function does not have any effect on improving/worsening things. I am completely lost now.
My problem is basically that I need to change QGraphicsObjects in (the same or another Scene) after or with changes effecting them initiated by events in some other QGraphicsObject or even some completely different object (such as a widget or dialog). How should this be done?
Edit: A bit more info. It seems that somehow the object at which the mouse click was originally done is moved to the position of where the next mouse click happens after the redrawing of (all) the object(s) was done successfully. Maybe this gives somebody an idea of what might be wrong.
-
I have made a small screen cast to show what is happening...
ScreenCast of Jumping QGraphicsObject
In this cast, I have two States in a State Machine of which the one labelled LL SS is the initial State at the beginning. Being the initial State is visualized by the double line. Then I double click on the State labelled SS SS to open a dialog that allows me to change the initial State to the one labelled SS SS. After pressing Ok, both QGraphicsObjects are properly updated. So far so good. Now I want to move the State labelled LL SS to some other position. At the moment that I click on it, the state labelled SS SS jumps to the position of where I clicked on the State LL SS to move it and instead of moving State LL SS, I am moving State SS SS.
-
Hi,
When I see your screen cast, it seems to me that you have at first selected LL SS. The you change something to SS SS and after clicking the OK button is becomes selected. And when you click on LL SS, SS SS moves to that point. Right?
Does it jump to every point you would click, to M M, SL SL or perhaps some empty space in State Machine?
Are those items meant to be moveable?
Does the boundingRect of SS SS change (on the code side) or is setPos somewher called? -
When I see your screen cast, it seems to me that you have at first selected LL SS. The you change something to SS SS and after clicking the OK button is becomes selected. And when you click on LL SS, SS SS moves to that point. Right?
Yes
Does it jump to every point you would click, to M M, SL SL or perhaps some empty space in State Machine?
Yes, to any point where I start a drag movement after clicking the ok button.
Are those items meant to be moveable?
Only the item that is under the cursor should move when I start a drag movement
Does the boundingRect of SS SS change (on the code side)?
The bounding box of both SS SS and LL SS are recomputed in this example. The result happens to be the same as before as the labels SS, and LL have not changed.
or is setPos somewhere called?
No. It is not called anymore after creation of the object (i.e., it is not called after the object is moved or after the bounding box is recomputed.)
-
@ModelTech said in Jumping QGraphicsObject:
Only the item that is under the cursor should move when I start a drag movement
The bounding box of both SS SS and LL SS are recomputed in this example. The result happens to be the same as before as the labels SS, and LL have not changed.
Could you share the code on these parts?
-
No problem. But it perhaps needs a bit of explanation...
I use a separation between Model objects (currently mostly QObjects but they will become traditional C++ classes) and View objects (the QGraphicsObjects we are talking about). For the particular case we are looking at here, the Model object class is called State and referred to by variable Specification, which basically just has setters and getters for its attributes which include a Name (The top text in the state visualization), the Position on the canvas that would eventually be written to file and something called SubScenario, of which the Name gets represented by the bottom text in the state visualization. The class name for the QGraphicsObject is in this case called StateDiagram. Hopefully, this is sufficient context for the code below.
Only the item that is under the cursor should move when I start a drag movement
The moving of objects relies on build-in functionality and I just reimplemented the QGraphicsObject::itemChange function to store the position in the Model object:
QVariant StateDiagram::itemChange(GraphicsItemChange Change, const QVariant &Value) { if (Change == ItemPositionHasChanged) Specification->setPosition(pos()); return QGraphicsItem::itemChange(Change, Value); }
The bounding box of both SS SS and LL SS are recomputed in this example. The result happens to be the same as before as the labels SS, and LL have not changed.
This one is a bit more elaborate as it is distributed over a few functions. The following function sets the internal variables such as NameText and SubScenarioText based on the properties of the referred State and also computes required Width for the StateDiagram to ensure that the texts are actually in the rounded rectangles.
void StateDiagram::dimension() { QFontMetricsF Metrics = QFontMetricsF(qApp->font()); NameText = QString(Specification->name()); QRectF NameRect = Metrics.boundingRect(NameText); SubScenarioText = QString(Specification->subScenario()->name()); QRectF SubScenarioRect = Metrics.boundingRect(SubScenarioText); Initial = Specification->scenario()->initial() == Specification; Width = qMax(NameRect.width(), SubScenarioRect.width()) + 20; }
The computation of the two bounding boxes for the two text elements:
QRectF StateDiagram::nameRect() const { QFontMetricsF Metrics = QFontMetricsF(qApp->font()); QRectF TextRect = Metrics.boundingRect(NameText); QPointF Position = QPointF(-0.5 * Width, - 0.5 * TextRect.height() - 5); TextRect.moveCenter(Position); return TextRect.adjusted(-1, -1, 1, 1); } QRectF StateDiagram::subScenarioRect() const { QFontMetricsF Metrics = QFontMetricsF(qApp->font()); QRectF TextRect = Metrics.boundingRect(SubScenarioText); QPointF Position = QPointF(-0.5 * Width, 0.5 * TextRect.height() + 5); TextRect.moveCenter(Position); return TextRect.adjusted(-1, -1, 1, 1); }
The bouding box of a StateDiagram is computed as follows, which depends on the stuff above:
QRectF StateDiagram::boundingRect() const { QPointF TopLeft = QPointF(qMin(nameRect().left(), subScenarioRect().left()) - 10, nameRect().top() - 10); QPointF BottomRight = QPointF(qMax(nameRect().right(), subScenarioRect().right()) + 10, subScenarioRect().bottom() + 10); return QRectF(TopLeft, BottomRight); }
Now, we finally get to the function that redraws a StateDiagram as follows:
void StateDiagram::redraw() { prepareGeometryChange(); dimension(); update(); }
This redraw function is explicitly called for both the SS SS and LL SS states in the screencast after the Ok button of the dialog is pressed. Any changes in the Model related to the texts in the StateDiagram are handled by the StateDiagram itself (i.e., the StateDiagram calls redraw itself after the Ok is pressed). The changes to the Model regarding the initial state (which results in drawing the internal rounded rectangle) is handled at a higher level as this involves two states (the current one from which the dialog was executed and another one i.e., the existing initial state). In this case, the Model is changed before invoking the redraw function for both involved StateDiagrams.
If the explanation to the code is not clear, please let me know.
-
Hi,
I haven't had the time to look at it. Hopefully next week.
By the way, could you show the code where the mouse-click is handled?
-
Sure, here it is. The dialog has some functions to retrieve what the user has done.
void StateDiagram::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { StateDialog *Dialog = new StateDialog(Specification); if (Dialog->exec()) { if ((Dialog->name() != Specification->name()) || (Dialog->subScenario() != Specification->subScenario())) { Specification->rename(Dialog->name()); Specification->setSubScenario(Dialog->subScenario()); redraw(); } if (Dialog->makeInitial()) emit changeInitial(Specification); } delete Dialog; }
-
Does the mousePressEvent does something to?
And are such events also implemeted in the QGraphicsScene or -View? -
Thanks for asking. I have not reimplemented the mousePressEvent and it does not seem to do anything...