Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QGraphicsItem gets selected when clicking outside of visible shape

QGraphicsItem gets selected when clicking outside of visible shape

Scheduled Pinned Locked Moved Solved General and Desktop
qgraphicsitemqgraphicsscenezoomqgraphicsviewqt4
9 Posts 2 Posters 6.2k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • VaniaxV Offline
    VaniaxV Offline
    Vaniax
    wrote on last edited by Vaniax
    #1

    Hi,

    currently I have a simple Qt application (using Qt 4.7.1) which renders some simple QGraphicsItemsinto a QGraphicsScene. 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:

    alt text

    More Images

    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).

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi and welcome to devnet,

      Are you really running Qt 4.7.1 ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      VaniaxV 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi and welcome to devnet,

        Are you really running Qt 4.7.1 ?

        VaniaxV Offline
        VaniaxV Offline
        Vaniax
        wrote on last edited by
        #3

        @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.

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          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.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          VaniaxV 1 Reply Last reply
          0
          • SGaistS SGaist

            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.

            VaniaxV Offline
            VaniaxV Offline
            Vaniax
            wrote on last edited by Vaniax
            #5

            @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.

            Images of 40k Chip example

            alt text

            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #6

              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.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              1 Reply Last reply
              0
              • VaniaxV Offline
                VaniaxV Offline
                Vaniax
                wrote on last edited by Vaniax
                #7

                @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.

                1 Reply Last reply
                0
                • SGaistS Offline
                  SGaistS Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  Sometimes it's nice to be mistaken ! :)

                  Glad you found out and thanks for sharing !

                  Would you care submitting the fix for Qt 5 ?

                  Interested in AI ? www.idiap.ch
                  Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                  VaniaxV 1 Reply Last reply
                  0
                  • SGaistS SGaist

                    Sometimes it's nice to be mistaken ! :)

                    Glad you found out and thanks for sharing !

                    Would you care submitting the fix for Qt 5 ?

                    VaniaxV Offline
                    VaniaxV Offline
                    Vaniax
                    wrote on last edited by
                    #9

                    :D
                    I haven't had much time to test if the bug exists in Qt5. I'll check it out and update the topic accordingly. If the bug is still there, of course, I will submit the fix.

                    Thanks for your time.

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved