Draw shift when drawing on QGraphicsView - qt

I have a problem.
I have a class that inherits QGraphicsView, let's suppose it called "g". I reimplemented mousePressEvent method, the code of that method is:
void GraphWidget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::MiddleButton)
createNode(event->pos().x(), event->pos().y());
update();
QGraphicsView::mousePressEvent(event);
}
The code of createNode method is:
Node *GraphWidget::createNode(qreal x, qreal y, int id)
{
Node* node = new Node(this);
scene()->addItem(node);
node->setPos(x, y);
return node;
}
I use this class "g" as a central widget in my mainwindow class. So it's working like QGraphicsView.
The problem is when I press the middlebutton on the "draw area" - the point is created but not in the place when I clicked - the point is shifted. Why? So when I try to draw those points by pressing the middlebutton - all them are drawed on the wrong place (not under my cursor, they are drawn lefter left and above my cursor position).
How can I fix that?

QGraphicsView and QGraphicsScene have different coordinate spaces. When you call setPos, it should be in scene coordinates, but since your in the mouse event of the view, your x and y are going to be in view coordinates.
I suspect that mapping your x and y coordinates to the scene space should fix the issue:
node->setPos( mapToScene(QPoint(x, y) );

Related

Which coordinate do sceneRect and boundingRect work on?

According to the Qt's documentation, the QPainter is working on the logical coordinate.
But how about the sceneRect of the QGraphicsScene and the boundingRect of the QGraphicsItem?
Are they working on the logical coordinates or the physical coordinates?
If it's on the logical coordinates, is there any functions like the QPainter::setWindow for them?
A GraphicsItem's boundingRect defines its area in local coordinates; local to the item. So, an item derived from QGraphicsItem, which overrides its paint function, can draw the item's area by drawing its boundingRect: -
painter->drawRect(boundingRect());
The sceneRect of a QGraphicsItem is the item's boundingRect translated into scene coordinates.
So, for example, from this skeleton class:
class MyItem : public QGraphicsItem
{
public:
QRectF boundingRect() const { return m_boundingRect; }
private:
QRectF m_boundingRect = QRectF(-10, -10, 20, 20);
}
The bounding rect is defined such that its centre lies at (0,0) in local coordinates.
If we add it to a scene, at position (0,0), calling the item's sceneBoundingRect function will return the same coordinates.
Now, if we move the item 5 units in the x: -
pItem->setPos(5, 0);
The boundingRect returns the same local coordinates, but its sceneBoundingRect will return its position in the scene; (-5, -10, 20, 20), with these being (x, y, width,height).
If an item is a child of another item, then this will be taken into account, as setting its position sets it relative to its parent or, in the case of no parent, will set it as the coordinates in the scene.
Therefore, calling an item's boundingRect() function, will always return the same coordinates, regardless of where the item resides in the scene, but it's sceneBoundingRect will return scene coordinates; where it resides in the scene.
If it's on the logical coordinates, is there any functions like the QPainter::setWindow for them?
Yes, the QPainter has its own transformation system, which allows you to perform actions such rotation or scaling before drawing. You can read more about its coordinate transformation in the Qt documentation for QPainter
sceneRect() and boundingRect() work in the scene coordinates (logical coordinates). However if you draw in a scene the QPainter also resides in these cordinates, it does not know the physical coordinates.
You probably want to use setWorldTransform() instead of setWindow(). While setWindow() might still work as intended, it does not support floating point coordinates, which is what you get from boundingRect() and friends.
To get back to physical coordinates from the QGraphicsScene, you can use QGraphicsView::mapToGlobal().

Items in a QGraphicsScene near the mouse

I am trying to find the items under the mouse in a scene. The code I am using is as follows:
QPainterPath mousePath;
mousePath.addEllipse(mouseEvent -> pos(),5,5);
QList<QGraphicsItem *> itemsCandidate = this->items(mousePath);
if (!(itemsCandidate.contains(lastSelectedItem))) lastSelectedItem =itemsCandidate.first();
PS: this refers to a scene.
The code should find the items intersected by a small circle around the mouse position and keep the item pointer unchanged if the previous intersected one is still intersected, or take the first in the QList otherwise.
Unfortunately, this code does not work with items inside each other. For example, if I have a Rect side a Rect, the outer Rect is always intersecting the mouse position even when this one is near the inner Rect. How can i solve this?
UPDATE: This seems not to be a problem with polygons, but with Rect, Ellipses, etc.
UPDATE: This code is in the redefined scene::mouseMoveEvent
You can reimplement ‍mouseMoveEvent in ‍QGraphicsView‍ to capture mouse move events in view and track items near the mouse like:
void MyView::mouseMoveEvent(QMouseEvent *event)
{
QPointF mousePoint = mapToScene(event->pos());
qreal x = mousePoint.x();
qreal y = mousePoint.y();
foreach(QGraphicsItem * t , items())
{
int dist = qSqrt(qPow(t->pos().x()-x,2)+qPow(t->pos().y()-y,2));
if( dist<70 )
{
//do whatever you like
}
}
QGraphicsView::mouseMoveEvent(event);
}

Using QGraphicsScene ItemAt() to detect QGraphicsLineItem

I have a QGraphicsScene which stores QGraphicsLinesItems and QGraphicsRectItems.
I am using the QGraphicsScene method to itemsAt() and I pass through x and y co ordiantes to return the QGraphicsItem and I use the following:
QPointF getItemPos= this->mapToScene(this->mapFromGlobal(QCursor::pos()));
QGraphicsItem *itm = scene->itemAt(getItemPos.x(),getItemPos.y());
QGraphicsLineItem *lineItm;
QGraphicsRectItem *rectItm;
if((lineItm = dynamic_cast<QGraphicsLineItem*>(itm))){
// do stuff with as_pnedge
qDebug("Line");
}else if((rectItm = dynamic_cast<QGraphicsRectItem*>(itm))){
// do stuff with as_pnitem
qDebug("Rect");
}
else
{
qDebug("Select Item");
}
The issue I am having is that QGraphicsRectItem is returned fine and can be detected. But no matter where I click around the QGraphicsLineItems it can never detect and return the item. Any assistance would be very much appreciated.
If your line uses a cosmetic pen (width of zero), it means that the shape() method will return a zero width QPainterPath (source code, search for "qt_graphicsItem_shapeFromPath").
You will have to derive from QGraphicsLineItem and reimplement shape() to clamp the minimum pen width for QPainterPathStroker to something reasonable.

Moving a QPixmap to the corner of a QGraphicsScene

I have a QGraphicsView and a QGraphicsScene connected like this:
graphicsScene->setSceneRect(this->graphicsView->rect());
graphicsView->setScene(this->Scene);
Then I load an image and add it to the scene:
QPixmap pixmap;
pixmap.load(fileName);
pixmap = pixmap.scaled(this->graphicsView->size());
QGraphicsPixmapItem* item = this->Scene->addPixmap(pixmap);
Now, as described in the documentation, the image corner is at (0,0), which is not the corner of the graphicsScene. I know I can position the resulting pixmap by doing:
item->setPos(this->Scene->sceneRect().x(), this->Scene->sceneRect().y());
However, I can't seem to make sense of the coordinates of the rect's of the scene or the view. Can anyone explain how I would move the pixmap to the corner of the scene/view?
Thanks,
David
EDIT: Here is the full form constructor. The QGraphicsView was created in Qt Designer and is inside of a GridLayout:
Form::Form(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
QGraphicsScene* scene = new QGraphicsScene;
scene->setSceneRect(this->graphicsView->rect());
this->graphicsView->setScene(scene);
QPixmap pixmap;
pixmap.load("image.png");
pixmap = pixmap.scaled(this->graphicsView->size());
scene->addPixmap(pixmap);
}
I also tried this:
QGraphicsScene* scene = new QGraphicsScene;
this->graphicsView->setScene(scene);
QPixmap pixmap;
pixmap.load("/home/doriad/glasses.jpg");
QGraphicsPixmapItem * item = scene->addPixmap(pixmap);
this->graphicsView->fitInView (item);
but the image appears tiny, rather than filling up the view like I would expect. Can anyone explain this?
The full project and image are available here: daviddoria.com/Uploads/qt/QPixmapPosition
Don't worry about scaling the pixmap yourself or even translating it, let the view do it for you.
Use graphicsView->fitInView(pixmap); but you should read the documentation for :
Qt's Graphics View Framework
void QGraphicsView::setSceneRect (QRectF )
void QGraphicsView::translate ( qreal dx, qreal dy )
void QGraphicsView::fitInView ( const QGraphicsItem * item, ... )
The way that QGraphicsScene and QGraphicsView interact is that you can have a single scene with at least one or more views.
A good example I like to think of is a zoomed in view of part of a map with a mini view of the entire map in the corner. There are two views, one of part of the map and one of the entire map, with one scene, the map itself.
So you put items in your scene and all the items in the scene are drawn relative in size to each other. The "scene rect" of your view, by default, scales to fit the items in the view until one unit in the scene is one pixel in the view or until it needs to zoom out to fit all the items in your scene.
If you call fitInView(someItem) it should scale your view of the scene so that the item specified fills it up and translates the view so that it is centered. If you need to translate or scale it more use the translate or scale functions in QGraphicsView.
When you are jumping between coordinate systems of your scene and view with your QRect's or QPoint's, use the helper functions: mapToScene and mapFromScene from QGraphicsView.
Try this:
QGraphicsScene* scene = new QGraphicsScene;
scene->setSceneRect(graphicsView->sceneRect());
QPixmap pixmap;
pixmap.load("/home/doriad/glasses.jpg");
pixmap=pixmap.scaledToWidth(this->graphicsView->width());
QGraphicsPixmapItem * item = scene->addPixmap(pixmap);
graphicsView->setScene(scene);
By default, the pixmap will be at (0,0) in the scene, and the scene will be at (0,0) in the view. QWidgets are sized by pixels. If you had a QGraphicsView the size of the screen and the resolution is 1440 x 900, you can position objects in that view from (0,0) or the top left corner of the screen, to (1440,900) the bottom right corner of the screen. Most QGraphicsItems are placed with reference to their top left corner. So placing a pixmap at (0,0) aligns the top left corner of the pixmap with the top left corner of the scene it's placed in. If your pixmap 'hangs off the bottom' of your view, try using:
pixmap=pixmap.scaledToHeight(this->graphicsView->height());
If you use the function:
this->graphicsView->fitInView (item);
The graphicsView will only scroll to the point where your item fits in the view.
I downloaded David's code and ran it two ways. Click the links to see the results.
1) with pixmap=pixmap.scaledToHeight(this->graphicsView->height());
2) with pixmap=pixmap.scaledToHeight(200);
I don't know enough to explain why this is happening, but I thought it would be a useful data point.
It turns out the problem was that the GraphicsView was in a layout. In my example, the resizing of the image was done in the Form constructor. Apparently this is before the layout takes its shape? I moved the code to a pushButton and when I click it the image is sized how I would expect.
I got the image to stay sized to the GraphicsView in the Layout by subclassing QGraphicsView and reimplementing :
class CustomGraphicsView: public QGraphicsView
{
Q_OBJECT
{
void resizeEvent ( QResizeEvent * event )
{
emit resized();
}
signals:
void resized();
}
Then I connect this resized() signal to a slot that simply calls this->View->fitInView (this->ImageToTraceItem);
David

Meaning of first two parameters of QGraphicsScene constructor

I can construct a QGraphicsScene by using constructor below:
QGraphicsScene::QGraphicsScene ( qreal x, qreal y, qreal width, qreal height, QObject * parent = 0 )
For example:
QGraphicsScene scene(-350, -350, 700, 700);
I know the first two parameters represent a point, but in which coordinate system?
When I create a QGraphics object and show this view like this:
QGraphicsView view(&scene);
view.show();
Where will this view appear on my device?
Is the location controlled by first two parameters mentioned above?
The first two parameter mean, when a view focus on this scene, which point in the scene is the top-left corner of the view.
In your code, the -350,-350 will be the top-left corner of the view window.
Here's another example:
QGraphicsScene *scene = new QGraphicsScene;
scene->setSceneRect(-360,-240,720,480);
QGraphicsView *view = new QGraphicsView(this);
view->setScene(scene);
if you try to add item without telling where to show it, it will show at (0,0). And in the code above, is the center of the view.

Resources