Selection of items in QGraphicsScene

Francesco picture Francesco · Mar 26, 2014 · Viewed 13.1k times · Source

I am trying to understand how it is possible to redefine the way items are selected and transformed (once selected) in a QGraphicsScene. For example changing length of a line, move a line, change a polygon by moving one of its points.

I have created a child of QGraphicsView and have started to overload its mousePressEvent, but it seems that the selection and move actions are captured by QGraphicsItem. How can I override it as they are protected and not visible from a child of QGraphicsView?

I can imagine I need to overload QGraphicsItem::mousePressEvent in a myGraphicsItem, but then it means I have to also overload the QGraphicsScene for handling myGraphicsItem? And how do I handle the selected item position in a scene when it gets moved?

Are there any examples I could look at?

I am (clearly) a bit lost.

UPDATE: Based on the feedback I have created a child of QGraphicsItems as follows:

class baseGraphicItem : public QGraphicsItem
{
public:
    explicit baseGraphicItem(QVector<QPoint> data, operationType shape, QObject * parent = 0);

signals:

public slots:

public:
    virtual QRectF boundingRect() const;
    virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
    QPainterPath shape() const;
    virtual void hoverEnterEvent ( QGraphicsSceneHoverEvent * event );
    virtual void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event );


private:
    QPolygon vertex;
    operationType shapeType;

};

baseGraphicItem::baseGraphicItem(QVector<QPoint> data, operationType shape, QObject *parent) :
    QGraphicsItem(), vertex(data), shapeType(shape)
{
    qDebug() << vertex;
    this->setAcceptHoverEvents(false);
}

These are for the paint, boundingRect and shape.

void baseGraphicItem::paint(QPainter * painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    int i=0;

    // Following needs better code for polygons
    do painter->drawLine(vertex.at(i), vertex.at(i+1));
    while (i++<vertex.size()-2);
}

QRectF baseGraphicItem::boundingRect() const
{
    return vertex.boundingRect();
}

QPainterPath baseGraphicItem::shape() const
{
    QPainterPath path;
    path.addPolygon(vertex);
    return path;
}

Unfortunately, the selection works well with one line or polygon. But when a line is inside a polygon, it is almost always selecting the polygon instead of the line. Is this because of the boundingRect or the shape? Also how can I get the new coordinates stored in my QPolygon vertex? Thanks

Answer

TheDarkKnight picture TheDarkKnight · Mar 26, 2014

This all depends on exactly what you mean by redefining "the way items are selected and transformed"

Let's take QGraphicsLineItem as an example.

If I want this item to be moveable, I can call its function setFlag(QGraphicsItem::ItemIsMovable). Now the item can be clicked on and moved around in the scene. Of-course, this assumes the item is selectable, which can be achieved by setting the flag QGraphicsItem::ItemIsSelectable.

Now, if I want to be able to change a line's points, I could probably use its setLine function and keep redefining the line. However, it would be better to inherit directly from QGraphicsItem and create my own.

class MyLine : public QGraphicsItem
{
    Q_OBJECT

    public:
        virtual QRectF boundingRect();
        virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);
};

So this is what I need as a minimum to inherit from QGraphicsItem, as the boundingRect and paint functions are pure virtual in QGraphicsItem.

So, now we can add the start and end points to the class: -

class MyLine : public QGraphicsItem
{
    Q_OBJECT

    public:
        virtual QRectF boundingRect();
        virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);

    private:
        QPointF m_pointA;
        QPointF m_pointB;
};

Next, the class needs to draw itself in the paint function: -

void MyLine::paint(QPainter * painter, const QStyleOptionGraphicsItem* , QWidget*)
{
    // The painter's pen and brush could be set here first

    // Draw the line
    painter->drawLine(m_pointA, m_pointB);
}

The last thing to complete this class is the boundingRect function, which represents the visible area of the class: -

QRectF MyLine::boundingRect()
{
    return QRectF(m_pointA, m_pointB);
}

While this class is functionally complete, you'll find that the bounding rect is very large when the line is horizontal, which is a problem when selecting the object, so we can override the shape function to solve that

QPainterPath MyLine::shape() const
{
    QPainterPath path;
    path.moveTo(m_pointA);
    path.lineTo(m_pointB);
    return path;
}

Now that we have our own line class, we can add the mouseEvent handlers: -

class MyLine : public QGraphicsItem
{
    Q_OBJECT

    public:
        virtual QRectF boundingRect();
        QPainterPath shape() const
        virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0);


        virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
        virtual void mousePressEvent(QGraphicsSceneMouseEvent * event);
        virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);

    private:
        QPointF m_pointA;
        QPointF m_pointB;
};

With the event handlers, you need to store when the mouse is clicked in mouseMoveEvent. The point at which the mouse event is closest (m_pointA or m_PointB) is the one you can move and update in the mouseMoveEvent, until mouseReleaseEvent is called.

If the original mouseEvent in mousePressEvent is nearer the centre, rather than one of the points, then simply forward the event to the parent class for it to move the whole line.

Of-course, you can use this as a template for a polygon with a list of points, which you then draw in paint, add to a painterPath in shape and manipulate in the mouse events.