I tried to add several items to QGraphicsScene, but after calling scene->addItem(new Bonus(Bonus::BonusType::coin, randPoint, pixels, parent)); in application output appears this message: QGraphicsScene::addItem: item has already been added to this scene.What I do wrong?
Code:
for(int i = 0; i < coinsCount; ) {
QPoint randPoint(random() % g->getWidth(),
random() % g->getHeight());
if(g->getType(randPoint) != Graph::wall && !usedPoints.contains(randPoint)) {
scene->addItem(new Bonus(Bonus::BonusType::coin, randPoint, pixels, parent));
usedPoints.push_back(randPoint);
i++;
}
}
You are passing a parent item. If the parent item is already added to the scene, setting it as a parent for the new item will add the latter to the scene as well.
The constructor runs before addItem() so by the time the latter is executed, the item is already in the scene.
Note that this implicitly adds this graphics item to the scene of the
parent. You should not add the item to the scene yourself.
Related
Hello guys I need your help,
I'm creating a timeline like widget in Qt based on the QGraphics framework. My problem is to handle collisions of items (inherited from QGraphicsRectItem) in my Timeline tracks.
I use the itemChange() function to keep track of the collisions. To keep the items in the parent boundingRect I use the following code wich works like a charm
if (change == ItemPositionChange && scene())
if (thisRect.intersects(parentRect)) {
const QPointF offset(mapFromParent(thisRect.topLeft()));
QPointF newPos(value.toPointF());
if (snapToGrid) {
newPos.setX(floor(qMin(parentRect.right() - offset.x() - thisRect.width(),
qMax(newPos.x(), parentRect.left() / 2 - offset.x())) / (snapValue * pxPerSec(duration))) * snapValue * pxPerSec(duration));
}
else {
newPos.setX(qMin(parentRect.right() - offset.x() - thisRect.width(),
qMax(newPos.x(), parentRect.left() - offset.x())));
}
newPos.setY(parentItem()->boundingRect().height() * 0.1);
return newPos;
}
}
This stops the items immediately if they reach the left or right boundary of my timline tracks, even if I move the mouse outside my view/scene. It's like an invisible wall.
Now I want the same behaviour if one item in a track collides with another.
const QRectF parentRect(parentItem()->sceneBoundingRect());
const QRectF thisRect(sceneBoundingRect());
foreach (QGraphicsItem *qgitem, collidingItems()) {
TimelineItem *item = qgraphicsitem_cast<TimelineItem *>(qgitem);
QPointF newPos(value.toPointF());
if (item) {
const QRectF collideRect = item->sceneBoundingRect();
const QPointF offset(mapFromParent(thisRect.topLeft()));
if (thisRect.intersects(collideRect) && thisRect.x() < collideRect.x()) {
newPos.setX(collideRect.left() - offset.x() - thisRect.width());
}
if (thisRect.intersects(collideRect) && thisRect.x() > collideRect.x()) {
newPos.setX(collideRect.right() + offset.x());
}
}
newPos.setY(parentItem()->boundingRect().height() * 0.1);
return newPos;
}
The problem is that if I move an item via mouse against another item you see them intersecting/overlapping and then the item I moved snaps back to the minimum not intersecting distance. How do I manage to stop the moving item immediately if it hits another (no trembling forth and back movement intersecting thing). Just like the way the items are kept in parents boundingRect (first code block), the invisible wall like behaviour?
I think the problem here is with "thisRect". If you're calling this from an ItemPositionChange, then sceneBoundingRect is returning the bounding rectangle of the item in its previous position, not the new one. What happens is that the current position succeeds even though though there's a collision, but the next one fails because you're always checking the previous results, and then it snaps back to avoid the collision.
After getting the local item's scene rectangle, you'll need to translate it to the new future position of the item:
QPointF new_pos (value.toPointF ());
QRectF thisRect (sceneBoundingRect());
thisRect.translate (new_pos - pos());
I've moved the creation of "new_pos" outside the loop so that it's available for the rectangle translation. It's also faster.
How I can find a particular item in the scene and remove it .
I have declared a graphicsitem and added it to the scene. Now in certain case I have to remove the item from the scene but before removing the item from the scene I want to know the item is added to scene or not. If I try to remove the item which is not added to the scene, I get the below error:
"QGraphicsScene::removeItem: item 0x121c520's scene (0x0) is different from this scene (0x1143850)"
the item is not selected so I can not use the scene()->selectedItem() list.
You can check the pointer returned from calling QGraphicsItem::scene(). It will either return the scene, or NULL if it is not present in a scene.
// assuming item is a class derived from QGraphicsItem
if(item->scene() != nullptr) // nullptr from C++ 11, else use NULL
{
// item is in a scene
}
I am trying to move the listwidget item programmatically. I am able to move the listwidget successfully if the move is in current view. If i try to move the list widget item across view ( i.e Using scrollbar ), move is not working as expected. i.e List widget item is not reflecting
code snip:
void func(int fromPage, int toPage)
{
QListWidget* expListWidget =i.next();
QListWidgetItem* widgetItem = expListWidget->takeItem(fromPage);
expListWidget->insertItem(toPage,widgetItem);
}
Here is an example how to move items up and down independently of where they are located:
QListWidget* lw1 = new QListWidget;
for (int i = 0; i < 500 ; i++)
{
QListWidgetItem* item = new QListWidgetItem(QString::number(i));
lw1->addItem(item);
}
//move from lower part to the top
QListWidgetItem* i = lw1->takeItem(400);
lw1->insertItem(0, i);
//move from the top to the lower part of the list
i = lw1->takeItem(1);
lw1->insertItem(400, i);
what I want to do is the following:
I have a little GUI with a QGraphicsView. In this graphics View I load a picture:
// m_picture is QPixmap
// image is QImage
// m_graphic is QGraphicsScene
// graphicsView is QGraphicsView
m_picture.convertFromImage(image);
m_graphic->addPixmap(m_picture);
ui->graphicsView->setScene(m_graphic);
This doesn't cause any problems and I can always load a new image without problems.
Now in addition to just display the pictures I want to give the user the ability to draw a rectangle on them ( to "focus" on a specific area ). Actually the user just types in the coordinates in four text boxes on the GUI ( x,y, width,heigth). After providing the coordinates the User presses a button and the rectangle at the following coordinates shall be displayed.
I accomplished this with this code:
void tesseract_gui::show_preview_rect()
{
int x,y,h,w;
x = ui->numBox_x->value();
y = ui->numBox_y->value();
h = ui->numBox_h->value();
w = ui->numBox_w->value();
if( rect_initialized )
{
m_graphic->removeItem(m_rect);
}
else
{
rect_initialized = true;
}
m_rect->setPen(QPen(Qt::red));
m_rect->setRect(x,y,h,w);
m_graphic->addItem(m_rect);
return;
}
The remove call is because I always want to display just one rectangle.
Now as I mentioned this works fine. But if the user now loads another picture ( with the calls at the top of my post ) the program crashes when I try to draw a new rectangle. I
get a Segmentation fault at the call of
m_rect->setPen(QPen(Qt::red));
If I call
m_graphic->removeItem(m_rect);
after loading a new picture I get
QGraphicsScene::removeItem: item 0x8c04080's scene (0x0) is different from this scene (0x8c0a8b0)
and then it crashes with the same Error at setPen.
What I don't get is, I don't change the scene. I just add another picture to it ( or overwrite it) .
Well any suggestions how I could do this right?
Best Regards
// edit:
I tried to do it just with everytime a new rectangle like this:
void tesseract_gui::show_preview_rect()
{
int x,y,h,w;
x = ui->numBox_x->value();
y = ui->numBox_y->value();
h = ui->numBox_h->value();
w = ui->numBox_w->value();
m_graphic->clear();
m_graphic->addRect(x,y,h,w);
return;
}
Problem at this is that with the clear() call it also clears the picture itself from my GraphicsView... so no solution there
// edit:
As suggested I got rid of the Warning like this:
if( m_rect->scene() != 0 )
{
m_graphic->removeItem(m_rect);
}
m_rect->setPen(QPen(Qt::red));
m_rect->setRect(x,y,h,w);
m_graphic->addItem(m_rect);
I know it's not the best way but I tried it also this way ( did not work for me ):
I added the item in the constructor:
m_graphic->addItem(m_rect);
and then
m_rect->setPen(QPen(Qt::red));
m_rect->setRect(x,y,h,w);
m_graphic->update();
and I get the "same" error as always ( Program crashes at m_rect->setPen() )
So it seems the problem always occurs when I already added the rectangle to the graphic, THEN changed the image of m_graphic and then did any operation with m_rect. ( Actually I guess m_graphic takes ownership of m_rect and so this causes the segmentation fault ... ? )
The message QGraphicsScene::removeItem: item 0x8c04080's scene (0x0) is different from this scene (0x8c0a8b0) tells you m_rect is not in any scene at the time you call it. It's probably removed somewhere else in your code or you have 2 variables with the same name in the class hierarchy.
Also, you don't need to remove it from scene to change it. Just change it while it's in the scene. It will get repainted with the new color and geometry in the next paint event.
Even if you REALLY want to remove it before changing it, just check if it's in a scene by calling QGraphicsItem::scene(). There's no need for the init check variable.
In my Qt Application I am dynamically creating QGraphicsView(s) and adding them inside
a QGridLayout.
When I add first view inside grid, the view covers all the available space inside grid.
Then I add second view and there are now two equally sized views inside grid.
Then I add third view and there are now three equally sized views inside grid.
And so on.
How can I get updated size of first view?
Below is my trial but I think this is not working.
//Definition of viewsPerRow
static const int viewsPerRow = 3;
void window::newViewRequested()
{
QGraphicsView *view = new QGraphicsView;
view->setVisible(true);
viewVector.push_back(view);
for(int i = viewGrid->count(); i < viewVector.count(); i++)
{
viewGrid->addWidget(view,i / viewsPerRow ,i % viewsPerRow);
}
qDebug()<<viewGrid->cellRect(0,0);
}
You probably should use the QWidget::rect() methods to get the geometry of the views themselves. Here is a sample that should work as intended.
QVector< QGraphicsView* > views;
for (int i=0 ; i < 3; ++i) {
QGraphicsView *view = new QGraphicsView;
view->setVisible(true);
viewGrid->addWidget(view, i ,0);
views.push_back(view);
}
qDebug() << "The view size of the first view is " << views[0]->rect();
Not sure why you need the size of the first view, but Qt does only few redraws at the time it "thinks" it is suited. Therefore you can't rely on a size you query outside the view.
I'd try to use the resizeEvent to get notified when the size of an item changes and perform the necessary actions then.