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.
  • V Offline
    V Offline
    Vaniax
    wrote on 6 Jan 2017, 08:49 last edited by Vaniax 1 Sept 2017, 11:44
    #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
    • S Offline
      S Offline
      SGaist
      Lifetime Qt Champion
      wrote on 6 Jan 2017, 23:07 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

      V 1 Reply Last reply 7 Jan 2017, 22:10
      0
      • S SGaist
        6 Jan 2017, 23:07

        Hi and welcome to devnet,

        Are you really running Qt 4.7.1 ?

        V Offline
        V Offline
        Vaniax
        wrote on 7 Jan 2017, 22:10 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
        • S Offline
          S Offline
          SGaist
          Lifetime Qt Champion
          wrote on 7 Jan 2017, 23:12 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

          V 1 Reply Last reply 8 Jan 2017, 22:25
          0
          • S SGaist
            7 Jan 2017, 23:12

            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.

            V Offline
            V Offline
            Vaniax
            wrote on 8 Jan 2017, 22:25 last edited by Vaniax 1 Aug 2017, 22:27
            #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
            • S Offline
              S Offline
              SGaist
              Lifetime Qt Champion
              wrote on 9 Jan 2017, 21:44 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
              • V Offline
                V Offline
                Vaniax
                wrote on 10 Jan 2017, 13:56 last edited by Vaniax 1 Oct 2017, 14:06
                #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
                • S Offline
                  S Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on 10 Jan 2017, 20:35 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

                  V 1 Reply Last reply 12 Jan 2017, 08:43
                  0
                  • S SGaist
                    10 Jan 2017, 20:35

                    Sometimes it's nice to be mistaken ! :)

                    Glad you found out and thanks for sharing !

                    Would you care submitting the fix for Qt 5 ?

                    V Offline
                    V Offline
                    Vaniax
                    wrote on 12 Jan 2017, 08:43 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

                    7/9

                    10 Jan 2017, 13:56

                    • Login

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