Qt remove object with contains() when the position is set - c++

I have a scene and inside the scene I have the ellipses (circles) to which I change the position with setPos() so when I ask for its position later I will not get 0,0 coordinates, but now when I want to delete the object, the member function contains() does not ever evaluate to true understandably. The question is, how can I get to the scene coordinates or object coordinates so when I click on the object I get the true evaluation of contains() member function. I have tried the mapToScene(), mapFromScene() which do not help. (still kinda lost in Qt coordinate system)
Code sample:
void MyQGraphicsView::mousePressEvent(QMouseEvent * e)
{
QPointF pt = mapToScene(e->pos());
if(e->button()==Qt::RightButton)
{
// remove point
QList<QGraphicsItem *> listIt = scene->items();
QList<QGraphicsItem *>::const_iterator stlIter;
QGraphicsItem * itemToRemove = NULL;
QGraphicsEllipseItem it; // for type checking
for(stlIter = listIt.begin(); stlIter != listIt.end(); ++stlIter)
{
// if it has the expected type and the point is inside (type checking is redundant)
if(((*stlIter)->type() == it.type()) && ((*stlIter)->contains(pt))){
// contains(pt) is never true - understandably
itemToRemove = *stlIter;
break;
}
}
if(itemToRemove != NULL) scene->removeItem(itemToRemove);
}else{ // leftClick to add ellipse
double rad = 10;
QGraphicsEllipseItem* pEllipse = scene->addEllipse(-rad, -rad, rad*2.0, rad*2.0, QPen(Qt::red,0), QBrush(Qt::red,Qt::SolidPattern));
pEllipse->setPos(pt.x(), pt.y()); // set the postion so it does not return 0,0
}
}

The QGraphicsItem::contains method takes points in local coordinates, that is in coordinates with (0, 0) being the center of the QGraphicsItem.
You are using points in global scene coordinates.
To get a point in local coordinates of a given QGprahicsItem you can use the QGraphicsItem::mapFromScene(const QPointF & point) method.
You might want to do something like :
for(Object& obj : objects)
if(obj.contains(obj.mapFromScene(point)))
// do stuf because point is inside obj
Sources :
http://doc.qt.io/qt-4.8/graphicsview.html#item-coordinates
http://doc.qt.io/qt-4.8/qgraphicsitem.html#contains

Related

How to insert pointer in QGraphicsItem so when they get selected pointer will be accessed?

