Selection of items in QGraphicsScene
Asked Answered
L

2

9

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

Lagerkvist answered 26/3, 2014 at 16:3 Comment(2)
Seems similar: #18818038Jenkins
If I read it correctly, there QGraphicsView was subclassed and used to capture events. I am doing this and I cannot capture the selection as it seems done by QGraphicsItem::mousepressevent and not byQGraphicsView::mousepressevent.Lagerkvist
K
8

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.

Kordofanian answered 26/3, 2014 at 16:44 Comment(9)
Thanks. Very detailed. Basically I need to make myGraphicsItem and myGraphicsPolygon from it, and so on. One question, what about adding the items to QGraphicsScene?Lagerkvist
Inherit from QGraphicsItem, or QGraphicsObject (if you want signal and slots) in order to create MyGraphicsLine and MyGraphicsPolygonKordofanian
I tried the above and used for a class that can accept polygons, but the boundary box is such that if i draw something inside another polygon, it is a nightmare for selecting the item that is inside. Any suggestions?Lagerkvist
Did you overload the shape function?Kordofanian
Yes. I did use the polygon itself there as shape return value.Lagerkvist
So, you said 'if I draw something inside another polygon...', do you mean in the paint function of the current polygon? If so, it sounds like that should be a separate object, if you want to select it separately from the main polygon. Or am I misunderstanding you?Kordofanian
I was not clear. I created two items in the main and displayed on the same scene. I can select both, but selecting a line when drawing inside a polygon (but as separate element) makes it rather difficult to select. I start to think it is because of its small boundingbox as if I click on the line I select the line, if I click near I select the polygon.Lagerkvist
The way I've solved this in the past is when you select the object you want to change, the object then brings up tools attached to the object. For example, if you select a line, it would display two rectangles, one at each end. These are the points you drag to change the line and they move the underlying point of the line. The tools would be child objects of the original item.Kordofanian
I think i understand. The best is probably to make a new item for the points bringing up a square. A line item from polygon which includes them. And a new polygon item which includes the lines. In this way I could differentiate every element of a polygon by inheritance.Lagerkvist
J
0

As this answer mentions, it might be easier to do this in the QGraphicItems themselves, especially if the actions are specific to certain items. In this case, the following code shows how it's done:

#include <QtWidgets>

class Item : public QGraphicsRectItem
{
public:
    Item() {
        setRect(0, 0, 100, 100);
        setFlag(QGraphicsItem::ItemIsMovable, true);
    }

    void mousePressEvent(QGraphicsSceneMouseEvent *event) {
        qDebug() << "Item";
        QGraphicsRectItem::mousePressEvent(event);
    }
};

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QGraphicsView view;
    QGraphicsScene *scene = new QGraphicsScene;
    scene->addItem(new Item());
    view.setScene(scene);
    view.resize(400, 400);
    view.show();

    return app.exec();
}

#include "main.moc"

For more information, see the QGraphicsItem documentation.

If you are only doing generic manipulations of the items that could apply to any QGraphicsItem subclass, then take a look at the Elastic Nodes Example.

Jenkins answered 26/3, 2014 at 16:38 Comment(1)
Merlin, Mitch, thanks. It looks that I cannot avoiding this route. For info, I am manipulating polygons only in order to draw floor plans. Here I need to stretch polygons by dragging lines or points. On top of that I am adding layers for objects that can only be moved along the polygons (as they represent walls).Lagerkvist

© 2022 - 2024 — McMap. All rights reserved.