Qt: Custom QGraphicsItem not showing when boundingRect() center is out of view - c++

I'm making a Diagram (Fluxogram) program and for days I'm stuck with this issue:
I have a custom QGraphicsScene that expands horizontally whenever I place an item to it's rightmost area. The problem is that my custom arrows (they inherit QGraphicsPathItem) disappear from the scene whenever it's boundingRect() center is scrolled off the view. Everytime the scene expands, both it's sceneRect() and the view's sceneRect() are updated as well.
I've:
set ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate)
the item flags QGraphicsItem::ItemIgnoresTransformations and QGraphicsItem::ItemSendsGeometryChanges, setActive(true) on the item as well, and everytime I add an arrow to the scene i call the update(sceneRect()) method. Still, everytime I scroll the view, as soon as the arrow's boundingRect() center moves away from the view, all the arrow disappears. If I scroll back and the boundingRect() center enters the view, all the arrow appears again.
Can someone give me a tip of what I might be missing? I've been using Qt's example project diagramscene as reference, so a lot of my code is similar (the "press item toolButton -> click on the scene" relation to insert items, the way they place the arrows to connect the objects,...).
In the meanwhile I'll try to make a minimal running example that can show what my issue is.

Your Arrow object inherits from QGraphicsPathItem, which I expect also implements the QGraphicsItem::shape function.
Override the shape function in your Arrow class, to return the shape of the item. This, along with the boundingRect is used to collision detection and detection of an item on-screen.
In addition, before changing the shape of an item by changing its boundingRect, you need to call prepareGeometryChange.
As the docs state: -
Prepares the item for a geometry change. Call this function before changing the bounding rect of an item to keep QGraphicsScene's index up to date.
So, in the Arrow class, store a QRectF called m_boundingRect and in the constructor: -
prepareGeometryChange();
m_boundingRect = QRectF(-x, -y, x*2, y*2);
Then return m_boundingRect in the boundingRect() function.
If this is still an issue, I expect it's something in QGraphicsPainterPath that's causing the problem, in which case, you can simply inherit from QGraphicsItem and store a QPainterPath with which you draw in the item's paint function and also return the painter path in shape().

You are making your life too complicated. Do not subclass QGraphicsPathItem just use it and update its path value every time position of anchors (from to) changes.

Related

How to clear existing content before redrawing QGraphicsItem?

I have a drawing that is built inside a QGraphicsScene with several layers of QGraphicsItem derived objects.
I am repositioning the QGraphicsItem objects based on some parameters and have noticed that there are some "ghost" trails left unless I call graphicsArea->viewport()->update(). I am repositioning the QGraphicsItem objects quite frequently (i.e. when a slider is moved) and updating the viewport only works if I call it manually some time after drawing is finished (e.g. on a button click).
One possible solution that I found was to fill the background of each QGraphicsItem to be a neutral colour. This doesn't work when I have overlapping items though, as the underlying items can get overwritten.
Does anyone have any suggestions?
Thanks,
Alan

Qt QGraphicsScene origin point

Whenever I add a new item to a QGraphicsScene, the origin of the QGraphicsScene seem to change for the position of the item I have just added.
How to make the QGraphicsScene origin fixed?
Do I need to add the item first in the QGraphicsScene and then specify a position for the item?
Well, by default the content of the scene will be centered in the QGraphicsView. The origin of the graphics scene does not change randomly.
You might want to use setSceneRect() to define the size of the scene, so that the QGraphicsView always centers the scene in the view in a fixed manner. (If you don't set it manually, the rect will be calculated based on the items in the scene, which changes if you add more.)
I answered a related question about a year ago that may be helpful:
How to draw a point (on mouseclick) on a QGraphicsScene?
Ditto to what badcat.
There are a lot of controls for adjusting or manipulating your viewport(s) that you have pointing at your scene. The scene sets what is on the stage. The view is how you look at it. Be sure to set the sceneRect or set it indirectly using centerOn or fitInView or scale or translate from the QGraphicsView class.
http://qt-project.org/doc/qt-4.8/graphicsview.html
http://qt-project.org/doc/qt-4.8/qgraphicsview.html
QGraphicsScene::setSceneRect ( const QRectF & rect ) will make it absolute.
see http://doc.qt.digia.com/qt/qgraphicsscene.html#sceneRect-prop

Qt C++ Let multiple QGraphicsItem handle one mouse event

I've created an object Chartblock that implements QGraphicsItem. My goal is to create a grid of these objects, and when the mouse button is pressed (and held) and is drug over each block, perform something on each block as the cursor enters it.
Since a QGraphicsItem grabs the mouse events when it is clicked within it, other Items will not fire for the mouseMoveEvent. I then created an object based on the QGraphicsItemGroup to handle all the mouse events, but then I would need some way to pass mousePressEvent/mouseReleaseEvent as well as mouseMoveEvent to each child that the cursor is over.
Am I overthinking how to do this? It seems like such a simple action shouldn't be that difficult to create, but with QGraphicsItems holding onto the mouse events for itself, I'm not sure how to get around it. I've read similar situations, but nothing seems to give a straightforward answer.
Edit: I suppose a way to do this would keep track of the coordinates/sizes of every single QGraphicsItem I create in an array, then get the position of the cursor in the Group mouseMoveEvent, and see if there's a hit..
I was able to pull together a few similar answers to create a solution; I dropped the idea of placing all my QGraphicItem's in a Group, and placed them directly on a scene. With the scene grabbing all mouse events, I have the mouseMoveEvent check to see if the current position is on top of a QGraphicsItem - if so, perform something.
I still need to try and get itemAt() to work for my own classes that implement QGraphicsItem, as itemAt returns only QGraphicsItem's, but I'm sure some cast should get it working.
void ChartScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QPointF mousePosition = event->scenePos();
QGraphicsItem* pItem = this->itemAt(mousePosition.x(), mousePosition.y());
}