I want to select rectangle/polyline through scene with mouse click and should be able to print it's name and other property. It's name and other properties are in the graph node. But I dont want to interact graph again.
So when I was drawing rectangle/polyline through graph co-ordinates, I should be able to store some pointer of graph node on rectangle/polyline so when I will click on rectangle/polyline, then through that pointer I can access it's name and other properties.
Question is `Is this possible ?
Among all above parameters, I want to store only _Ptr ( it is basically a pointer, but store as long, while using it will be type cast )
rect = new myRect();
while(true)
{
for(auto iter = verts.begin();iter != verts.end();++iter)
{
// getting co-ordinates of rectangle
QGraphicsRectItem* rectItem = rect->createRect(co-ordinates of rectangle);
rect->_Ptr = iter->_Ptr; // trying to store _crossRefPtr
}
}
myRect.h
class myRect : public QGraphicsRectItem
{
........
QGraphicsRectItem* createRect(QRectF& rect);
}
And when I will click on rectangle on scene through mouse I am doing like this:
if(_scene->selectedItems().count() != 0)
{
foreach(QGraphicsItem* currentItem, _scene->selectedItems())
{
QGraphicsRectItem* rItem = qgraphicsitem_cast<QGraphicsRectItem*>(currentItem);
if(rItem)
{
myRect* r = reinterpret_cast<myRect*>(rItem);
if(r)
{
Instance (rectangle)
Instance* i = reinterpret_cast<Instance*>(r->_boostRefPtr);
qDebug()<< i->Name();
}
}
But aobve way is wrong. Getting run time errors ( Showing Verbose stack trace)
So the question is :
How to store pointer on QGraphicsItem so that, once they get selected,
that pointer will be accessed ?
Use Qt's dynamic properties, check QObject::setProperty. It should do the trick.
But AFAIC, I would have used a double QMap to associate directly <graph_node, QGraphicsItem> AND <QGraphicsItem, graph_node> - so you can search quickly for both associations, and both in O(log2(n)) complexity. You can store this either as a static part of your graph (better) or standalone (not the best idea). Obviously, if your graph is already in O(log2(n)), you don't need this map and you need only the <QGraphicsItem, graph_node> one.

Is there a way to stop QGraphicsScene?

I have an ellipse that moves from certain position upwards.
void Equalizer::advance(int phase)
{
if(!phase) return;
QPointF location = this->pos();
setPos(mapToParent(0 , -(speed)));
}
Though I want it to stop moving when it reaches certain y coordinate. How do I do that?
Don't update its y position, when it reaches the specified y coordinate,
void Equalizer::advance(int phase)
{
if(!phase) return;
QPointF location = this->pos();
if(location.y() <= specifiedY)
{
//If the speed at which the ellipse is moving is great enough to take it beyond the specifiedY, set it to the specifiedY before the return.
setPos(pos().x(), specifiedY); // assuming specifiedY is in scene coordinates
return;
}
setPos(mapToParent(0 , -(speed)));
}

QGraphicsItem collision jitter/tremble

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 to map item coordinates?

I know that every item has own coordinates relative to scene. I am adding an ellipse in the scene. Each of them returns the following from boundingRect(): QRect(0, 0, 50, 50). I don't know how to map coordinates to another QGraphicsItem which is a line. The line supposed to connect this two ellipses. I have correct coordinates of ellipses and I am passing them to a custom QGraphicsLineItem constructor. However the line is in a wrong place. How should I use mapFromItem() or other method to get the result?
I get each ellipse's coordinates as follows:
selfCoords = ellipse->mapFromScene(QPointF(0.0,0.0));
You should map the coordinates from each ellipse to some common coordinate system that you can then map to the line's parent. The common coordinate system can be the scene's coordinate system.
For example, to connect the centers of the ellipses:
QGraphicsScene scene;
QGraphicsEllipseItem e1, e2;
scene.addItem(&e1);
scene.addItem(&e2);
... // set the ellipse rects/sizes
auto start = e1.mapToScene(e1.boundingRect().center());
auto end = e2.mapToScene(e2.boundingRect().center());
QGraphicsLineItem l(QLineF(start, end));
scene.addItem(&l);
You can do this because the line's parent is the scene. Now assume that we had some other parent for the line - you'd need to map the coordinates to that parent instead.
...
QGraphicsItem p;
p(20, 20);
scene.addItem(&p);
auto start = e1.mapToItem(&p, e1.boundingRect().center());
auto end = e2.mapToItem(&p, e2.boundingRect().center());
QGraphicsLineItem l(QLineF(start, end), &p);
If I want to add new ellipse on mouse position, how to map coords to ellipse item to get right position on scene ? For example from contextMenuEvent I get
QPointF coords = event->scenePos(); and there I want to create ellipse. I have custom QGraphicsScene MyScene where I have pointer to QGraphicsView* view.
I use event form void MyScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
QPointF coords = event->scenePos();
QPointF ellpiseCoords = view->mapToScene(coords .x(), coords .y())
I always get wrong transform.

How to remove a node from its callback function?

There is a Geode whose Geometry is a ball with a MatrixTransform() assigned above it. It's callback function makes it falls. When the ball intersects with the ground, I hope to remove it from the scene.
The following code throws exception:
//inside the ball's callback
virtual void operator()(osg::Node* node ,osg::NodeVisitor* nv)
{
using namespace osg;
MatrixTransform* matrix_node = dynamic_cast<MatrixTransform*>(node);
Matrix matrix = matrix_node->getMatrix();
velocity += Vec3(0, 0, -0.002);
matrix.postMultTranslate(velocity);
matrix_node->setMatrix(matrix);
Vec3 now_position = start_position * matrix;
osgUtil::IntersectVisitor ivXY;
osg::ref_ptr<osg::LineSegment> lineXY = new osg::LineSegment(now_position, now_position+velocity);
ivXY.addLineSegment(lineXY);
GAME.main_camera->m_pHostViewer->getSceneData()->accept(ivXY) ;
if(ivXY.hits())
{
node->getParent(0)->removeChild(node);
}
return;
}
How to do it correctly? Thank you!
This is an excerpt from the OpenSceneGraph Group class (from which MatrixTransform inherits):
void Group::traverse(NodeVisitor& nv)
{
for(NodeList::iterator itr=_children.begin();
itr!=_children.end();
++itr)
{
(*itr)->accept(nv);
}
}
bool Group::removeChild( Node *child )
{
unsigned int pos = getChildIndex(child);
if (pos<_children.size()) return removeChildren(pos,1);
else return false;
}
Your code (which is called from traverse) throws an exception probably because the iterator gets invalidated in the middle of the loop, when removeChild is called. To remove the node you would have to wait at least until your node callback returns.
To resolve this I would just use a node mask to control whether the ball is displayed or not. Initially, set the ball node mask to the "visible" value. When the ball hits the ground, set the node mask to the "hidden" value. The node will not be traversed/rendered, but will still be in memory.
If memory is an issue, you can move the code to the parent node or outside the update traversal (e.g. use a modified Viewer::run method).