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. Crash in QGraphicsScene::event when removing and deleting items that use SceneEventFilters
Forum Updated to NodeBB v4.3 + New Features

Crash in QGraphicsScene::event when removing and deleting items that use SceneEventFilters

Scheduled Pinned Locked Moved Solved General and Desktop
qgraphicssceneeventfilterremoveitemdeletelater
6 Posts 3 Posters 6.1k Views 1 Watching
  • 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.
  • J Offline
    J Offline
    JEllery
    wrote on last edited by
    #1

    Overview:
    Using Qt 5.9.2
    Crash in QGraphicsScene::event, QGraphicsScene::mouseMoveEvent when using a hover event on one item to trigger removing a second item from the scene (removeItem), then deleting it later (deleteLater). Both items use SceneEventFilters.

    Details:
    The problem can be reproduced in the following simplified case:

    -We have 2 main items in the scene, which have separate classes, MyItemA and MyItemB. MyItemA draws a yellow rectangle and MyItemB draws a set of red lines. Each line is a class of type MyLine

    -MyItemB class has a related MyItemBManager class that controls creation and deletion of the MyItemB object.

    -We have a 'helper' class called HoverItemListener that creates a SceneEventFilter to listen for hover events on a parent. This helper class is used for both MyItemA and MyLine classes.

    -When the helper class detects a change in the hover, it informs an intermediate class called HoverDelay, that adds a delay and emits a signal to say the hover state has changed. This signal is connected to slots on the associated MyItemA or MyLine class

    -When MyItemA (the rectangle) gets a signal that hover has changed to on, it changes its colour. When it then receives a signal to say the hover has ended, it emits a signal that is used by MyItemBManager to remove the MyItemB (red lines) from the scene, then delete (using removeItem and deleteLater).

    Most of the time, this causes a segmentation violation.

    The crash does not seem to occur if we remove the use of SceneEventFilters and instead detect the hover directly in MyItemA and MyLine class but we dont really want to put all that code in to each class, if we can avoid it (since we use this in many classes).

    We would like to know if there is something wrong with our implementation, or if this is a Qt bug.

    We have looked at other reported problems that suggest the problem may be related to a missing call to prepareGeometryChange() but the geometry does not change in this test and we tried turning off BSP indexing by adding a call to
    myScene->setItemIndexMethod(QGraphicsScene::NoIndex);
    but this made no difference.

    We have also tried removing the SceneEventFilter manually when the HoverItemListener gets an itemChange event to say it has been removed from the scene. This did not help and this is done anyway in Qt in file qgrahpicsscene.cpp, function QGraphicsScenePrivate::removeItemHelper

    The following code creates a test program that demonstrates the problem. The Qt project file is also included.

    When the application is started, it draws a yellow rectangle and some red lines.
    If the mouse is moved over the yellow rectangle it will change colour to cyan. When the mouse is moved out of the rectangle again, the red lines should be turned off but it usually crashes.
    If it does not crash first time, the middle mouse can be clicked to display the red lines again
    and the mouse moved over and off the yellow rectangle to retry. The red lines must be displayed before moving the mouse over and off the yellow rectangle, to get the crash.
    Usually no more than a few times are required to get a crash.

    //--------------------------------------------------------
    //File: main.cpp
    //This creates the view, scene and all objects.
    //It sets up the connection to toggle the red lines on/off on middle mouse click
    //and to respond to a hover change on MyItemA (rectangle)
    //--------------------------------------------------------
    #include <QApplication>
    #include<QGraphicsView>
    #include<QGraphicsScene>
    
    #include "my_view.h"
    #include "my_itemB_manager.h"
    #include "my_itemA.h"
    
    //Global for test
    MyItemBManager* myMgr;
    bool    GlobalTestFlag1 = false;     //If true, manually remove scene filters when item removed from scene
    
    int main(int argc, char *argv[])
    {
        QApplication qapp(argc, argv);
    
        MyView* view=nullptr;
    
        view= new MyView;
        view->setBackgroundBrush(QBrush(Qt::white, Qt::SolidPattern));
        view->show();
    
        QGraphicsScene* myScene = view->scene();
    
        //Create a route (red lines for test)
        myMgr = new MyItemBManager(myScene);
    
        myMgr->setVisible(true);
    
        //Toggle route display on middle mouse click
        QObject::connect(view, &MyView::myMouseMiddleClick, myMgr, &MyItemBManager::toggleVisible);
    
    
        //Create a control object (yellow rectangle for test)
        MyItemA* myItemA = new MyItemA;
    
        //Turn off route display when focus lost
        QObject::connect(myItemA, &MyItemA::explicitHoverChanged, myItemA, &MyItemA::manage_test2);
    
        myScene->addItem(myItemA);
        QPointF pos(100,100);
        myItemA->setPosition(pos);
    
    
        return qapp.exec();
    }
    
    //--------------------------------------------------------
    //File: my_itemA.h
    //This class draws a rectangle and uses HoverItemListener and HoverDelay
    //classes to detect hover changes.
    //It emits a signal when hover has changed.
    //In this simplified code, it also responds to that signal via 'manage_test2' slot.
    //--------------------------------------------------------
    #ifndef MYITEMA_H
    #define MYITEMA_H
    
    #include <QtWidgets/qgraphicsitem.h>
    #include <QtCore/QPointer>
    
    class HoverDelay;
    class HoverItemListener;
    
    class MyItemA : public QGraphicsObject
    {
       Q_OBJECT
    
    public:
       explicit MyItemA(QGraphicsItem *parent = 0);
    
       void setPosition (const QPointF &pos);
    
       virtual QRectF boundingRect() const;
       virtual QPainterPath shape() const;
    
    public Q_SLOTS:
       void manage_test2(bool);
    
    Q_SIGNALS:
       void explicitHoverChanged(bool);
    
    protected:
       QRectF labelBoundingRect() const;
    
    private Q_SLOTS:
       void label_hover_changed(bool hovered);
    
    private:
       Q_DISABLE_COPY(MyItemA)
       void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    
       mutable bool label_box_valid;
       mutable bool shape_valid;
       mutable QRectF label_box;
       mutable QPainterPath p_shape;
       bool p_hover_active;
       QPointer<HoverDelay> p_hover_label;
       mutable HoverItemListener *p_hover_label_listener;
    
       mutable QRectF p_bounding_rect;
    };
    
    #endif // MYITEMA_H
    
    //--------------------------------------------------------
    //File: my_itemA.cpp
    //--------------------------------------------------------
    #include <QtGui/qpainter.h>
    #include <QtWidgets/qgraphicsscene.h>
    #include <QtWidgets/qgraphicssceneevent.h>
    #include <QtWidgets/qgraphicsview.h>
    #include <QtWidgets/qwidget.h>
    #include <QtWidgets/qstyleoption.h>
    
    #include <QtCore/qdebug.h>
    
    
    #include "my_itemA.h"
    #include "hover_delay.h"
    #include "hover_item_listener.h"
    #include "my_itemB_manager.h"
    
    
    static int g_hover_delay = 50;
    
     //Global for test
    extern MyItemBManager* myMgr;
    
    
    MyItemA::MyItemA(QGraphicsItem *parent) :
       QGraphicsObject(parent),
       label_box_valid(false),
       shape_valid(false)
    {
        p_hover_active = false;
        p_hover_label = new HoverDelay(g_hover_delay,  this, 2);
    
        p_hover_label_listener = new HoverItemListener(p_hover_label,  this, 2);
    
        connect(p_hover_label,  &HoverDelay::hoverChanged, this, &MyItemA::label_hover_changed);
        setFlags(ItemIgnoresTransformations | ItemSendsScenePositionChanges | ItemClipsToShape);
    
    
       // Set the correct granularity (for all the children too)
       setBoundingRegionGranularity(1.0);
    }
    
    void MyItemA::setPosition(const QPointF &newpos)
    {
       QGraphicsObject::setPos(newpos);
    }
    
    
    QRectF MyItemA::boundingRect() const
    {
       if (!label_box_valid)
       {
          {
             label_box_valid = true;
    
             //Just use a fixed size for demo
             label_box = QRectF(0,0,100,50);
    
             p_hover_label_listener->setRect(label_box);
    
             p_bounding_rect = label_box;
          }
       }
    
       return p_bounding_rect;
    }
    
    QPainterPath MyItemA::shape() const
    {
       if (!shape_valid)
       {
          if (!label_box_valid)
          {
             boundingRect();
          }
    
          QPainterPath new_shape;
          new_shape.setFillRule(Qt::WindingFill);
    
          // label - just a solid block
          new_shape.addRect(label_box);
    
          p_shape = new_shape;
          shape_valid = true;
       }
    	
       return p_shape;
    }
    
    void MyItemA::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
       Q_UNUSED(option);
       Q_UNUSED(widget);
       painter->save();
    
       {
          if (p_hover_active)
             painter->fillRect(label_box, Qt::cyan);
          else
             painter->fillRect(label_box, Qt::yellow);
       }
    
       painter->restore();
    }
    
    void MyItemA::label_hover_changed(bool hovered)
    {
       p_hover_active = hovered;
    
       prepareGeometryChange();
       label_box_valid = false;
       shape_valid     = false;
       update();
    
       //This triggers the flight leg to be deleted, when hovered is false
       emit explicitHoverChanged(hovered);
    }
    
    //Simplified version of original code
    void MyItemA::manage_test2(bool enabled)
    {
       if (!enabled)
       {
           if (myMgr)
             myMgr->setVisible(false);
       }
    }
    
    //--------------------------------------------------------
    //File: my_itemB.h
    //This class creates a set of lines, based on points given.
    //The drawing is not done by this class but by the line classes.
    //--------------------------------------------------------
    #ifndef MYITEMB_H
    #define MYITEMB_H
    
    #include <QtWidgets/qgraphicsitem.h>
    
    class MyLine;
    
    class MyItemB : public QObject, public QAbstractGraphicsShapeItem
    {
       Q_OBJECT
    
    public:
       explicit MyItemB(QGraphicsItem *parent = 0);
       ~MyItemB();
    
       void setRoute(const QList<QPointF> &points);
    	
       virtual QRectF boundingRect() const;
       virtual void   paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
    
    private:
       Q_DISABLE_COPY(MyItemB)
       QList<MyLine*> p_flight_line;
    
       QList<QPointF> p_points;
       void updateContents();
    };
    
    #endif
    
    //--------------------------------------------------------
    //File: my_itemB.cpp
    //--------------------------------------------------------
    #include "my_itemB.h"
    
    #include <QtCore/QDebug>
    #include <QtWidgets/qapplication.h>
    #include <QtGui/qpen.h>
    #include <QtWidgets/qgraphicsscene.h>
    
    #include "my_line.h"
    
    static int g_line_width         = 2;
    static QColor g_line_colour = Qt::red;
    
    MyItemB::MyItemB(QGraphicsItem *parent) :
        QObject(),
        QAbstractGraphicsShapeItem(parent)
    {
        qDebug() << "MyItemB()";
        setFlags(ItemHasNoContents);
        QPen the_pen = pen();
        the_pen.setColor(g_line_colour);
        the_pen.setWidth(g_line_width);
        the_pen.setCosmetic(true);
        the_pen.setCapStyle(Qt::FlatCap);
        setPen(the_pen);
    }
    
    MyItemB::~MyItemB()
    {
        qDebug() << "~MyItemB";
       qDebug() << "-";
    }
    
    void MyItemB::setRoute(const QList<QPointF> &points)
    {
        p_points = points;
        updateContents();
    }
    
    QRectF MyItemB::boundingRect() const
    {
        // We have done setFlags(ItemHasNoContents)
        return QRectF();
    }
    
    void MyItemB::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
       // We have done setFlags(ItemHasNoContents)
    }
    
    void MyItemB::updateContents()
    {
       // Update the polyline to the new set of points
    
       // Line goes through all known points
       int nb_points = p_points.size();
    
       QPointF prev_point;
       QPointF next_point;
    
       for (int nb=0; nb<nb_points; nb++)
       {
          const QPointF &point = p_points.at(nb);
    
          if (nb == 0)
          {
             next_point = point;
          }
          else
          {
             // Always draw the line
             next_point = point;
    
             if (p_flight_line.size() < nb)
             {
                // create a new MyLine
                MyLine *line = new MyLine(QLineF(prev_point, next_point), this);
                line->setPen(pen());
    
                p_flight_line.append(line);
             }
             else
             {
                // re-use the existing MyLine
                p_flight_line.at(nb-1)->setLine(QLineF(prev_point, next_point));
             }
          }
    
          prev_point = next_point;
       }
    }
    
    //--------------------------------------------------------
    //File: my_itemB_manager.h
    //This class controls the creation and removal of a MyItemB object
    //--------------------------------------------------------
    #ifndef MYITEMB_MANAGER_H
    #define MYITEMB_MANAGER_H
    
    #include <QtCore/QObject>
    
    class MyItemB;
    class QGraphicsScene;
    
    class MyItemBManager : public QObject
    {
       Q_OBJECT
    public:
       explicit MyItemBManager(QGraphicsScene *scene);
       ~MyItemBManager();
       void setVisible(bool visible);
    
    public Q_SLOTS:
       void toggleVisible();
    
    private:
        QGraphicsScene * m_scene;
        MyItemB* m_leg;
        bool m_visible;
    };
    
    #endif
    
    //--------------------------------------------------------
    //File: my_itemB_manager.cpp
    //--------------------------------------------------------
    #include "my_itemB_manager.h"
    #include "my_itemB.h"
    
    #include <QtCore/QDebug>
    #include <QtWidgets/QApplication>
    #include <QGraphicsScene>
    
    
    MyItemBManager::MyItemBManager(QGraphicsScene *scene) :
        m_scene(scene),
        m_leg(nullptr),
        m_visible(false)
    {
    }
    
    MyItemBManager::~MyItemBManager()
    {
        if (m_leg)
        {
            m_leg->deleteLater();
            m_leg = nullptr;
        }
    }
    
    void MyItemBManager::toggleVisible()
    {
        m_visible = (m_visible) ? false : true;
        setVisible(m_visible);
    }
    
    void MyItemBManager::setVisible(bool visible)
    {
        m_visible = visible;
        if (visible)	// show it
        {
            if (!m_leg)
            {
    qDebug() << "Creating leg";
                // Create it since it doesn't already exist
                m_leg = new MyItemB();
    
                QList<QPointF> pos;
                pos.append(QPointF(-100,-100));
                pos.append(QPointF(20,20));
                pos.append(QPointF(50,100));
                pos.append(QPointF(100,300));
                pos.append(QPointF(200,50));
    
                m_leg->setRoute(pos);
                m_scene->addItem(m_leg);
            }
        }
        else		// remove and delete
        {
            if (m_leg)
            {
                QGraphicsScene* legScene = m_leg->scene();
                if (legScene)
                {
                    //This removes all children from scene too
                    legScene->removeItem(m_leg);
                }
    
                m_leg->deleteLater();
                m_leg = nullptr;
            }
        }
    }
    
    //--------------------------------------------------------
    //File: my_line.h
    //This class draws a line that can detect hovers over the line
    //(nothing responds to the hover in this test)
    //--------------------------------------------------------
    #ifndef MY_LINE_H
    #define MY_LINE_H
    
    #include <QtCore/qline.h>
    #include <QtGui/QPen>
    #include <QtWidgets/qgraphicsitem.h>
    #include <QtWidgets/qgraphicssceneevent.h>
    #include <QtCore/QPointer>
    #include "hover_delay.h"
    
    class MyLine : public QObject, public QGraphicsLineItem
    {
        Q_OBJECT
    
    public:
        explicit MyLine(const QLineF &line, QGraphicsItem *parent = 0);
        ~MyLine();
    
    Q_SIGNALS:
        void hoverChanged(bool is_hovering);
    
    protected:
        virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
    
    private:
        Q_DISABLE_COPY(MyLine)
        QPointer<HoverDelay> p_hover;
    };
    
    #endif
    
    //--------------------------------------------------------
    //File: my_line.cpp
    //--------------------------------------------------------
    #include "my_line.h"
    #include "hover_delay.h"
    #include "hover_item_listener.h"
    
    #include <QtGui/QPainter>
    #include <QtCore/QDebug>
    
    
    MyLine::MyLine(const QLineF &line, QGraphicsItem *parent) :
        QObject(), QGraphicsLineItem(line, parent)
    {
    qDebug() << "MyLine";
        p_hover = new HoverDelay(50, this, 7);
        if (!p_hover)
            return;
        new HoverItemListener(p_hover, this, 7);
        connect(p_hover, &HoverDelay::hoverChanged, this, &MyLine::hoverChanged);
    }
    
    MyLine::~MyLine()
    {
        qDebug() << "~MyLine";
    }
    
    void MyLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        QGraphicsLineItem::paint(painter, option, widget);
    }
    
    //--------------------------------------------------------
    //File: hover_delay.h
    //This class keeps track on whether a hover state is different to before
    //based on calls it gets from HoverItemListener. It can also add a delay when a
    //hover is first detected.
    //--------------------------------------------------------
    #ifndef HOVER_DELAY_H
    #define HOVER_DELAY_H
    
    #include <QtCore/qobject.h>
    #include <QtCore/qtimer.h>
    
    class HoverDelay : public QObject
    {
        Q_OBJECT
    
    public:
        explicit HoverDelay(int msec, QObject *parent = 0, int id=0);
        ~HoverDelay();
    
        void hoverEnterEvent(bool inside);
        void hoverMoveEvent(bool inside);
        void hoverLeaveEvent();
    
        bool isHover() const { return hover;   }
        void setHoverDelay(int msec);
        int hoverDelay() const { return timer.interval(); }
    
    Q_SIGNALS:
        void hoverChanged(bool);
        void hoverStarted();
        void hoverStopped();
    
    private Q_SLOTS:
        void start_hover();
    
    private:
        Q_DISABLE_COPY(HoverDelay)
        QTimer timer;
        bool hover;
        bool m_timerRunning;
        int m_id;
    };
    
    #endif
    
    //--------------------------------------------------------
    //File: hover_delay.cpp
    //--------------------------------------------------------
    #include "hover_delay.h"
    #include <QtCore/QDebug>
    
    HoverDelay::HoverDelay(int msec, QObject *parent, int id) :
        QObject(parent),
        hover(false),
        m_id(id)
    {
        timer.setInterval(msec);
        timer.setSingleShot(true);
        m_timerRunning = false;
        connect(&timer, &QTimer::timeout, this, &HoverDelay::start_hover);
    }
    
    HoverDelay::~HoverDelay()
    {
        timer.stop();
    }
    
    void HoverDelay::hoverEnterEvent(bool inside)
    {
        // Start the hover timer, if not already running
        if (inside)
        {
            if (!m_timerRunning)
            {
                timer.start();
                m_timerRunning = true;
            }
        }
    }
    
    void HoverDelay::hoverMoveEvent(bool inside)
    {
        if (!inside)
        {
            if (hover)
            {
                hover = false;
                emit hoverStopped();
                emit hoverChanged(false);
            }
            else
            {
                timer.stop();
                m_timerRunning = false;
            }
        }
        else
        {
            if (!hover)
            {
                //If the timer is currently running, it will call start_hover() when expired, so do nothing.
                //If timer is not currently running, start it, so we observe any required delay
                if (!m_timerRunning)
                {
                    timer.start();
                }
            }
        }
    }
    
    void HoverDelay::hoverLeaveEvent()
    {
        if (hover)
        {
            hover = false;
            emit hoverStopped();
            emit hoverChanged(false);
        }
        timer.stop();
        m_timerRunning = false;
    }
    
    //This is called when timer expires
    void HoverDelay::start_hover()
    {
        m_timerRunning = false;
        hover   = true;
        emit hoverStarted();
        emit hoverChanged(true);
    }
    
    void HoverDelay::setHoverDelay(int msec)
    {
        timer.setInterval(msec);
    }
    
    //--------------------------------------------------------
    //File: hover_item_listener.h
    //This class handles hover detection for a parent class by using SceneEventFilters.
    //It has an associated HoverDelay class which it calls when it gets hover events.
    //--------------------------------------------------------
    #ifndef HOVER_ITEM_LISTENER_H
    #define HOVER_ITEM_LISTENER_H
    
    #include <QtWidgets/QGraphicsItem>
    #include <QtCore/QPointer>
    
    class HoverDelay;
    
    class HoverItemListener : public QGraphicsItem
    {
    public:
        explicit HoverItemListener(QPointer<HoverDelay> hover, QGraphicsItem *parent, int id=0);
        ~HoverItemListener();
    
        inline void setRect(const QRectF &rect) {
            this->rect = rect;
        }
    
        virtual QRectF boundingRect() const { return QRectF(); }
        virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget *widget = 0) {}
    
    protected:
        virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
        virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value);
    
    private:
        Q_DISABLE_COPY(HoverItemListener)
        QRectF rect;
        const QPointer<HoverDelay> p_hover;
        int m_myId;
    };
    
    #endif
    
    //--------------------------------------------------------
    //File: hover_item_listener.cpp
    //--------------------------------------------------------
    #include "hover_item_listener.h"
    
    #include <QtWidgets/QGraphicsSceneHoverEvent>
    #include "hover_delay.h"
    
    #include <QtCore/QDebug>
    #include <QtWidgets/QGraphicsScene>
    
    extern bool    GlobalTestFlag1;    //If set, manually remove filter when item removed from scene
    
    
    HoverItemListener::HoverItemListener(QPointer<HoverDelay> hover, QGraphicsItem *parent, int id) :
        QGraphicsItem(parent),
        p_hover(hover),
        m_myId(id)
    {
        //Tell graphics that paint() function not required
        setFlags(ItemHasNoContents);
    
        //This causes all events of the parent to be passed to the
        //sceneEventFilter() function of "this"
        if (parent->scene())
        {
            parent->installSceneEventFilter(this);
        }
        parent->setAcceptHoverEvents(true);
    }
    
    
    HoverItemListener::~HoverItemListener()
    {
    }
    
    bool HoverItemListener::sceneEventFilter(QGraphicsItem *item, QEvent *event)
    {
        if (!p_hover)
        {
            qInfo() << "HoverItemListener::sceneEventFilter - Crash avoided";
            return false;
        }
    
        switch (event->type())
        {
        case QEvent::GraphicsSceneHoverEnter:
            if (rect.isNull() || rect.contains(static_cast<QGraphicsSceneHoverEvent*>(event)->pos()))
            {
                p_hover->hoverEnterEvent(true);
            }
            break;
    
        case QEvent::GraphicsSceneHoverLeave:
            // Definitely the end of the hover
            p_hover->hoverLeaveEvent();
            break;
    
        case QEvent::GraphicsSceneHoverMove:
            p_hover->hoverMoveEvent(rect.isNull() || rect.contains(static_cast<QGraphicsSceneHoverEvent*>(event)->pos()));
            break;
        default:
            break;
        }
    
        // Now process the event normally
        return false;
    }
    
    
    QVariant HoverItemListener::itemChange(GraphicsItemChange change, const QVariant &value)
    {
        if (!p_hover)
        {
            qInfo() << "HoverItemListener::itemChange - Crash avoided";
            return value;
        }
    
        if (change == ItemSceneHasChanged)
        {
            //In this case, the item's 'scene' is the new scene it belongs to
            QGraphicsItem *parent = parentItem();
            if (parent)
            {
                if (parent->scene())
                {
                    parent->installSceneEventFilter(this);
                }
                else
                {
                    //Don't need to do this.
                    //In qgrahpicsscene.cpp, QGraphicsScenePrivate::removeItemHelper, the scene filter is removed
                    //when an item is removed from the scene.
    
                    if (GlobalTestFlag1)
                    {
                        parent->removeSceneEventFilter(this);
                    }
                }
            }
        }
        else if (change == ItemVisibleHasChanged && !value.toBool())
        {
            if (p_hover->isHover())
            {
                // Ensure we cancel hover when the item is hidden
                p_hover->hoverLeaveEvent();
            }
        }
        return value;
    }
    
    //--------------------------------------------------------
    //File: my_view.h
    //This extends the basic view class to add signals for mouse events
    //--------------------------------------------------------
    #ifndef MY_VIEW_H
    #define MY_VIEW_H
    
    #include <QtGui/QMouseEvent>
    #include <QtWidgets/qgraphicsview.h>
    
    class QGraphicsView;
    
    class MyView : public QGraphicsView
    {
        Q_OBJECT
    
    public:
        explicit MyView(QWidget *parent = 0);
    
    Q_SIGNALS:
        void myMouseMiddleClick();
    
    protected:
        virtual void mousePressEvent(QMouseEvent *event);
    private:
        Q_DISABLE_COPY(MyView)
    };
    
    #endif
    
    //--------------------------------------------------------
    //File: my_view.cpp
    //--------------------------------------------------------
    #include "my_view.h"
    
    #include <QtCore/qdebug.h>
    #include <QtCore/QtMath>
    #include <QtGui/qevent.h>
    #include <QtWidgets/qgraphicsview.h>
    #include <QtWidgets/qapplication.h>
    #include <QtWidgets/qgraphicssceneevent.h>
    #include <QtWidgets/qscrollbar.h>
    #include <QtWidgets/QStyleOptionGraphicsItem>
    
    static qreal g_half_wind_size = 400;
    
    MyView::MyView(QWidget *parent) :
        QGraphicsView(parent)
    {
        QGraphicsScene* myScene = new QGraphicsScene(-g_half_wind_size, -g_half_wind_size, 2 * g_half_wind_size, 2 * g_half_wind_size, this);
    
        setScene(myScene);
        setContextMenuPolicy(Qt::NoContextMenu);
        setTransformationAnchor(NoAnchor);
        setCacheMode(CacheBackground);
    }
    
    void MyView::mousePressEvent(QMouseEvent *event)
    {
        QGraphicsView::mousePressEvent(event);
        if (event->isAccepted())
        {
            return;
        }
    
        if (event->button()==Qt::MiddleButton)
        {
            emit myMouseMiddleClick();
        }
    }
    
    #--------------------------------------------------------
    #File: Test1.pro
    #This is the Qt project file
    #--------------------------------------------------------
    QT -= gui
    QT += widgets
    
    CONFIG += c++11 console
    CONFIG -= app_bundle
    
    DEFINES += QT_DEPRECATED_WARNINGS
    
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
    
    SOURCES += main.cpp \
        hover_delay.cpp \
        hover_item_listener.cpp \
        my_view.cpp \
        my_line.cpp \
        my_itemA.cpp \
        my_itemB.cpp \
        my_itemB_manager.cpp
    
    HEADERS += \
        hover_delay.h \
        hover_item_listener.h \
        my_view.h \
        my_line.h \
        my_itemA.h \
        my_itemB.h \
        my_itemB_manager.h
    
    A 1 Reply Last reply
    0
    • J JEllery

      Overview:
      Using Qt 5.9.2
      Crash in QGraphicsScene::event, QGraphicsScene::mouseMoveEvent when using a hover event on one item to trigger removing a second item from the scene (removeItem), then deleting it later (deleteLater). Both items use SceneEventFilters.

      Details:
      The problem can be reproduced in the following simplified case:

      -We have 2 main items in the scene, which have separate classes, MyItemA and MyItemB. MyItemA draws a yellow rectangle and MyItemB draws a set of red lines. Each line is a class of type MyLine

      -MyItemB class has a related MyItemBManager class that controls creation and deletion of the MyItemB object.

      -We have a 'helper' class called HoverItemListener that creates a SceneEventFilter to listen for hover events on a parent. This helper class is used for both MyItemA and MyLine classes.

      -When the helper class detects a change in the hover, it informs an intermediate class called HoverDelay, that adds a delay and emits a signal to say the hover state has changed. This signal is connected to slots on the associated MyItemA or MyLine class

      -When MyItemA (the rectangle) gets a signal that hover has changed to on, it changes its colour. When it then receives a signal to say the hover has ended, it emits a signal that is used by MyItemBManager to remove the MyItemB (red lines) from the scene, then delete (using removeItem and deleteLater).

      Most of the time, this causes a segmentation violation.

      The crash does not seem to occur if we remove the use of SceneEventFilters and instead detect the hover directly in MyItemA and MyLine class but we dont really want to put all that code in to each class, if we can avoid it (since we use this in many classes).

      We would like to know if there is something wrong with our implementation, or if this is a Qt bug.

      We have looked at other reported problems that suggest the problem may be related to a missing call to prepareGeometryChange() but the geometry does not change in this test and we tried turning off BSP indexing by adding a call to
      myScene->setItemIndexMethod(QGraphicsScene::NoIndex);
      but this made no difference.

      We have also tried removing the SceneEventFilter manually when the HoverItemListener gets an itemChange event to say it has been removed from the scene. This did not help and this is done anyway in Qt in file qgrahpicsscene.cpp, function QGraphicsScenePrivate::removeItemHelper

      The following code creates a test program that demonstrates the problem. The Qt project file is also included.

      When the application is started, it draws a yellow rectangle and some red lines.
      If the mouse is moved over the yellow rectangle it will change colour to cyan. When the mouse is moved out of the rectangle again, the red lines should be turned off but it usually crashes.
      If it does not crash first time, the middle mouse can be clicked to display the red lines again
      and the mouse moved over and off the yellow rectangle to retry. The red lines must be displayed before moving the mouse over and off the yellow rectangle, to get the crash.
      Usually no more than a few times are required to get a crash.

      //--------------------------------------------------------
      //File: main.cpp
      //This creates the view, scene and all objects.
      //It sets up the connection to toggle the red lines on/off on middle mouse click
      //and to respond to a hover change on MyItemA (rectangle)
      //--------------------------------------------------------
      #include <QApplication>
      #include<QGraphicsView>
      #include<QGraphicsScene>
      
      #include "my_view.h"
      #include "my_itemB_manager.h"
      #include "my_itemA.h"
      
      //Global for test
      MyItemBManager* myMgr;
      bool    GlobalTestFlag1 = false;     //If true, manually remove scene filters when item removed from scene
      
      int main(int argc, char *argv[])
      {
          QApplication qapp(argc, argv);
      
          MyView* view=nullptr;
      
          view= new MyView;
          view->setBackgroundBrush(QBrush(Qt::white, Qt::SolidPattern));
          view->show();
      
          QGraphicsScene* myScene = view->scene();
      
          //Create a route (red lines for test)
          myMgr = new MyItemBManager(myScene);
      
          myMgr->setVisible(true);
      
          //Toggle route display on middle mouse click
          QObject::connect(view, &MyView::myMouseMiddleClick, myMgr, &MyItemBManager::toggleVisible);
      
      
          //Create a control object (yellow rectangle for test)
          MyItemA* myItemA = new MyItemA;
      
          //Turn off route display when focus lost
          QObject::connect(myItemA, &MyItemA::explicitHoverChanged, myItemA, &MyItemA::manage_test2);
      
          myScene->addItem(myItemA);
          QPointF pos(100,100);
          myItemA->setPosition(pos);
      
      
          return qapp.exec();
      }
      
      //--------------------------------------------------------
      //File: my_itemA.h
      //This class draws a rectangle and uses HoverItemListener and HoverDelay
      //classes to detect hover changes.
      //It emits a signal when hover has changed.
      //In this simplified code, it also responds to that signal via 'manage_test2' slot.
      //--------------------------------------------------------
      #ifndef MYITEMA_H
      #define MYITEMA_H
      
      #include <QtWidgets/qgraphicsitem.h>
      #include <QtCore/QPointer>
      
      class HoverDelay;
      class HoverItemListener;
      
      class MyItemA : public QGraphicsObject
      {
         Q_OBJECT
      
      public:
         explicit MyItemA(QGraphicsItem *parent = 0);
      
         void setPosition (const QPointF &pos);
      
         virtual QRectF boundingRect() const;
         virtual QPainterPath shape() const;
      
      public Q_SLOTS:
         void manage_test2(bool);
      
      Q_SIGNALS:
         void explicitHoverChanged(bool);
      
      protected:
         QRectF labelBoundingRect() const;
      
      private Q_SLOTS:
         void label_hover_changed(bool hovered);
      
      private:
         Q_DISABLE_COPY(MyItemA)
         void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
      
         mutable bool label_box_valid;
         mutable bool shape_valid;
         mutable QRectF label_box;
         mutable QPainterPath p_shape;
         bool p_hover_active;
         QPointer<HoverDelay> p_hover_label;
         mutable HoverItemListener *p_hover_label_listener;
      
         mutable QRectF p_bounding_rect;
      };
      
      #endif // MYITEMA_H
      
      //--------------------------------------------------------
      //File: my_itemA.cpp
      //--------------------------------------------------------
      #include <QtGui/qpainter.h>
      #include <QtWidgets/qgraphicsscene.h>
      #include <QtWidgets/qgraphicssceneevent.h>
      #include <QtWidgets/qgraphicsview.h>
      #include <QtWidgets/qwidget.h>
      #include <QtWidgets/qstyleoption.h>
      
      #include <QtCore/qdebug.h>
      
      
      #include "my_itemA.h"
      #include "hover_delay.h"
      #include "hover_item_listener.h"
      #include "my_itemB_manager.h"
      
      
      static int g_hover_delay = 50;
      
       //Global for test
      extern MyItemBManager* myMgr;
      
      
      MyItemA::MyItemA(QGraphicsItem *parent) :
         QGraphicsObject(parent),
         label_box_valid(false),
         shape_valid(false)
      {
          p_hover_active = false;
          p_hover_label = new HoverDelay(g_hover_delay,  this, 2);
      
          p_hover_label_listener = new HoverItemListener(p_hover_label,  this, 2);
      
          connect(p_hover_label,  &HoverDelay::hoverChanged, this, &MyItemA::label_hover_changed);
          setFlags(ItemIgnoresTransformations | ItemSendsScenePositionChanges | ItemClipsToShape);
      
      
         // Set the correct granularity (for all the children too)
         setBoundingRegionGranularity(1.0);
      }
      
      void MyItemA::setPosition(const QPointF &newpos)
      {
         QGraphicsObject::setPos(newpos);
      }
      
      
      QRectF MyItemA::boundingRect() const
      {
         if (!label_box_valid)
         {
            {
               label_box_valid = true;
      
               //Just use a fixed size for demo
               label_box = QRectF(0,0,100,50);
      
               p_hover_label_listener->setRect(label_box);
      
               p_bounding_rect = label_box;
            }
         }
      
         return p_bounding_rect;
      }
      
      QPainterPath MyItemA::shape() const
      {
         if (!shape_valid)
         {
            if (!label_box_valid)
            {
               boundingRect();
            }
      
            QPainterPath new_shape;
            new_shape.setFillRule(Qt::WindingFill);
      
            // label - just a solid block
            new_shape.addRect(label_box);
      
            p_shape = new_shape;
            shape_valid = true;
         }
      	
         return p_shape;
      }
      
      void MyItemA::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
      {
         Q_UNUSED(option);
         Q_UNUSED(widget);
         painter->save();
      
         {
            if (p_hover_active)
               painter->fillRect(label_box, Qt::cyan);
            else
               painter->fillRect(label_box, Qt::yellow);
         }
      
         painter->restore();
      }
      
      void MyItemA::label_hover_changed(bool hovered)
      {
         p_hover_active = hovered;
      
         prepareGeometryChange();
         label_box_valid = false;
         shape_valid     = false;
         update();
      
         //This triggers the flight leg to be deleted, when hovered is false
         emit explicitHoverChanged(hovered);
      }
      
      //Simplified version of original code
      void MyItemA::manage_test2(bool enabled)
      {
         if (!enabled)
         {
             if (myMgr)
               myMgr->setVisible(false);
         }
      }
      
      //--------------------------------------------------------
      //File: my_itemB.h
      //This class creates a set of lines, based on points given.
      //The drawing is not done by this class but by the line classes.
      //--------------------------------------------------------
      #ifndef MYITEMB_H
      #define MYITEMB_H
      
      #include <QtWidgets/qgraphicsitem.h>
      
      class MyLine;
      
      class MyItemB : public QObject, public QAbstractGraphicsShapeItem
      {
         Q_OBJECT
      
      public:
         explicit MyItemB(QGraphicsItem *parent = 0);
         ~MyItemB();
      
         void setRoute(const QList<QPointF> &points);
      	
         virtual QRectF boundingRect() const;
         virtual void   paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
      
      private:
         Q_DISABLE_COPY(MyItemB)
         QList<MyLine*> p_flight_line;
      
         QList<QPointF> p_points;
         void updateContents();
      };
      
      #endif
      
      //--------------------------------------------------------
      //File: my_itemB.cpp
      //--------------------------------------------------------
      #include "my_itemB.h"
      
      #include <QtCore/QDebug>
      #include <QtWidgets/qapplication.h>
      #include <QtGui/qpen.h>
      #include <QtWidgets/qgraphicsscene.h>
      
      #include "my_line.h"
      
      static int g_line_width         = 2;
      static QColor g_line_colour = Qt::red;
      
      MyItemB::MyItemB(QGraphicsItem *parent) :
          QObject(),
          QAbstractGraphicsShapeItem(parent)
      {
          qDebug() << "MyItemB()";
          setFlags(ItemHasNoContents);
          QPen the_pen = pen();
          the_pen.setColor(g_line_colour);
          the_pen.setWidth(g_line_width);
          the_pen.setCosmetic(true);
          the_pen.setCapStyle(Qt::FlatCap);
          setPen(the_pen);
      }
      
      MyItemB::~MyItemB()
      {
          qDebug() << "~MyItemB";
         qDebug() << "-";
      }
      
      void MyItemB::setRoute(const QList<QPointF> &points)
      {
          p_points = points;
          updateContents();
      }
      
      QRectF MyItemB::boundingRect() const
      {
          // We have done setFlags(ItemHasNoContents)
          return QRectF();
      }
      
      void MyItemB::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
      {
         // We have done setFlags(ItemHasNoContents)
      }
      
      void MyItemB::updateContents()
      {
         // Update the polyline to the new set of points
      
         // Line goes through all known points
         int nb_points = p_points.size();
      
         QPointF prev_point;
         QPointF next_point;
      
         for (int nb=0; nb<nb_points; nb++)
         {
            const QPointF &point = p_points.at(nb);
      
            if (nb == 0)
            {
               next_point = point;
            }
            else
            {
               // Always draw the line
               next_point = point;
      
               if (p_flight_line.size() < nb)
               {
                  // create a new MyLine
                  MyLine *line = new MyLine(QLineF(prev_point, next_point), this);
                  line->setPen(pen());
      
                  p_flight_line.append(line);
               }
               else
               {
                  // re-use the existing MyLine
                  p_flight_line.at(nb-1)->setLine(QLineF(prev_point, next_point));
               }
            }
      
            prev_point = next_point;
         }
      }
      
      //--------------------------------------------------------
      //File: my_itemB_manager.h
      //This class controls the creation and removal of a MyItemB object
      //--------------------------------------------------------
      #ifndef MYITEMB_MANAGER_H
      #define MYITEMB_MANAGER_H
      
      #include <QtCore/QObject>
      
      class MyItemB;
      class QGraphicsScene;
      
      class MyItemBManager : public QObject
      {
         Q_OBJECT
      public:
         explicit MyItemBManager(QGraphicsScene *scene);
         ~MyItemBManager();
         void setVisible(bool visible);
      
      public Q_SLOTS:
         void toggleVisible();
      
      private:
          QGraphicsScene * m_scene;
          MyItemB* m_leg;
          bool m_visible;
      };
      
      #endif
      
      //--------------------------------------------------------
      //File: my_itemB_manager.cpp
      //--------------------------------------------------------
      #include "my_itemB_manager.h"
      #include "my_itemB.h"
      
      #include <QtCore/QDebug>
      #include <QtWidgets/QApplication>
      #include <QGraphicsScene>
      
      
      MyItemBManager::MyItemBManager(QGraphicsScene *scene) :
          m_scene(scene),
          m_leg(nullptr),
          m_visible(false)
      {
      }
      
      MyItemBManager::~MyItemBManager()
      {
          if (m_leg)
          {
              m_leg->deleteLater();
              m_leg = nullptr;
          }
      }
      
      void MyItemBManager::toggleVisible()
      {
          m_visible = (m_visible) ? false : true;
          setVisible(m_visible);
      }
      
      void MyItemBManager::setVisible(bool visible)
      {
          m_visible = visible;
          if (visible)	// show it
          {
              if (!m_leg)
              {
      qDebug() << "Creating leg";
                  // Create it since it doesn't already exist
                  m_leg = new MyItemB();
      
                  QList<QPointF> pos;
                  pos.append(QPointF(-100,-100));
                  pos.append(QPointF(20,20));
                  pos.append(QPointF(50,100));
                  pos.append(QPointF(100,300));
                  pos.append(QPointF(200,50));
      
                  m_leg->setRoute(pos);
                  m_scene->addItem(m_leg);
              }
          }
          else		// remove and delete
          {
              if (m_leg)
              {
                  QGraphicsScene* legScene = m_leg->scene();
                  if (legScene)
                  {
                      //This removes all children from scene too
                      legScene->removeItem(m_leg);
                  }
      
                  m_leg->deleteLater();
                  m_leg = nullptr;
              }
          }
      }
      
      //--------------------------------------------------------
      //File: my_line.h
      //This class draws a line that can detect hovers over the line
      //(nothing responds to the hover in this test)
      //--------------------------------------------------------
      #ifndef MY_LINE_H
      #define MY_LINE_H
      
      #include <QtCore/qline.h>
      #include <QtGui/QPen>
      #include <QtWidgets/qgraphicsitem.h>
      #include <QtWidgets/qgraphicssceneevent.h>
      #include <QtCore/QPointer>
      #include "hover_delay.h"
      
      class MyLine : public QObject, public QGraphicsLineItem
      {
          Q_OBJECT
      
      public:
          explicit MyLine(const QLineF &line, QGraphicsItem *parent = 0);
          ~MyLine();
      
      Q_SIGNALS:
          void hoverChanged(bool is_hovering);
      
      protected:
          virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
      
      private:
          Q_DISABLE_COPY(MyLine)
          QPointer<HoverDelay> p_hover;
      };
      
      #endif
      
      //--------------------------------------------------------
      //File: my_line.cpp
      //--------------------------------------------------------
      #include "my_line.h"
      #include "hover_delay.h"
      #include "hover_item_listener.h"
      
      #include <QtGui/QPainter>
      #include <QtCore/QDebug>
      
      
      MyLine::MyLine(const QLineF &line, QGraphicsItem *parent) :
          QObject(), QGraphicsLineItem(line, parent)
      {
      qDebug() << "MyLine";
          p_hover = new HoverDelay(50, this, 7);
          if (!p_hover)
              return;
          new HoverItemListener(p_hover, this, 7);
          connect(p_hover, &HoverDelay::hoverChanged, this, &MyLine::hoverChanged);
      }
      
      MyLine::~MyLine()
      {
          qDebug() << "~MyLine";
      }
      
      void MyLine::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
      {
          QGraphicsLineItem::paint(painter, option, widget);
      }
      
      //--------------------------------------------------------
      //File: hover_delay.h
      //This class keeps track on whether a hover state is different to before
      //based on calls it gets from HoverItemListener. It can also add a delay when a
      //hover is first detected.
      //--------------------------------------------------------
      #ifndef HOVER_DELAY_H
      #define HOVER_DELAY_H
      
      #include <QtCore/qobject.h>
      #include <QtCore/qtimer.h>
      
      class HoverDelay : public QObject
      {
          Q_OBJECT
      
      public:
          explicit HoverDelay(int msec, QObject *parent = 0, int id=0);
          ~HoverDelay();
      
          void hoverEnterEvent(bool inside);
          void hoverMoveEvent(bool inside);
          void hoverLeaveEvent();
      
          bool isHover() const { return hover;   }
          void setHoverDelay(int msec);
          int hoverDelay() const { return timer.interval(); }
      
      Q_SIGNALS:
          void hoverChanged(bool);
          void hoverStarted();
          void hoverStopped();
      
      private Q_SLOTS:
          void start_hover();
      
      private:
          Q_DISABLE_COPY(HoverDelay)
          QTimer timer;
          bool hover;
          bool m_timerRunning;
          int m_id;
      };
      
      #endif
      
      //--------------------------------------------------------
      //File: hover_delay.cpp
      //--------------------------------------------------------
      #include "hover_delay.h"
      #include <QtCore/QDebug>
      
      HoverDelay::HoverDelay(int msec, QObject *parent, int id) :
          QObject(parent),
          hover(false),
          m_id(id)
      {
          timer.setInterval(msec);
          timer.setSingleShot(true);
          m_timerRunning = false;
          connect(&timer, &QTimer::timeout, this, &HoverDelay::start_hover);
      }
      
      HoverDelay::~HoverDelay()
      {
          timer.stop();
      }
      
      void HoverDelay::hoverEnterEvent(bool inside)
      {
          // Start the hover timer, if not already running
          if (inside)
          {
              if (!m_timerRunning)
              {
                  timer.start();
                  m_timerRunning = true;
              }
          }
      }
      
      void HoverDelay::hoverMoveEvent(bool inside)
      {
          if (!inside)
          {
              if (hover)
              {
                  hover = false;
                  emit hoverStopped();
                  emit hoverChanged(false);
              }
              else
              {
                  timer.stop();
                  m_timerRunning = false;
              }
          }
          else
          {
              if (!hover)
              {
                  //If the timer is currently running, it will call start_hover() when expired, so do nothing.
                  //If timer is not currently running, start it, so we observe any required delay
                  if (!m_timerRunning)
                  {
                      timer.start();
                  }
              }
          }
      }
      
      void HoverDelay::hoverLeaveEvent()
      {
          if (hover)
          {
              hover = false;
              emit hoverStopped();
              emit hoverChanged(false);
          }
          timer.stop();
          m_timerRunning = false;
      }
      
      //This is called when timer expires
      void HoverDelay::start_hover()
      {
          m_timerRunning = false;
          hover   = true;
          emit hoverStarted();
          emit hoverChanged(true);
      }
      
      void HoverDelay::setHoverDelay(int msec)
      {
          timer.setInterval(msec);
      }
      
      //--------------------------------------------------------
      //File: hover_item_listener.h
      //This class handles hover detection for a parent class by using SceneEventFilters.
      //It has an associated HoverDelay class which it calls when it gets hover events.
      //--------------------------------------------------------
      #ifndef HOVER_ITEM_LISTENER_H
      #define HOVER_ITEM_LISTENER_H
      
      #include <QtWidgets/QGraphicsItem>
      #include <QtCore/QPointer>
      
      class HoverDelay;
      
      class HoverItemListener : public QGraphicsItem
      {
      public:
          explicit HoverItemListener(QPointer<HoverDelay> hover, QGraphicsItem *parent, int id=0);
          ~HoverItemListener();
      
          inline void setRect(const QRectF &rect) {
              this->rect = rect;
          }
      
          virtual QRectF boundingRect() const { return QRectF(); }
          virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget *widget = 0) {}
      
      protected:
          virtual bool sceneEventFilter(QGraphicsItem *watched, QEvent *event);
          virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value);
      
      private:
          Q_DISABLE_COPY(HoverItemListener)
          QRectF rect;
          const QPointer<HoverDelay> p_hover;
          int m_myId;
      };
      
      #endif
      
      //--------------------------------------------------------
      //File: hover_item_listener.cpp
      //--------------------------------------------------------
      #include "hover_item_listener.h"
      
      #include <QtWidgets/QGraphicsSceneHoverEvent>
      #include "hover_delay.h"
      
      #include <QtCore/QDebug>
      #include <QtWidgets/QGraphicsScene>
      
      extern bool    GlobalTestFlag1;    //If set, manually remove filter when item removed from scene
      
      
      HoverItemListener::HoverItemListener(QPointer<HoverDelay> hover, QGraphicsItem *parent, int id) :
          QGraphicsItem(parent),
          p_hover(hover),
          m_myId(id)
      {
          //Tell graphics that paint() function not required
          setFlags(ItemHasNoContents);
      
          //This causes all events of the parent to be passed to the
          //sceneEventFilter() function of "this"
          if (parent->scene())
          {
              parent->installSceneEventFilter(this);
          }
          parent->setAcceptHoverEvents(true);
      }
      
      
      HoverItemListener::~HoverItemListener()
      {
      }
      
      bool HoverItemListener::sceneEventFilter(QGraphicsItem *item, QEvent *event)
      {
          if (!p_hover)
          {
              qInfo() << "HoverItemListener::sceneEventFilter - Crash avoided";
              return false;
          }
      
          switch (event->type())
          {
          case QEvent::GraphicsSceneHoverEnter:
              if (rect.isNull() || rect.contains(static_cast<QGraphicsSceneHoverEvent*>(event)->pos()))
              {
                  p_hover->hoverEnterEvent(true);
              }
              break;
      
          case QEvent::GraphicsSceneHoverLeave:
              // Definitely the end of the hover
              p_hover->hoverLeaveEvent();
              break;
      
          case QEvent::GraphicsSceneHoverMove:
              p_hover->hoverMoveEvent(rect.isNull() || rect.contains(static_cast<QGraphicsSceneHoverEvent*>(event)->pos()));
              break;
          default:
              break;
          }
      
          // Now process the event normally
          return false;
      }
      
      
      QVariant HoverItemListener::itemChange(GraphicsItemChange change, const QVariant &value)
      {
          if (!p_hover)
          {
              qInfo() << "HoverItemListener::itemChange - Crash avoided";
              return value;
          }
      
          if (change == ItemSceneHasChanged)
          {
              //In this case, the item's 'scene' is the new scene it belongs to
              QGraphicsItem *parent = parentItem();
              if (parent)
              {
                  if (parent->scene())
                  {
                      parent->installSceneEventFilter(this);
                  }
                  else
                  {
                      //Don't need to do this.
                      //In qgrahpicsscene.cpp, QGraphicsScenePrivate::removeItemHelper, the scene filter is removed
                      //when an item is removed from the scene.
      
                      if (GlobalTestFlag1)
                      {
                          parent->removeSceneEventFilter(this);
                      }
                  }
              }
          }
          else if (change == ItemVisibleHasChanged && !value.toBool())
          {
              if (p_hover->isHover())
              {
                  // Ensure we cancel hover when the item is hidden
                  p_hover->hoverLeaveEvent();
              }
          }
          return value;
      }
      
      //--------------------------------------------------------
      //File: my_view.h
      //This extends the basic view class to add signals for mouse events
      //--------------------------------------------------------
      #ifndef MY_VIEW_H
      #define MY_VIEW_H
      
      #include <QtGui/QMouseEvent>
      #include <QtWidgets/qgraphicsview.h>
      
      class QGraphicsView;
      
      class MyView : public QGraphicsView
      {
          Q_OBJECT
      
      public:
          explicit MyView(QWidget *parent = 0);
      
      Q_SIGNALS:
          void myMouseMiddleClick();
      
      protected:
          virtual void mousePressEvent(QMouseEvent *event);
      private:
          Q_DISABLE_COPY(MyView)
      };
      
      #endif
      
      //--------------------------------------------------------
      //File: my_view.cpp
      //--------------------------------------------------------
      #include "my_view.h"
      
      #include <QtCore/qdebug.h>
      #include <QtCore/QtMath>
      #include <QtGui/qevent.h>
      #include <QtWidgets/qgraphicsview.h>
      #include <QtWidgets/qapplication.h>
      #include <QtWidgets/qgraphicssceneevent.h>
      #include <QtWidgets/qscrollbar.h>
      #include <QtWidgets/QStyleOptionGraphicsItem>
      
      static qreal g_half_wind_size = 400;
      
      MyView::MyView(QWidget *parent) :
          QGraphicsView(parent)
      {
          QGraphicsScene* myScene = new QGraphicsScene(-g_half_wind_size, -g_half_wind_size, 2 * g_half_wind_size, 2 * g_half_wind_size, this);
      
          setScene(myScene);
          setContextMenuPolicy(Qt::NoContextMenu);
          setTransformationAnchor(NoAnchor);
          setCacheMode(CacheBackground);
      }
      
      void MyView::mousePressEvent(QMouseEvent *event)
      {
          QGraphicsView::mousePressEvent(event);
          if (event->isAccepted())
          {
              return;
          }
      
          if (event->button()==Qt::MiddleButton)
          {
              emit myMouseMiddleClick();
          }
      }
      
      #--------------------------------------------------------
      #File: Test1.pro
      #This is the Qt project file
      #--------------------------------------------------------
      QT -= gui
      QT += widgets
      
      CONFIG += c++11 console
      CONFIG -= app_bundle
      
      DEFINES += QT_DEPRECATED_WARNINGS
      
      #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000
      
      SOURCES += main.cpp \
          hover_delay.cpp \
          hover_item_listener.cpp \
          my_view.cpp \
          my_line.cpp \
          my_itemA.cpp \
          my_itemB.cpp \
          my_itemB_manager.cpp
      
      HEADERS += \
          hover_delay.h \
          hover_item_listener.h \
          my_view.h \
          my_line.h \
          my_itemA.h \
          my_itemB.h \
          my_itemB_manager.h
      
      A Offline
      A Offline
      ambershark
      wrote on last edited by
      #2

      @JEllery Thanks for all the info and code! It helps not needing to ask for it, lol.

      Is it possible for you to share a stack trace when the crash happens? When I get some time tomorrow I will try running it and making it crash, but if I don't get a chance a backtrace would help a lot.

      My guess based on what you've described is that the item being removed is still being referenced somewhere. Using deleteLater should have protected from this type of thing but sometimes it doesn't..

      Does the crash go away if you stop calling deleteLater or any sort of delete? Obviously this will cause memory leaks so it's temporary but I'm curious if the crash will stop when you do that.

      I will look at the code tomorrow when I'm not so tired. :)

      My L-GPL'd C++ Logger github.com/ambershark-mike/sharklog

      1 Reply Last reply
      1
      • J Offline
        J Offline
        JEllery
        wrote on last edited by
        #3

        Thank you for your reply.
        This is the trace from gdb:

        Program received signal SIGSEGV, Segmentation fault.
        0x00007f835b167d53 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        (gdb) where
        #0  0x00007f835b167d53 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #1  0x00007f835b167df1 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #2  0x00007f835b16cb80 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #3  0x00007f835b16cdd7 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #4  0x00007f835b16d3ab in QGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent*) () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #5  0x00007f835b178394 in QGraphicsScene::event(QEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #6  0x00007f835ae855ec in QApplicationPrivate::notify_helper(QObject*, QEvent*)
            () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #7  0x00007f835ae8ca17 in QApplication::notify(QObject*, QEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #8  0x00007f835a0c12b8 in QCoreApplication::notifyInternal2(QObject*, QEvent*)
            () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #9  0x00007f835b19091f in QGraphicsViewPrivate::mouseMoveEventHandler(QMouseEvent*) () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #10 0x00007f835aec19b8 in QWidget::event(QEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #11 0x00007f835af65ace in QFrame::event(QEvent*) ()
        ---Type <return> to continue, or q <return> to quit---
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #12 0x00007f835b19341b in QGraphicsView::viewportEvent(QEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #13 0x00007f835a0c1052 in QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject*, QEvent*) () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #14 0x00007f835ae855c5 in QApplicationPrivate::notify_helper(QObject*, QEvent*)
            () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #15 0x00007f835ae8d3f3 in QApplication::notify(QObject*, QEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #16 0x00007f835a0c12b8 in QCoreApplication::notifyInternal2(QObject*, QEvent*)
            () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #17 0x00007f835ae8c05f in QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #18 0x00007f835aedaf56 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #19 0x00007f835aedd8ab in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #20 0x00007f835ae855ec in QApplicationPrivate::notify_helper(QObject*, QEvent*)
            () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #21 0x00007f835ae8ca17 in QApplication::notify(QObject*, QEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Widgets.so.5
        #22 0x00007f835a0c12b8 in QCoreApplication::notifyInternal2(QObject*, QEvent*)
        ---Type <return> to continue, or q <return> to quit---
            () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #23 0x00007f835a6b0940 in QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::MouseEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Gui.so.5
        #24 0x00007f835a6b2735 in QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent*) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Gui.so.5
        #25 0x00007f835a68df7b in QWindowSystemInterface::sendWindowSystemEvents(QFlags<QEventLoop::ProcessEventsFlag>) ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Gui.so.5
        #26 0x00007f834c570ad0 in ?? ()
           from /opt/Qt5.9.2/5.9.2/gcc_64/plugins/platforms/../../lib/libQt5XcbQpa.so.5
        #27 0x00007f83562747aa in g_main_context_dispatch ()
           from /lib64/libglib-2.0.so.0
        #28 0x00007f8356274af8 in g_main_context_iterate.isra ()
           from /lib64/libglib-2.0.so.0
        #29 0x00007f8356274bac in g_main_context_iteration ()
           from /lib64/libglib-2.0.so.0
        #30 0x00007f835a113baf in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #31 0x00007f835a0bf8aa in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #32 0x00007f835a0c8134 in QCoreApplication::exec() ()
        ---Type <return> to continue, or q <return> to quit---
           from /opt/Qt5.9.2/5.9.2/gcc_64/lib/libQt5Core.so.5
        #33 0x0000000000407010 in main (argc=1, argv=0x7ffe425a9948)
            at ../Test1a/main.cpp:51
        
        

        If m_leg->deleteLater(); is removed from function MyItemBManager::setVisible(), then it does still crash with a very similar backtrace

        1 Reply Last reply
        0
        • J Offline
          J Offline
          JEllery
          wrote on last edited by
          #4

          This issue has been resolved by Andy Shaw from Qt support.

          The problem is due to calling removeItem() in MyItemBManager::setVisible()

          This function is being called from an event handler (in this case from HoverItemListener::sceneEventFilter, when event is QEvent::GraphicsSceneHoverLeave and direct signal/slot connections).

          Consequently the scene is being changed by removeItem before returning to the event loop and this is causing a problem.

          It was not clear exactly why this is, since the item being removed from the scene is not the one generating the event.

          (It has nothing to do with the deleteLater, since that is done later and the crash occurs without doing the delete at all)

          The solution is to ensure that removeItem is not called until the process returns to the event loop. This would be like a "remove Item later" idea.

          In this particular example, the ItemB is going to be deleted anyway by deleteLater, so the solution can be to simply remove the call to removeItem, since the destructor removes an item from the scene before destroying it and the deleteLater handles the necessary delay.

          The other solution is to manually delay the removeItem until the process has returned to the event loop. This can be done from a single shot timer like:

          if (legScene)
          {
              QTimer::singleShot(0, this, [=](){
                              legScene->removeItem(m_leg);
                              m_leg->deleteLater();
                              m_leg = nullptr;
                          });
          }
          else
          {
              m_leg->deleteLater();
              m_leg = nullptr;
          }
          

          We had originally put the removeItem in separately because the documentation for QGraphicsItem::~QGraphicsItem() says that it is more efficient to remove
          the item from the scene before destroying it - but it seems this needs to be done with caution.

          A enjoysmathE 2 Replies Last reply
          3
          • J JEllery

            This issue has been resolved by Andy Shaw from Qt support.

            The problem is due to calling removeItem() in MyItemBManager::setVisible()

            This function is being called from an event handler (in this case from HoverItemListener::sceneEventFilter, when event is QEvent::GraphicsSceneHoverLeave and direct signal/slot connections).

            Consequently the scene is being changed by removeItem before returning to the event loop and this is causing a problem.

            It was not clear exactly why this is, since the item being removed from the scene is not the one generating the event.

            (It has nothing to do with the deleteLater, since that is done later and the crash occurs without doing the delete at all)

            The solution is to ensure that removeItem is not called until the process returns to the event loop. This would be like a "remove Item later" idea.

            In this particular example, the ItemB is going to be deleted anyway by deleteLater, so the solution can be to simply remove the call to removeItem, since the destructor removes an item from the scene before destroying it and the deleteLater handles the necessary delay.

            The other solution is to manually delay the removeItem until the process has returned to the event loop. This can be done from a single shot timer like:

            if (legScene)
            {
                QTimer::singleShot(0, this, [=](){
                                legScene->removeItem(m_leg);
                                m_leg->deleteLater();
                                m_leg = nullptr;
                            });
            }
            else
            {
                m_leg->deleteLater();
                m_leg = nullptr;
            }
            

            We had originally put the removeItem in separately because the documentation for QGraphicsItem::~QGraphicsItem() says that it is more efficient to remove
            the item from the scene before destroying it - but it seems this needs to be done with caution.

            A Offline
            A Offline
            ambershark
            wrote on last edited by
            #5

            @JEllery Glad you got it fixed. Sorry I disappeared mid helping you. I get really busy with work sometimes and don't have time to be on the forums here.

            My L-GPL'd C++ Logger github.com/ambershark-mike/sharklog

            1 Reply Last reply
            0
            • J JEllery

              This issue has been resolved by Andy Shaw from Qt support.

              The problem is due to calling removeItem() in MyItemBManager::setVisible()

              This function is being called from an event handler (in this case from HoverItemListener::sceneEventFilter, when event is QEvent::GraphicsSceneHoverLeave and direct signal/slot connections).

              Consequently the scene is being changed by removeItem before returning to the event loop and this is causing a problem.

              It was not clear exactly why this is, since the item being removed from the scene is not the one generating the event.

              (It has nothing to do with the deleteLater, since that is done later and the crash occurs without doing the delete at all)

              The solution is to ensure that removeItem is not called until the process returns to the event loop. This would be like a "remove Item later" idea.

              In this particular example, the ItemB is going to be deleted anyway by deleteLater, so the solution can be to simply remove the call to removeItem, since the destructor removes an item from the scene before destroying it and the deleteLater handles the necessary delay.

              The other solution is to manually delay the removeItem until the process has returned to the event loop. This can be done from a single shot timer like:

              if (legScene)
              {
                  QTimer::singleShot(0, this, [=](){
                                  legScene->removeItem(m_leg);
                                  m_leg->deleteLater();
                                  m_leg = nullptr;
                              });
              }
              else
              {
                  m_leg->deleteLater();
                  m_leg = nullptr;
              }
              

              We had originally put the removeItem in separately because the documentation for QGraphicsItem::~QGraphicsItem() says that it is more efficient to remove
              the item from the scene before destroying it - but it seems this needs to be done with caution.

              enjoysmathE Offline
              enjoysmathE Offline
              enjoysmath
              wrote on last edited by
              #6

              @JEllery

              Thanks so much for solving this. It seems to solve my deletion-crash issue as well. :)

              https://github.com/enjoysmath
              https://math.stackexchange.com/users/26327/exercisingmathematician

              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