1

I have a QGraphicsScene (let's call it mother scene) that includes items, and some of them include a QGraphicsScene as well (let's call them daughter scenes).

Observation : The selection in the daughter scene and in the mother scene don't bother each other. It means that, if I select an item in the mother scene, I can select items in the daughter scene and the items in the mother scene will remain selected.

My expected result : I would like my selection in a scene to clear the selection in any other scene.

[Edit for clarity] When I select an item in a scene, I would like to unselect the selected items in the other scenes, not to have several focuses in different scenes.

My motivation : I want to do that because when I use a keyboard shortcut, I don't know which scene will be chosen by Qt. When I have only one daughter, the mother is chosen (I would prefer the daughter).

My solution so far : In the daughter scene container item, the mousePressEvent clears the mother scene's selection. I find this solution pretty ugly and I would like to know if someone does know a better solution that would use some inner Qt features. Now it looks like a bad DIY solution that will bring many issues.

Thanks in advance !

[Edit : minimal example] In this example, we can select the two nested elements simultaneously. I would rather have only one selection at a time in my whole scene.

#include <QApplication>
#include <QGraphicsView>

#include <QGraphicsScene>
#include <QMainWindow>
#include <QGraphicsItem>
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsProxyWidget>

// Item that gets red contour when selected
class SimpleItem : public QGraphicsItem
{
public :
    SimpleItem():QGraphicsItem()
    {
        setFlag(QGraphicsItem::ItemIsSelectable, true);
    }

    QRectF boundingRect() const override { return QRectF(-20, -20, 40, 40);}

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->setPen(Qt::black);
        if(isSelected())
            painter->setPen(Qt::red);
        painter->setBrush(Qt::gray);
        painter->drawRect(boundingRect());
    }
};

// Item that contains a QGraphicsScene in a layout
// This item gets also a red contour when selected
class SceneItem : public QGraphicsWidget
{
public :
    SceneItem():QGraphicsWidget()
    {
        setFlag(QGraphicsItem::ItemIsSelectable, true);
        setFocusPolicy(Qt::ClickFocus);

        // Create the inner scene
        QGraphicsLinearLayout * layout = new QGraphicsLinearLayout;
        setLayout(layout);
        QGraphicsScene * scene = new QGraphicsScene;
        QGraphicsView * view = new QGraphicsView(scene);
        QGraphicsProxyWidget * proxy = new QGraphicsProxyWidget;
        layout->addItem(proxy);
        proxy->setWidget(view);

        // Add a simple item
        SimpleItem * simpleItem = new SimpleItem;
        scene->addItem(simpleItem);
    }

    QRectF boundingRect() const override { return QRectF(0, 0, 100, 100);}

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->setPen(Qt::black);
        if(isSelected())
            painter->setPen(Qt::red);
        painter->setBrush(Qt::lightGray);
        painter->drawRect(boundingRect());
    }
};

// Main
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow w;

    QGraphicsScene * scene = new QGraphicsScene;
    QGraphicsView * view = new QGraphicsView(scene);
    view->setDragMode(QGraphicsView::RubberBandDrag);
    w.setCentralWidget(view);

    SceneItem * item = new SceneItem;
    scene->addItem(item);

    w.show();

    return a.exec();
}
Gustavitch
  • 23
  • 3
  • It seems like a XY problem. Why do you nest scenes? – scopchanov Nov 03 '20 at 11:02
  • I'm pretty sure it's not an XY problem. I nest scene because I want some items to contain graphics scenes, that's my use case. – Gustavitch Nov 03 '20 at 12:09
  • _I nest scene because I want some items to contain graphics scenes_ That is obvious right from your post. If that the intended use of `QGraphicsScene` is, is a whole other story. In any case, without a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) it is hard to understand your problem, resp. to find a solution. – scopchanov Nov 03 '20 at 12:44
  • Thanks for looking at my problem. Here is a code sample. – Gustavitch Nov 03 '20 at 13:42
  • Please take a look at [this](https://www.qt.io/blog/2017/01/19/should-you-be-using-qgraphicsview), starting from _Graphics View supports embedding QWidgets_ to _The key to the high performance of Graphics View is reducing how much is painted each frame. QGraphicsWidget and QGraphicsProxyWidget together are huge performance killers because they can not be rendered in a efficient way._ – scopchanov Nov 03 '20 at 14:32

1 Answers1

0

Disclamer

I personally do not recommend embedding widgets in a QGraphicsScene:

The key to the high performance of Graphics View is reducing how much is painted each frame. QGraphicsWidget and QGraphicsProxyWidget together are huge performance killers because they can not be rendered in a efficient way.

The source of this citation, as well as more information on that topic could be found in this blog post:

Should you still be using QGraphicsView?

Solution

If I must at any cost use the exact approach the OP uses, my solution would be to:

  1. Use QGraphicsScene::selectionChanged to react to selection changes and
  2. Use QGraphicsScene::selectedItems to check if an item is selected.

Example

Here is the MVCE the OP provided, modified by me to demonstrate how the proposed solution could be implemented:

#include <QApplication>
#include <QMainWindow>
#include <QGraphicsLinearLayout>
#include <QGraphicsProxyWidget>
#include <QGraphicsView>
#include <QPainter>

class SimpleItem : public QGraphicsItem
{
public :
    explicit SimpleItem(QGraphicsItem *parent = nullptr) :
        QGraphicsItem(parent) { setFlag(ItemIsSelectable, true); }

    QRectF boundingRect() const override { return QRectF(-20, -20, 40, 40); }
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
               QWidget *) override {
        painter->setPen(isSelected() ? Qt::red : Qt::black);
        painter->setBrush(Qt::gray);
        painter->drawRect(boundingRect());
    }
};

