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().
Related
I am drawing a line using mouse clicks. The line is drawn using paint function as:
painter->drawLine(start_p, end_p);
The bounding rect of line is defined as:
QRectF Line::boundingRect() const
{
// bounding rectangle for line
return QRectF(start_p, end_p).normalized();
}
This shows the line painted. I get the bounding rect for this as shown:
I want to have the bounding rect according to the shape of the item, something like:
How to achieve this?
Edit
While selecting any of the overlapping lines, the one with bounding rect on top is selected(see figure below). Even making use of setZValue won't work here.
I want to implement this by minimizing the bounding rect to the shape of line.
If you have an item that is not shaped like a rectangle, or is a rotated rectangle use QGraphicsItem::shape.
This function should return a QPainterPath. You should be able to create your path by using QPainterPath::addPolygon.
Here is a small example:
QPainterPath Item::shape() const
{
QPainterPath path;
QPolygon polygon;
polygon << QPoint(0, 0);
polygon << QPoint(5, 5);
polygon << QPoint(width, height);
polygon << QPoint(width - 5, height - 5);
path.addPolygon(polygon);
return path;
}
You of course should calculate your points inside the path in a different way, but you get the point. Now when you click on an item, it will only select it if the click happened inside the shape defined by the QPainterPath.
If you ever need to make curvy lines, you can use QPainterPathStroker::createStroke as suggested by cmannett85.
There are two relevant functions in a QGraphicsItem that you should be interested in. The first is boundingRect. This, as you probably realise is a rectangle which encompasses the whole item. Qt uses this for such things as quickly calculating how much of an item is visible and simple item collision.
That's great if you have rectangular items; you can just override boundingRect() in any items you inherit from QGraphicsItem or QGraphicsObject.
If you have a shape that isn't regular and you want to do things such as collision with an item's shape, then theshape() function needs overriding too in your class.
This returns a QPainterPath, so you can do something like this: -
QPainterPath Line::shape()
{
QRectF rect(start_p, end_p).normalized();
// increase the rect beyond the width of the line
rect.adjust(-2, -2, 2, 2);
QPainterPath path;
path.addRect(rect);
return path; // return the item's defined shape
}
Now, you can use a painter to draw the shape() item, instead of the boundingRect() and collision will work as expected.
boundingRect is always used for optimize painting process of of scene. So you have have no room for manipulation here.
BUT if you want change area for mouse interaction there is shape method. By default this method returns QPainterPath rectangle received from boundingRect method.
So just override this method and provide desired shape.
QPainterPath YourGraphicsItem::shape() const {
static const qreal kClickTolerance = 10;
QPointF vec = end_p-start_p;
vec = vec*(kClickTolerance/qSqrt(QPointF::dotProduct(vec, vec)));
QPointF orthogonal(vec.y(), -vec.x());
QPainterPath result(start_p-vec+orthogonal);
result.lineTo(start_p-vec-orthogonal);
result.lineTo(end_p+vec-orthogonal);
result.lineTo(end_p+vec+orthogonal);
result.closeSubpath();
return result;
}
You must draw yourself bounding if you want some thing like this. let Qt have it's QRect for bounding and define your new QRect dependent to the corner of previous QRect, top-left and bottom-right. for example if the top-left corner is (2,2) your new QRect top-left is (1,2) and top-right is (2,1) and ....
I have some kind of histogram drawn with QGraphicsRectItem; some of these rectangles are long, some short. While it is no problem to select a long rectangle, one might have difficulties with the short ones.
So I was wondering if there is a way to specify custom area that would trigger mousePressEvent for the item, so rectangles would have the same size selection area.
Apart from rectangles I also draw some text on the same line. Would it be helpful to group them somehow and write mousePressEvent for the group instead?
Thank you!
It would be useful to see an example image of what you're asking, but I think I know what mean.
Personally, I'd just create my own class, inherited from QGraphicsItem (or QGraphicsObject, if you want signals and slots). This class can then provide a boundingRect() of the full area that you want to represent the area to be selected, but the paint() function only draw the visible part of the bar. Something like this: -
class Bar: public QGraphicsItem
{
Q_OBJECT
public:
Bar(int x, int y, int width, int height, int visibleBarHeight);
// returns the area of the object
QRectF boundingRect() const;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
};
In the paint function, you would draw a rect up to the visible bar height, but in boundingRect, return the full rect. That way, the bar could visibly be very small, but the object is of full height and would respond to mouse selection above the visible area of the bar.
As for the text, you could either add it as a child to this object and signal the parent when it gets selected, or extend the boundingRect of this Bar class and render it in the paint function.
Note that boundingRect is the area represented by the object, in local coordinates. If you have an object that isn't defined by a rectangle, you'd also want to implement the shape() function. By default, shape() calls boundingRect().
I have a QGraphicsScene with rather small point markers. I would like to enlarge the area of these markers to make dragging easier. The marker is a cross, +/- 2 pixels from the origin. I have reimplemented
QGraphicsItem::contains(const QPointF & point ) const
{
return QRectF(-10,-10,20,20);
}
and
void hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
setPen(QPen(Qt::red));
update();
}
but the marker only turns red when it is directly hit by the cursor (and even that is a bit picky). How can I enlarge the "hover area"?
As stated in the short comment:
Usually those things are handled via the bounding rect or the shape function, try overloading those. Take a look into the qt help of QGraphicsItem under shape (http://doc.qt.io/qt-4.8/qgraphicsitem.html#shape):
Returns the shape of this item as a QPainterPath in local coordinates.
The shape is used for many things, including collision detection, hit
tests, and for the QGraphicsScene::items() functions.
The default implementation calls boundingRect() to return a simple
rectangular shape, but subclasses can reimplement this function to
return a more accurate shape for non-rectangular items. For example, a
round item may choose to return an elliptic shape for better collision
detection. For example:
QPainterPath RoundItem::shape() const {
QPainterPath path;
path.addEllipse(boundingRect());
return path; } The outline of a shape can vary depending on the width and style of the pen used when drawing. If you want to include
this outline in the item's shape, you can create a shape from the
stroke using QPainterPathStroker.
This function is called by the default implementations of contains()
and collidesWithPath().
So what basically happens is that all functions that want to access the "Zone" which is associated with an item, call shape and then do e.g. a containment or collision detection with the resulting painterpath.
Thus if you have small items you should enlargen the shape zone.
Lets for instance consider a line that is your target, than your shape implementation could look like the following:
QPainterPath Segment::shape() const{
QLineF temp(qLineF(scaled(Plotable::cScaleFactor)));
QPolygonF poly;
temp.translate(0,pen.widthF()/2.0);
poly.push_back(temp.p1());
poly.push_back(temp.p2());
temp.translate(0,-pen.widthF());
poly.push_back(temp.p2());
poly.push_back(temp.p1());
QPainterPath path;
path.addPolygon(poly);
return path;
}
Pen is a member of the segment, and I use its width to enlarge the shape zone. But you can take anything else as well that has a good relation to the actual dimension of your object.
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) );
I am trying the hello triangle example of OpenGL ES 2.0. I am using Qt, so I created a QGraphicsScene and added that code as a QGraphicsItem. It draws correctly, but I cannot get the bounding rectangle correctly. The triangle vertices are
GLfloat afVertices[] =
{-0.4f,-0.4f,0.0f,
0.4f ,-0.4f,0.0f,
0.0f ,0.4f ,0.0f};
and my viewport is glViewport(0, 0, 800, 480);
What would be the correct bounding rect coordinates?
I set the viewport to a QGLWidget. The thing with the QGraphicsItem is that I have to re-implement the bounding rectangle of the item and if I just use
QRectF myGraphicsItem::boundingRect() const
{
return QGraphicsItem::boundingRect();
}
it says undefined reference to `QGraphicsItem::boundingRect() const'
I had originally used
QRectF myGraphicsItem::boundingRect() const
{
return QRectF(-0.4, -0.4, 0.8, 0.8);
}
but the result is a very small bounding box. The seemingly correct one was created when I was used values like QRectf(300, 200, 200, 200) by trial and error -which is too 'manual'-, so I was wondering maybe there is some kind of coordinate correspondence or transformation that I'm unaware of.
QGraphicsItem::boundingRect() is a pure virtual function. Thus, there is no implementation. You must provide your own implementation. Based upon your vertices, probably
QRectF myGraphicsItem::boundingRect() const
{
return QRectF(-0.4, -0.4, 0.8, 0.8);
}
I'm not sure I follow, if you're using a QGraphicsItem (with or without an OpenGL viewport), you would typically use QGraphicsItem::boundingRect() to get the bounding rectangle?
I would do (in Python):
# inside class
def parentBoundingRect(self):
return self.mapToParent(self.boundingRect()).boundingRect()
# or if that doesn't work
def parentBoundingRect(self):
pos = self.pos()
rect = self.transform().mapToPolygon(self.boundingRect()).boundingRect()
return QRectF(pos.x(), pos.y(), rect.width(), rect.height())
# or if that doesn't work, keep playing with it til it does! :)