Accessing the the coordinates in a QPushbutton clicked slot

I have a QPushButton with an image that has two areas that I want to handle differently when clicked. Due to the positioning on the image, I cannot really use separate buttons for the two images.
What I'd like to do is, in the slot where I am handling the click, have it check the coordinates of the click to determine which area was clicked.
Is there a way to access this?
This is what first comes to mind. It should work, although there may be a simpler way:
Create your own class that derives from QPushButton.
Override the necessary mouse events (mousePressEvent and mouseReleaseEvent) and, before calling the base class implementation, set a property in your object using setProperty with the position of the mouse click. (The position is available from the event parameter.)
In the slot handling the event, use sender() to get the button, and read the property using property().
If you don't need to treat your object as the base class (QPushButton*) you could just create a new signal that includes the mouse event and attach that to your slot. Then you wouldn't need the property at all.
You can get the current mouse position using QCursor::pos() which returns the position of the cursor (hot spot) in global screen coordinates.
Now screen coordinates are not easy to use, and probably not what you want. Luckily there is a way to transform screen coordinates to coordinates relative to a widget.
QPoint _Position = _Button->mapFromGlobal(QCursor::pos());
This should tell you where on the button the mouse was when the user clicked. And you can take it from there.
Building on #Liz's simple mechanism, here's what I did; this is in a slot method that is called on pressed() but generalizes to other situations. Note that using pushButton->geometry() gives you coordinates that are already in global space so you don't need to mapFromGlobal.
void MainWindow::handlePlanButtonPress()
{
int clickX = QCursor::pos().x();
int middle = m_buttonPlan->geometry().center().x();
if ( clickX < middle ) {
// left half of button was pressed
m_buttonPlan->setStyleSheet(sStyleLargeBlueLeft);
} else {
// right half of button was pressed
m_buttonPlan->setStyleSheet(sStyleLargeBlueRight);
}
}

Overlapping labels in Qt fridge magnets example

I want to modify the fridge magnets example provided with Qt in a way that when I drag a label and drop it over another, it will push the label beneath the dragged label to the side, so they will never overlap one another.
I've seen how collision is detected in the colliding mice example, where it uses a QGraphicsScene to draw the QGraphicsItem mice on, and scene()->collidingItems(this) to see which mice are colliding.
The problem is that the fridge magnets example uses a class that inherits QWidget in place of QGraphicsScene, so there's no collidingItems() method to check when we have a collision.
How do I go about doing that?
You can get the location and size of each QWidget from geometry(), which returns a QRect. QRect has function intersects(), which will tell you if it intersects another QRect. After the drop is complete, iterate through all of the labels and check if any of them do intersect the new position.
(This will be easier if you modify dragwidget to keep a QList<DragLabel*> of each label on the dragwidget.)
QRect droppedRect = newLabel->geometry();
foreach(DragLabel* label, dragLabelList)
{
if (droppedRect.intersects(label->geometry())
{
// Add to the list of covered labels that need to be moved.
}
}
The harder part: If there is an intersection, move the old label out of the way.
Maybe try the following algorithm: Move the offending label out of the way in the direction that takes the least movement. Now check it against all the other labels. Any of those that are covered should be moved in the same direction. Repeat until all the labels are uncovered.