class SceneItem : public QGraphicsWidget
{
    QGraphicsScene *m_scene;
public:
    explicit SceneItem(QGraphicsItem *parent = nullptr) :
        QGraphicsWidget(parent),
        m_scene(new QGraphicsScene(this)) {
        auto *layout = new QGraphicsLinearLayout;
        auto *view = new QGraphicsView(m_scene);
        auto *proxy = new QGraphicsProxyWidget(this);
        auto *simpleItem = new SimpleItem(this);

        m_scene->addItem(simpleItem);
        proxy->setWidget(view);
        layout->addItem(proxy);

        setLayout(layout);
        setFocusPolicy(Qt::ClickFocus);
        setFlag(QGraphicsItem::ItemIsSelectable, true);
    }

    QGraphicsScene *scene() const { return m_scene; }
    QRectF boundingRect() const override { return QRectF(0, 0, 100, 100);}
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
               QWidget *) override {
        painter->setPen(isSelected() ? Qt::red : Qt::black);
        painter->setBrush(Qt::lightGray);
        painter->drawRect(boundingRect());
    }
    int type() const override { return UserType; }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow w;
    auto *scene = new QGraphicsScene;
    auto *view = new QGraphicsView(scene);
    auto *item = new SceneItem;

    scene->addItem(item);
    view->setDragMode(QGraphicsView::RubberBandDrag);

    QObject::connect(item->scene(), &QGraphicsScene::selectionChanged, [item, scene](){
        if (!item->scene()->selectedItems().isEmpty())
            scene->clearSelection();
    });

    QObject::connect(scene, &QGraphicsScene::selectionChanged, [scene](){
        if (scene->selectedItems().isEmpty())
            return;

        for (auto *item : scene->items())
            if (item->type() == QGraphicsItem::UserType)
                static_cast<SceneItem *>(item)->scene()->clearSelection();
    });

    w.setCentralWidget(view);
    w.show();

    return a.exec();
}
scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • Thanks a lot for your answer. Both informations are very useful. For now I'll use the scene signals. I didn't think about them at all, and it is an acceptable solution for my situation. I'll surely look forward the QML scene. I stick to the graphics view "like a mussel to its rock" because I know it and I didn't make the effort to really go into QML. But I'll take a few days to evaluate if I can adapt my application quick enough. – Gustavitch Nov 04 '20 at 13:34
  • @Gustavitch, it is a hard decision. I am facing the same problem and I am looking forward to Qt 6, which promises a better QML. One more thing. If you like the answer, please consider upvoting it, when you have enough rep. points. – scopchanov Nov 04 '20 at 14:06
  • 1
    Yeah hard decisions when it comes to tech choices. Thanks again, of course I will upvote your answer ! – Gustavitch Nov 05 '20 at 14:26
  • @Gustavitch, that is what I've meant by `XY problem`. You are trying to fight with a particular issue, but the solution is to not use it at all ;) – scopchanov Nov 05 '20 at 14:28
  • 1
    Oh ok I'm so sorry I didn't know the signification of an 'XY problem'... I thought you told me that my coordinates were wrong, hence my selections probably as well... I thought you got my problem wrong. Thanks for this supplemental material ! – Gustavitch Nov 05 '20 at 14:59
  • @Gustavitch, :) I see. No worries! Good luck with your project! – scopchanov Nov 05 '20 at 15:01
  • 1
    Thanks ! Good luck with yours ! – Gustavitch Nov 05 '20 at 15:09