QGraphicsItem position after changing boundingRect - c++

I have class derived from QGraphicsItem. It contains vector of points wich I draw in paint:
for(int i = 0; i < _vertexes.size(); i++)
{
...
painter->drawEllipse(_vertexes[i], POINT_RADIUS, POINT_RADIUS);
}
when I add point in _vertexes with this code
prepareGeometryChange();
_vertexes.pop_back();
position of points in the view is changing, boundingRect is calculated using _vertexes
How to save points positions? I don't want all points change position after adding new one if new boundingRect is bigger. By the pos() returns always the same position (0, 0) but it could be in a different position of screen.

I don't set initial sceneRect, so it was recalculated and scrolled after each increasing scene items bounding rect. Setting scene rect by ui->graphicsView->setSceneRect(x, y, width, heigh);
before adding of my items solves the problem,

Related

How to appropriately get position of QGraphicsRectItem after drag-release?

I wanted to have an online monitoring system that could tell where the shape is currently, but am getting very weird coordinates of the item, also the dimensions of it get higher by 1 each time I create new one and drag it.
Initial position (map size is 751 by 751, checked by outputting to qDebug(), scene bound to yellow space) :
Dragging it to the left top corner.
As you can see in the beginning it was on (200;200), but after dragging it is on (-201;-196). After deleting it and creating new shape on the same position with the same properties, new shape can't be seen because it is outside of the map, which suggests that edits don't show correct data.
Here is the code of updating the edits:
void CallableGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsRectItem::mouseReleaseEvent(event);
ptr->updateEdits(this);
}
Here is what I managed to cut down into updateEdits():
void MainWindow::updateEdits(QAbstractGraphicsShapeItem* item)
{
//stuff not related to scene
auto posReal = item->scenePos();
auto pos = posReal.toPoint();
//create QString from coordinates
QString coordinate;
coordinate.setNum(pos.x());
ui->leftXEdit->setText(coordinate);
coordinate.setNum(pos.y());
ui->upperYEdit->setText(coordinate);
//get width and height for rect, radius for circle
auto boundingRectReal = item->sceneBoundingRect();
auto boundingRect = boundingRectReal.toRect();
ui->widthEdit->setText(QString::number(boundingRect.width()));
//disables height edit for circles, not really relevant
if (!items[currentShapeIndex].isRect)
{
ui->heightEdit->setDisabled(true);
}
else
{
ui->heightEdit->setDisabled(false);
ui->heightEdit->setText(QString::number(boundingRect.height()));
}
}
Here is how I anchor the QGraphicsScene to the left top corner of the yellow area:
scene->setSceneRect(0, 0, mapSize.width() - 20, mapSize.height() - 20);
ui->graphicsView->setScene(scene);
How can I report the right data to the edits?
You're better off overriding the itemChange method and using the ItemPositionHasChanged notification. You have to set the ItemSendsGeometryChanges flag on the item so that it receives these notifications.
I'm not sure that your item's final position has been set when you're still in the mouseReleaseEvent method. Tracking it in itemChange will ensure that the data is valid, and this kind of thing is what it's for.
Also, note that "pos" is in the item's parent coordinates, and "boundingRect" is in the item's coordinate space. You should use "scenePos" and "sceneBoundingRect" if you want to be sure you're using scene coordinates. If the item doesn't have a parent, then "pos" and "scenePos" will return the same values, but "boundingRect" and "sceneBoundingRect" will generally differ.

OpenGL - Moving Primitives along the X and Y using arrow keys

I'm building a 2D drawing canvas that allows users to draw shapes on the screen by first selecting which shape you want to draw, and then clicking into the drawing area to draw your shape. Here's what the UI looks like for a better visualization:
So far, I have some code that allows the user to click on the derired shape, draw the shape, then click on another shape to draw, and so on...
First I check if a button has been clicked on in my processMouse() function:
if (x >= 0 && x <= 95 && y >= 302 && y <= 380) { // area of SQUARE shape
// squareShape = false by default
squareShape = true;
}
else squareShape = false;
If it has, we highlight the shape in my display() function:
if (squareShape != false) {
drawHighlight(100, 50, 350, true); // highlighted SQUARE
}
and in the same function, we enable some points to be drawn:
if (areaClicked2 != false) {
for (int i = 0; i < points.size();i++)
{
//areaClicked2 = true;
glLineWidth(1);
glBegin(GL_LINES);
glVertex2i(points[i].x + lineOffset.x, points[i].y + lineOffset.y);
}
}
I have applied this exact same method for every other button, and they all seem to be working fine...
My issue?
If I click on a button to draw squares, it draws a few squares, but when I click on an another button, my squares disappear because I don't have the square button selected. So for every button when I click to draw, the shapes are only visible until I click on an another button. How can I fix this?
EDIT:
Everytime I want to make a new points vector for a new shape, I make a new vector like this:
std::vector <point> lines; // old vector
point OnePoint;
std::vector <point> dots; // new vector
point OneDot;
Should I not be doing this? If not, how should I be? I tried to keep lines and just make new point variables but then when I draw, they're exactly the same point.
Just a guess, I think you are clearing(or creating new) points[] collection on each shape selection. You should preserve old point collection and append new shape points so that it will be visible even on different shape selection.

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 set the coordinate system?

