QGraphicsItem gets selected when clicking outside of visible shape
-
Hi,
currently I have a simple Qt application (using
Qt 4.7.1
) which renders some simpleQGraphicsItems
into aQGraphicsScene
. For example, I have a custom item which is supposed to be used as a polyline. With this item I have the problem, that it seems to receive mouse click events outside of the visible shape, what results in item selections when clicking nearby the element. I tried to visualize the scenario:The problem is be much more visible when zooming into the scene (see definition of
view.cpp
below), but it's noticeable without zooming too. Moreover the whole selection area seems not to be in sync with the visible item shape: e.g. the selection area for the item in the images seems to have a "left offset".The custom item is defined as follows:
// polyline.hpp #ifndef POLYLINE_HPP #define POLYLINE_HPP #include <vector> #include <QGraphicsItem> #include <QPen> class Polyline : public QGraphicsItem { public: Polyline(QGraphicsItem* parent = nullptr, QGraphicsScene* scene = nullptr); void append(QPointF point); void modifyLast(const QPointF& point); void setPen(QPen pen); void setBrush(QBrush brush); virtual void paint(QPainter *painter = nullptr, const QStyleOptionGraphicsItem *option = nullptr, QWidget *widget = nullptr) override; virtual QRectF boundingRect() const override; virtual QPainterPath shape() const override; private: std::vector<QPointF> points; QPen pen; QBrush brush; }; #endif // POLYLINE_HPP
// polyline.cpp #include "polyline.hpp" #include <QPainter> #include <QStyleOptionGraphicsItem> #include <algorithm> Polyline::Polyline(QGraphicsItem* parent /*= nullptr*/, QGraphicsScene* scene /*= nullptr*/) : QGraphicsItem(parent, scene) { setFlag(QGraphicsItem::ItemIsSelectable); } void Polyline::append(QPointF point) { prepareGeometryChange(); points.push_back(point); } void Polyline::modifyLast(const QPointF& point) { prepareGeometryChange(); auto backPoint = points.back(); backPoint.setX(point.x()); backPoint.setY(point.y()); } void Polyline::setPen(QPen pen) { this->pen = pen; } void Polyline::setBrush(QBrush brush) { this->brush = brush; } void Polyline::paint(QPainter *painter /*= nullptr*/, const QStyleOptionGraphicsItem *option /*= nullptr*/, QWidget *widget /* = nullptr */) { painter->setPen(pen); painter->setBrush(brush); if (option->state & QStyle::State_Selected) { QPen lighterPen = pen; lighterPen.setColor(pen.color().lighter()); painter->setPen(lighterPen); } painter->drawPolyline(points.data(), points.size()); } QRectF Polyline::boundingRect() const { auto minmaxX = std::minmax_element(points.begin(), points.end(), [](const QPointF& p1, const QPointF& p2) { return p1.x() < p2.x(); }); auto minmaxY = std::minmax_element(points.begin(), points.end(), [](const QPointF& p1, const QPointF& p2) { return p1.y() < p2.y(); }); QPointF topLeft(minmaxX.first->x(), minmaxY.first->y()); QPointF bottomRight(minmaxX.second->x(), minmaxY.second->y()); return QRectF(topLeft, bottomRight); } QPainterPath Polyline::shape() const { QPainterPath path; QPainterPathStroker stroker; path.moveTo(points.front()); for(int i = 1; i < points.size(); ++i) { path.lineTo(points[i]); } // As in implementations like QGraphicsLineItem const qreal penWidthZero = qreal(0.00000001); stroker.setWidth(pen.widthF() <= 0.0 ? penWidthZero : pen.widthF()); stroker.setJoinStyle(pen.joinStyle()); stroker.setMiterLimit(pen.miterLimit()); return stroker.createStroke(path); }
The corresponding
QGraphicsScene
is defined as:// scene.cpp #include "scene.hpp" #include "rectangleitem.hpp" #include "polyline.hpp" #include <iostream> Scene::Scene(QObject* parent /*= nullptr*/) : QGraphicsScene(parent), mode(Pointer), polyline(nullptr) { } void Scene::setMode(int index) { mode = static_cast<Mode>(index); } void Scene::mousePressEvent(QGraphicsSceneMouseEvent* e) { std::cout << "SceneCoords: " << e->scenePos().x() << ", " << e->scenePos().y() << std::endl; switch (mode) { case Rectangle: if (e->button() == Qt::LeftButton) { QGraphicsRectItem* rectangleItem = new CustomRectItem; rectangleItem->setRect(e->scenePos().x(), e->scenePos().y(),100,100); rectangleItem->setBrush(QBrush(QColor(255,0,0))); addItem(rectangleItem); } break; case Line: if (e->button() == Qt::LeftButton) { if (!polyline) { originPoint = e->scenePos(); polyline = new Polyline(nullptr, this); polyline->setPen(QPen(Qt::green, 2, Qt::SolidLine)); polyline->append(originPoint); polyline->append(originPoint); addItem(polyline); } else if (polyline) { polyline->append(e->scenePos()); } } else if (e->button() == Qt::RightButton) { if (polyline) { removeItem(polyline); polyline = nullptr; } } default: QGraphicsScene::mousePressEvent(e); break; } } void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent* e) { switch (mode) { case Line: if (polyline) { polyline->modifyLast(e->scenePos()); } break; default: QGraphicsScene::mouseMoveEvent(e); break; } } void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { switch (mode) { default: QGraphicsScene::mouseReleaseEvent(e); break; } }
The corresponding
QGraphicsView
is defined as:// view.cpp #include "sceneview.hpp" #include <QMatrix> #include <QWheelEvent> #include <iostream> SceneView::SceneView(QGraphicsScene* scene /*= nullptr*/, QWidget* parent /*= nullptr*/) : QGraphicsView(scene, parent), zoomLevel(0) { setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); } void SceneView::wheelEvent(QWheelEvent* e) { if (e->modifiers() & Qt::ControlModifier) { if (e->delta() > 0) zoomIn(1); else zoomOut(1); e->accept(); } else { QGraphicsView::wheelEvent(e); } } void SceneView::zoomIn(int level) { zoomLevel += level; double factor = level*2.0; scale(factor, factor); } void SceneView::zoomOut(int level) { zoomLevel -= level; double factor = level/2.0; scale(factor, factor); }
Can anyone spot the error I make? I think there is maybe a problem with the shape definition, but according to sources I found, it seems pretty standard for that kind of item.
EDIT:
Please take a look at my additional post below, where I describe how to reproduce the error with a standard Qt4 demo (part of the installation package). -
Hi and welcome to devnet,
Are you really running Qt 4.7.1 ?
-
@SGaist Thanks for your reply. Yes, I'm running Qt 4.7.1 *.
*In fact I have to, because I'm using a proprietary framework in my project which requires this version and currently I don't want to bother with different versions in my build scripts.
-
I thought it would be something like this.
However, can you test just the offending code with the latest and last Qt 4 (4.8.7) ? Just to know whether the behaviour changed in between.
-
@SGaist I tested it with version 4.8.7: Nothing changed, I got the same faulty behaviour.
Moreover, I found a very simple way to reproduce my problem using the 40000 Chips Demo, which is e.g. included in the Qt. 4.8.7 install package:
- In the top left window, zoom into the scene (Strg + mouse wheel) until you are very close to one chip (see my image link below).
- rotate the scene a bit via the buttons
=> Then the hover event and the selection via mouse click are triggered when clicking/hovering next to the visible shape of the chip.
-
Unless I'm mistaken, the default mouse click detection is done using the geometry of the item. If you want something more precise like detecting that you're on a line of your item, you'll have to implement this yourself.
-
@SGaist said in QGraphicsItem gets selected when clicking outside of visible shape:
Unless I'm mistaken, the default mouse click detection is done using the geometry of the item. If you want something more precise like detecting that you're on a line of your item, you'll have to implement this yourself.
The mouse click detection is far away from being only unprecise.
I found out that this behavior is a bug, as described e.g. for Qt 4.7.1 (https://bugreports.qt.io/browse/QTBUG-15468) and for Qt 4.7.4 (https://bugreports.qt.io/browse/QTBUG-21904). The problem in this Qt versions is, that the mouse click detection uses scene coordinates plus an intersection test with a rectangle of size (1,1). This is a problem in higher zoom levels, where scene coordinates get smaller.A user stated (first link) that the bug is fixed since Qt 4.8.5, but I reproduced the error with Qt 4.8.7 (see my example in the link and my last post here). As described in the first bug report the complete bugfix looks like this:
--- C:/SDK/qt-everywhere-opensource-src-4.7.1/src/gui/graphicsview/qgraphicsscene_original.cpp Mo Jan 9 16:39:35 2017 +++ C:/SDK/qt-everywhere-opensource-src-4.7.1/src/gui/graphicsview/qgraphicsscene.cpp Mo Jan 9 16:40:39 2017 @@ -1074,7 +1074,7 @@ /*! Returns all items for the screen position in \a event. */ -QList<QGraphicsItem *> QGraphicsScenePrivate::itemsAtPosition(const QPoint &/*screenPos*/, +QList<QGraphicsItem *> QGraphicsScenePrivate::itemsAtPosition(const QPoint &screenPos, const QPointF &scenePos, QWidget *widget) const { @@ -1085,11 +1085,11 @@ const QRectF pointRect(scenePos, QSizeF(1, 1)); if (!view->isTransformed()) - return q->items(pointRect, Qt::IntersectsItemShape, Qt::DescendingOrder); + return q->items(QRectF(scenePos, QSizeF(1, 1)), Qt::IntersectsItemShape, Qt::DescendingOrder); const QTransform viewTransform = view->viewportTransform(); - return q->items(pointRect, Qt::IntersectsItemShape, - Qt::DescendingOrder, viewTransform); + return q->items(view->mapToScene(QRect(view->mapFromGlobal(screenPos), QSize(1, 1))), + Qt::IntersectsItemShape, Qt::DescendingOrder, viewTransform); } /*!
This seems to be working for now.
-
Sometimes it's nice to be mistaken ! :)
Glad you found out and thanks for sharing !
Would you care submitting the fix for Qt 5 ?