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);
}
Related
I'm implementing an image viewer on an embedded platform. The hardware is a sort of tablet and has a touch screen as input device. The Qt version I'm using is 5.4.3.
The QGraphicsView is used to display a QGraphicsScene which contains a QGraphicsPixmapItem. The QGraphicsPixmapItem containts the pixmap to display.
The relevant part of the code is the following:
void MyGraphicsView::pinchTriggered(QPinchGesture *gesture)
{
QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
if (changeFlags & QPinchGesture::ScaleFactorChanged) {
currentStepScaleFactor = gesture->totalScaleFactor();
}
if (gesture->state() == Qt::GestureFinished) {
scaleFactor *= currentStepScaleFactor;
currentStepScaleFactor = 1;
return;
}
// Compute the scale factor based on the current pinch level
qreal sxy = scaleFactor * currentStepScaleFactor;
// Get the pointer to the currently displayed picture
QList<QGraphicsItem *> listOfItems = items();
QGraphicsItem* item = listOfItems.at(0);
// Scale the picture
item.setScale(sxy);
// Adapt the scene to the scaled picture
setSceneRect(scene()->itemsBoundingRect());
}
As result of the pinch, the pixmap is scaled starting from the top-left corner of the view.
How to scale the pixmap respect to the center of the QPinchGesture?
From The Docs
The item is scaled around its transform origin point, which by default is (0, 0). You can select a different transformation origin by calling setTransformOriginPoint().
That function takes in a QPoint so you would need to find out your centre point first then set the origin point.
void QGraphicsItem::setTransformOriginPoint(const QPointF & origin)
I have a number of classes that inherit from QGraphicsItem, that get to be arranged in a certain way. For simplicity of calculations, I made the scenes, and items, centered in (0, 0) (with the boundingRect() having +/- coordinates).
QGraphicsTextItem subclass defies me, its pos() is relative to top left point.
I have tried a number of things to shift it so it centers in the text center (for example, the suggested solution here - the code referenced actually cuts my text and only shows the bottom left quarter).
I imagined that the solution should be something simple, like
void TextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->translate( -boundingRect().width()/2.0, -boundingRect().height()/2.0 );
QGraphicsTextItem::paint(painter, option, widget );
}
the above "sort of" works - but as I increase the item scale -> increase the font, the displayed item is cut off...
I tried to set the pos() - but the problem is, I still need to track the actual position on the scene, so I cannot just replace it.
A slightly unpleasant side effect - centering the QGraphicsView on the element does not work either.
How can I make my QGraphicsTextItem show its position relative to the center of the text ?
Edit: one of the experiments of changing the boundingRect():
QRectF TextItem::boundingRect() const
{
QRectF rect = QGraphicsTextItem::boundingRect();
rect.translate(QPointF(-rect.width()/2.0, -rect.height()/2.0));
return rect;
}
I had to shift the initial position, as well as the resize, to trigger a new position - I was unable to do it in paint() because, as I thought from the start, any repaint would continuously recalculate the position.
Only the initial position needs to be adjusted - but as the font size (or style...) changes, its bounding rectangle also changes, so the position must be recalculated - based on previous position.
In the constructor,
setPos(- boundingRect().width()/2, - boundingRect().height()/2);
in the function that modifies item (font) size,
void TextItem::setSize(int s)
{
QRectF oldRect = boundingRect();
QFont f;
f.setPointSize(s);
setFont(f);
if(m_scale != s)
{
m_scale = s;
qreal x = pos().x() - boundingRect().width()/2.0 + oldRect.width()/2.0;
qreal y = pos().y() - boundingRect().height()/2.0 + oldRect.height()/2.0;
setPos(QPointF(x, y));
}
}
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'm trying to move a QGraphicsItem with the mouse hover over the parent item.
BaseItem::BaseItem(const QRectF &bounds)
: theBounds(bounds), theMousePressed(false)
{
theLineItem = new LineItem(theBounds, this);
setAcceptHoverEvents(true);
}
and
void BaseItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
QPointF position = mapToScene( event->pos());
theLineItem->setPos( position);
}
But the item is not moving. Is there any other way to move the item on the scene with mouse move, without using the ItemIsMovable flag, because I want the item to be moved around the parent item, once it's invoked?
When you create the LineItem, in its constructor you pass the BaseItem as the parent.
A call to setPos on a GraphicsItem, sets the position of the item relative to its parent which, in this case, is the BaseItem.
Mapping the event->pos() to scene coordinates is wrong here. event->pos() returns the position in local coordinates of the receiving object, which in this case is the BaseItem.
Therefore, you should be setting the position of theLineItem directly with event->pos().
theLineItem->setPos(event->pos());
Note that if you did happen to want the event position in scene coordinates, there's a function already available: -
event->scenePos();
So you would not have needed to call mapToScene.
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) );