I have a class MaskGraphicsWidget.cpp inheriting from QGraphicsView which contains several functions, the constructor is:
MaskGraphicsWidget::MaskGraphicsWidget(QTreeWidget * tree, QWidget* parent) : QGraphicsView(parent), m_tree_widget(tree){
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_mask_scene = new QGraphicsScene;
m_mask_scene->setSceneRect(0, 0, 250, 250);
setScene(m_mask_scene);
primitive = 0;
minX = 0;
minY = 0;
}
where I initialize my scene m_mask_scene and I set the coordinate system.
I also have a function MousePressEvent :
http://pastebin.com/wjwDTQzw
And my MaskGraphicsWidget.h is like that :
QGraphicsScene* m_mask_scene;
QList<QPointF> m_polygon_points;
QList<QGraphicsLineItem*> m_polygon_lines;
int minX;
int minY;
My problem is that, I want to set the Pos of the QGraphicsPolygonItem poly but if I set it to the value min and max (that I calculate somewhere else), the item moves from the current Pos to the Pos of min and max. Basically it sets the Pos of the item in his own coordinate system. How can I write that I want to set the Pos in the coordinate system of the m_mask_scene ?
Sorry for my English, if you did not understand, feel free to add a comment !
Edit 1 :
m_polygon_points and m_polygon_lines are filled somewhere else and it's working. (see pastebin)
Edit 2 : Added the plan, see below for better (I hope) understanding !
Explanation of the plan : the black polygon is what I got (I do not set any Pos), but if I do set the Pos at min and max, I get the red polygon. I want to set the Pos at min and max AND still have the black polygon ! Sorry for my poor paint skills
Edit 3 : Of course, if I print the Pos of the QGraphicsPolygonItem poly, it shows (0,0).
Edit 4 : Added a pastebin to not overload the post, I've put everything in it, please ignore the PrimitiveItem things, and the case 0 and 1 which are not for the QGraphicsPolygonItem.
Edit 5 : To clarify, I catch the event on MousePressEvent:
if it's a left click, I use it to create a new point (creating new lines for my polygon)
if it's a right click, I don't use the event but instead I just close my polygon, delete all the lines added to the scene and adding the polygon in the scene.
QPointF point(event->x(), event->y());
With event being a QMouseEvent, a call to x() and y() returns coordinates relative to the widget that receives the event. In this case, MaskGraphicsWidget, which is a QGraphicsView.
If you add an item to a scene and want to set its position, you're setting its position relative to its parent. In the case of an item with no parent, the position is relative to the scene (in scene coordinates).
So your current code is trying to set view coordinates, not scene coordinates. You need to convert the position of the mouse coordinates to the scene: -
QPointF scenePoint = mapToScene(event->pos());
Then use this to set the position of the newly created item.
Note that a QGraphicsView is like a window looking into a world (the QGraphicsScene), so it may not match the scene coordinates.

QGraphicsView ensureVisible() and centerOn()

I am going to do pan/scale stuff on QGraphicsView.
So I read the documentation of QGraphicsView and see some utility functions like ensureVisible() and centerOn().
I think I understand what the documentation says but I can' t manage to write a working example.
Could you please write/suggest me an example code to understand the issue.
Ton pan the view by a certain amount (for example in your view's mouseMoveEvent()), assuming MyView is a subclass of QGraphicsView (all the following code was ported from Python, I didn't test it):
void MyView::moveBy(QPoint &delta)
{
QScrollBar *horiz_scroll = horizontalScrollBar();
QScrollBar *vert_scroll = verticalScrollBar();
horiz_scroll->setValue(horiz_scroll.value() - delta.x());
vert_scroll->setValue(vert_scroll.value() - delta.y());
}
To fit a rectangle specified in scene coordinates by zooming and panning:
void MyView::fit(QRectF &rect)
{
setSceneRect(rect);
fitInView(rect, Qt::KeepAspectRatio);
}
Note that if your scene contains non transformable items (with the QGraphicsItem::ItemIgnoresTransformations flag set), you'll have to take extra steps to compute their correct bounding box:
/**
* Compute the bounding box of an item in scene space, handling non
* transformable items.
*/
QRectF sceneBbox(QGraphicsItem *item, QGraphicsItemView *view=NULL)
{
QRectF bbox = item->boundingRect();
QTransform vp_trans, item_to_vp_trans;
if (!(item->flags() & QGraphicsItem::ItemIgnoresTransformations)) {
// Normal item, simply map its bounding box to scene space
bbox = item->mapRectToScene(bbox);
} else {
// Item with the ItemIgnoresTransformations flag, need to compute its
// bounding box with deviceTransform()
if (view) {
vp_trans = view->viewportTransform();
} else {
vp_trans = QTransform();
}
item_to_vp_trans = item->deviceTransform(vp_trans);
// Map bbox to viewport space
bbox = item_to_vp_trans.mapRect(bbox);
// Map bbox back to scene space
bbox = vp_trans.inverted().mapRect(bbox);
}
return bbox;
}
In that case the bounding rect of your objects becomes dependent on the view's zoom level, meaning that sometimes MyView::fit() won't fit exactly your objects (for example when fitting a selection of objects from a largely zoomed out view). A quick and dirty solution is to call MyView::fit() repeatedly until the bounding rect naturally "stabilizes" itself.